From b6d721374f970cca912477a3972bb857758d983d Mon Sep 17 00:00:00 2001
From: Devin Ragotzy <devin.ragotzy@gmail.com>
Date: Wed, 18 Nov 2020 08:36:12 -0500
Subject: [PATCH] Have Media db return optional content_type, conversion fixes

---
 src/client_server/media.rs      |  8 ++----
 src/client_server/membership.rs |  3 +--
 src/database/media.rs           | 44 +++++++++++++++++++--------------
 src/database/rooms.rs           |  6 ++---
 src/utils.rs                    | 17 +++++++++++++
 5 files changed, 47 insertions(+), 31 deletions(-)

diff --git a/src/client_server/media.rs b/src/client_server/media.rs
index 0c234884..e6bd182b 100644
--- a/src/client_server/media.rs
+++ b/src/client_server/media.rs
@@ -66,7 +66,7 @@ pub async fn get_content_route(
     {
         Ok(get_content::Response {
             file,
-            content_type: Some(content_type),
+            content_type,
             content_disposition: filename,
         }
         .into())
@@ -116,11 +116,7 @@ pub async fn get_content_thumbnail_route(
             .try_into()
             .map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
     )? {
-        Ok(get_content_thumbnail::Response {
-            file,
-            content_type: Some(content_type),
-        }
-        .into())
+        Ok(get_content_thumbnail::Response { file, content_type }.into())
     } else if &*body.server_name != db.globals.server_name() && body.allow_remote {
         let get_thumbnail_response = server_server::send_request(
             &db.globals,
diff --git a/src/client_server/membership.rs b/src/client_server/membership.rs
index 849fb7e2..47fcde1a 100644
--- a/src/client_server/membership.rs
+++ b/src/client_server/membership.rs
@@ -510,8 +510,7 @@ async fn join_room_by_id_helper(
             .expect("event is valid, we just created it"),
         );
 
-        // TODO fixup CanonicalJsonValue
-        // use that instead of serde_json::Map... maybe?
+        // Convert `serde_json;:Value` to `CanonicalJsonObj` for hashing/signing
         let mut canon_json_stub: BTreeMap<_, ruma::signatures::CanonicalJsonValue> =
             serde_json::from_value(join_event_stub_value).expect("json Value is canonical JSON");
 
diff --git a/src/database/media.rs b/src/database/media.rs
index bfc62078..89d48e16 100644
--- a/src/database/media.rs
+++ b/src/database/media.rs
@@ -5,7 +5,7 @@ use std::mem;
 
 pub struct FileMeta {
     pub filename: Option<String>,
-    pub content_type: String,
+    pub content_type: Option<String>,
     pub file: Vec<u8>,
 }
 
@@ -83,12 +83,14 @@ impl Media {
             let (key, file) = r?;
             let mut parts = key.rsplit(|&b| b == 0xff);
 
-            let content_type = utils::string_from_bytes(
-                parts
-                    .next()
-                    .ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?,
-            )
-            .map_err(|_| Error::bad_database("Content type in mediaid_file is invalid unicode."))?;
+            let content_type = parts
+                .next()
+                .map(|bytes| {
+                    Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
+                        Error::bad_database("Content type in mediaid_file is invalid unicode.")
+                    })?)
+                })
+                .transpose()?;
 
             let filename_bytes = parts
                 .next()
@@ -158,12 +160,14 @@ impl Media {
             let (key, file) = r?;
             let mut parts = key.rsplit(|&b| b == 0xff);
 
-            let content_type = utils::string_from_bytes(
-                parts
-                    .next()
-                    .ok_or_else(|| Error::bad_database("Invalid Media ID in db"))?,
-            )
-            .map_err(|_| Error::bad_database("Content type in mediaid_file is invalid unicode."))?;
+            let content_type = parts
+                .next()
+                .map(|bytes| {
+                    Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
+                        Error::bad_database("Content type in mediaid_file is invalid unicode.")
+                    })?)
+                })
+                .transpose()?;
 
             let filename_bytes = parts
                 .next()
@@ -189,12 +193,14 @@ impl Media {
             let (key, file) = r?;
             let mut parts = key.rsplit(|&b| b == 0xff);
 
-            let content_type = utils::string_from_bytes(
-                parts
-                    .next()
-                    .ok_or_else(|| Error::bad_database("Media ID in db is invalid."))?,
-            )
-            .map_err(|_| Error::bad_database("Content type in mediaid_file is invalid unicode."))?;
+            let content_type = parts
+                .next()
+                .map(|bytes| {
+                    Ok::<_, Error>(utils::string_from_bytes(bytes).map_err(|_| {
+                        Error::bad_database("Content type in mediaid_file is invalid unicode.")
+                    })?)
+                })
+                .transpose()?;
 
             let filename_bytes = parts
                 .next()
diff --git a/src/database/rooms.rs b/src/database/rooms.rs
index 3d5b890b..a95587cc 100644
--- a/src/database/rooms.rs
+++ b/src/database/rooms.rs
@@ -499,7 +499,6 @@ impl Rooms {
         Ok(())
     }
 
-    #[allow(clippy::too_many_arguments)]
     /// Creates a new persisted data unit and adds it to a room.
     ///
     /// By this point the incoming event should be fully authenticated, no auth happens
@@ -856,9 +855,8 @@ impl Rooms {
         };
 
         // Hash and sign
-        let mut pdu_json: BTreeMap<String, ruma::serde::CanonicalJsonValue> =
-            serde_json::from_value(serde_json::json!(&pdu))
-                .expect("event is valid, we just created it");
+        let mut pdu_json =
+            utils::to_canonical_object(&pdu).expect("event is valid, we just created it");
 
         pdu_json.remove("event_id");
 
diff --git a/src/utils.rs b/src/utils.rs
index edcf48a1..c82e6feb 100644
--- a/src/utils.rs
+++ b/src/utils.rs
@@ -1,6 +1,7 @@
 use argon2::{Config, Variant};
 use cmp::Ordering;
 use rand::prelude::*;
+use ruma::serde::{try_from_json_map, CanonicalJsonError, CanonicalJsonObject};
 use sled::IVec;
 use std::{
     cmp,
@@ -94,3 +95,19 @@ pub fn common_elements(
             .all(|b| b)
     }))
 }
+
+/// Fallible conversion from any value that implements `Serialize` to a `CanonicalJsonObject`.
+///
+/// `value` must serialize to an `serde_json::Value::Object`.
+pub fn to_canonical_object<T: serde::Serialize>(
+    value: T,
+) -> Result<CanonicalJsonObject, CanonicalJsonError> {
+    use serde::ser::Error;
+
+    match serde_json::to_value(value).map_err(CanonicalJsonError::SerDe)? {
+        serde_json::Value::Object(map) => try_from_json_map(map),
+        _ => Err(CanonicalJsonError::SerDe(serde_json::Error::custom(
+            "Value must be an object",
+        ))),
+    }
+}