From cb3e0c620ab478728507d32183483b5845a66a04 Mon Sep 17 00:00:00 2001 From: Charles Hall Date: Tue, 17 Sep 2024 21:16:22 -0700 Subject: [PATCH] improve media key decoding logs On my HS I observed 5 instances of keys with the following format: * MXC bytes. * A 0xFF byte. * 4 bytes where the width and height are supposed to be, which are supposed to be 8 bytes in length. * 3 consecutive 0xFF bytes. This means that the `content-type` and `content-disposition` sections both parse as the empty string, and there's an extra separator at the end too. * Extra bytes, all of which were `image/png`. The 4 bytes where the width and height are supposed to be were one of: * 003ED000 * 003EE000 * 003EF001 Which seems to have some kind of pattern to it... After much digging, we have absolutely no idea what could've caused this. Cursed. --- src/database/key_value/media.rs | 18 +++++++++++------- src/utils.rs | 12 ++++++++++++ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/src/database/key_value/media.rs b/src/database/key_value/media.rs index 5b097f51..e3f771d3 100644 --- a/src/database/key_value/media.rs +++ b/src/database/key_value/media.rs @@ -19,24 +19,28 @@ struct MediaFileKeyParts { impl TryFrom<&MediaFileKey> for MediaFileKeyParts { type Error = Error; + #[tracing::instrument( + err, + fields(key = utils::u8_slice_to_hex(key.as_bytes())), + )] fn try_from(key: &MediaFileKey) -> Result { let mut parts = key.as_bytes().split(|&b| b == 0xFF); let mxc_bytes = parts .next() - .ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?; + .ok_or_else(|| Error::BadDatabase("Media ID in db is invalid."))?; let mxc = utils::string_from_bytes(mxc_bytes) .map_err(|_| { - Error::bad_database("Media MXC URI in db is invalid unicode.") + Error::BadDatabase("Media MXC URI in db is invalid unicode.") })? .into(); let thumbnail_size_bytes = parts .next() - .ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?; + .ok_or_else(|| Error::BadDatabase("Media ID in db is invalid."))?; let thumbnail_size_bytes: &[u8; 8] = thumbnail_size_bytes.try_into().map_err(|_| { - Error::bad_database("Media ID thumbnail size in db is invalid") + Error::BadDatabase("Media ID thumbnail size in db is invalid") })?; let width = u32::from_be_bytes( thumbnail_size_bytes[..4].try_into().expect("should be 4 bytes"), @@ -49,7 +53,7 @@ impl TryFrom<&MediaFileKey> for MediaFileKeyParts { .next() .map(|bytes| { utils::string_from_bytes(bytes).map_err(|_| { - Error::bad_database( + Error::BadDatabase( "Content type in mediaid_file is invalid unicode.", ) }) @@ -58,14 +62,14 @@ impl TryFrom<&MediaFileKey> for MediaFileKeyParts { let content_disposition_bytes = parts .next() - .ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?; + .ok_or_else(|| Error::BadDatabase("Media ID in db is invalid."))?; let content_disposition = if content_disposition_bytes.is_empty() { None } else { Some(utils::string_from_bytes(content_disposition_bytes).map_err( |_| { - Error::bad_database( + Error::BadDatabase( "Content Disposition in mediaid_file is invalid \ unicode.", ) diff --git a/src/utils.rs b/src/utils.rs index 504d21b1..e0d62063 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -4,6 +4,7 @@ pub(crate) mod on_demand_hashmap; use std::{ borrow::Cow, cmp, fmt, + fmt::Write, str::FromStr, time::{SystemTime, UNIX_EPOCH}, }; @@ -366,6 +367,17 @@ pub(crate) fn curlify(req: &http::Request) -> Option { ) } +/// Format a u8 slice as an uppercase hex string +/// +/// The output does not contain a leading `0x` nor any non-hex characters (e.g. +/// whitespace or commas do not appear in the output). +pub(crate) fn u8_slice_to_hex(slice: &[u8]) -> String { + slice.iter().fold(String::new(), |mut acc, x| { + write!(acc, "{x:X}").expect("in-memory write should succeed"); + acc + }) +} + #[cfg(test)] mod tests { use crate::utils::dbg_truncate_str;