Skip to content

Commit

Permalink
Fix mbtiles validate uppercase hash (#882)
Browse files Browse the repository at this point in the history
hash must be upper case to pass validation.

TODO (possibly separate PR) - need some tests for this
  • Loading branch information
nyurik authored Sep 14, 2023
1 parent 62fcdab commit 95ce71c
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 11 deletions.
4 changes: 2 additions & 2 deletions martin-mbtiles/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ pub enum MbtError {
#[error("Integrity check failed for MBTile file {0} for the following reasons: \n {1:?}")]
FailedIntegrityCheck(String, Vec<String>),

#[error("Invalid tile data for MBTile file {0}")]
InvalidTileData(String),
#[error("At least one tile has mismatching hash: stored value is `{1}` != computed value `{2}` in MBTile file {0}")]
IncorrectTileHash(String, String, String),

#[error("Computed aggregate tiles hash {0} does not match tile data in metadata {1} for MBTile file {2}")]
AggHashMismatch(String, String, String),
Expand Down
36 changes: 27 additions & 9 deletions martin-mbtiles/src/mbtiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ use serde::ser::SerializeStruct;
use serde::Serialize;
use serde_json::{Value as JSONValue, Value};
use sqlite_hashes::register_md5_function;
use sqlite_hashes::rusqlite::{Connection as RusqliteConnection, Connection, OpenFlags};
use sqlite_hashes::rusqlite::{
Connection as RusqliteConnection, Connection, OpenFlags, OptionalExtension,
};
use sqlx::sqlite::SqliteRow;
use sqlx::{query, Row, SqliteExecutor};
use tilejson::{tilejson, Bounds, Center, TileJSON};
Expand All @@ -25,7 +27,7 @@ use crate::mbtiles_queries::{
is_flat_tables_type, is_flat_with_hash_tables_type, is_normalized_tables_type,
};
use crate::MbtError::{
AggHashMismatch, AggHashValueNotFound, FailedIntegrityCheck, InvalidTileData,
AggHashMismatch, AggHashValueNotFound, FailedIntegrityCheck, IncorrectTileHash,
};

#[derive(Clone, Debug, PartialEq, Serialize)]
Expand Down Expand Up @@ -540,24 +542,40 @@ impl Mbtiles {
where
for<'e> &'e mut T: SqliteExecutor<'e>,
{
// Note that hex() always returns upper-case HEX values
let sql = match self.detect_type(&mut *conn).await? {
MbtType::Flat => {
println!("Skipping per-tile hash validation because this is a flat MBTiles file");
return Ok(());
}
MbtType::FlatWithHash => {
"SELECT 1 FROM tiles_with_hash WHERE tile_hash != hex(md5(tile_data)) LIMIT 1;"
"SELECT expected, computed FROM (
SELECT
upper(tile_hash) AS expected,
hex(md5(tile_data)) AS computed
FROM tiles_with_hash
) AS t
WHERE expected != computed
LIMIT 1;"
}
MbtType::Normalized => {
"SELECT 1 FROM images WHERE tile_id != hex(md5(tile_data)) LIMIT 1;"
"SELECT expected, computed FROM (
SELECT
upper(tile_id) AS expected,
hex(md5(tile_data)) AS computed
FROM images
) AS t
WHERE expected != computed
LIMIT 1;"
}
};

if self.open_with_hashes(true)?.prepare(sql)?.exists(())? {
return Err(InvalidTileData(self.filepath().to_string()));
}

Ok(())
self.open_with_hashes(true)?
.query_row_and_then(sql, [], |r| Ok((r.get(0)?, r.get(1)?)))
.optional()?
.map_or(Ok(()), |v: (String, String)| {
Err(IncorrectTileHash(self.filepath().to_string(), v.0, v.1))
})
}
}

Expand Down

0 comments on commit 95ce71c

Please sign in to comment.