From 7f674234de058c82bdff55fcedf628197aac4b08 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Wed, 13 Sep 2023 16:19:22 -0400 Subject: [PATCH] Fix mbtiles validate uppercase hash hash must be upper case to pass validation. TODO (possibly separate PR) - need some tests for this --- martin-mbtiles/src/errors.rs | 4 ++-- martin-mbtiles/src/mbtiles.rs | 36 ++++++++++++++++++++++++++--------- 2 files changed, 29 insertions(+), 11 deletions(-) diff --git a/martin-mbtiles/src/errors.rs b/martin-mbtiles/src/errors.rs index 4788bedbe..43d200037 100644 --- a/martin-mbtiles/src/errors.rs +++ b/martin-mbtiles/src/errors.rs @@ -23,8 +23,8 @@ pub enum MbtError { #[error("Integrity check failed for MBTile file {0} for the following reasons: \n {1:?}")] FailedIntegrityCheck(String, Vec), - #[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), diff --git a/martin-mbtiles/src/mbtiles.rs b/martin-mbtiles/src/mbtiles.rs index b6b71957f..4912e4e72 100644 --- a/martin-mbtiles/src/mbtiles.rs +++ b/martin-mbtiles/src/mbtiles.rs @@ -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}; @@ -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)] @@ -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)) + }) } }