diff --git a/Cargo.lock b/Cargo.lock index d3cd5a6d4f..584c0a1125 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4173,6 +4173,14 @@ dependencies = [ "unicase", ] +[[package]] +name = "mimetypes" +version = "0.0.1" +dependencies = [ + "errors", + "infer 0.11.0", +] + [[package]] name = "minidom" version = "0.15.0" diff --git a/Cargo.toml b/Cargo.toml index f9f834f51f..5a32ab2c75 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,6 +35,7 @@ members = [ "crates/lib/idempotency", "crates/lib/jobs_common", "crates/lib/media", + "crates/lib/mimetypes", "crates/lib/mysql_queries", "crates/lib/redis_common", "crates/lib/sqlite_queries", diff --git a/crates/lib/mimetypes/Cargo.toml b/crates/lib/mimetypes/Cargo.toml new file mode 100644 index 0000000000..d4d1cde116 --- /dev/null +++ b/crates/lib/mimetypes/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "mimetypes" +edition = "2018" +version = "0.0.1" +authors = [ + "Brandon Thomas ", + "Brandon Thomas ", +] +publish = false + +[lib] +# NB: Public "mimetypes" crate does not exist, so it's safe to use for now. +name = "mimetypes" +path = "src/lib.rs" + +[dependencies] +# Internal +errors = { path = "../../lib/errors" } + +# External +infer = "0.11" # Mimetype detection + +[dev-dependencies] +# None yet diff --git a/crates/lib/mimetypes/src/lib.rs b/crates/lib/mimetypes/src/lib.rs new file mode 100644 index 0000000000..bdb9492625 --- /dev/null +++ b/crates/lib/mimetypes/src/lib.rs @@ -0,0 +1,22 @@ +//! mimetypes +//! +//! The purpose of this library is to perform mimetype detection routines on files, raw bytes, +//! streams, etc. +//! + +// Never allow these +#![forbid(private_in_public)] +#![forbid(unused_must_use)] // NB: It's unsafe to not close/check some things + +// Okay to toggle +#![forbid(unreachable_patterns)] +#![forbid(unused_imports)] +#![forbid(unused_mut)] +#![forbid(unused_variables)] + +// Always allow +#![allow(dead_code)] +#![allow(non_snake_case)] + +pub mod mimetype_for_bytes; +pub mod mimetype_for_file; diff --git a/crates/lib/mimetypes/src/mimetype_for_bytes.rs b/crates/lib/mimetypes/src/mimetype_for_bytes.rs new file mode 100644 index 0000000000..b71ab972d5 --- /dev/null +++ b/crates/lib/mimetypes/src/mimetype_for_bytes.rs @@ -0,0 +1,41 @@ + +const DEFAULT_BINARY_MIMETYPE : &'static str = "application/octet-stream"; + +pub fn get_mimetype_for_bytes(bytes: &[u8]) -> Option<&'static str> { + infer::get(bytes) + .map(|typ| typ.mime_type()) +} + +pub fn get_mimetype_for_bytes_or_default(bytes: &[u8]) -> &'static str { + infer::get(bytes) + .map(|typ| typ.mime_type()) + .unwrap_or(DEFAULT_BINARY_MIMETYPE) +} + +#[cfg(test)] +mod tests { + use crate::mimetype_for_bytes::{get_mimetype_for_bytes, get_mimetype_for_bytes_or_default}; + + #[test] + fn test_mimetype_png() { + // NB: From "Hanashi Mask.png" + let bytes : Vec = vec![ + 137, 80, 78, 71, 13, 10, 26, 10, + 0, 0, 0, 13, 73, 72, 68, 82, + 0, 0, 20, 193, 0, 0, 15, 239, + 8, 6, 0, 0, 0, 163, 131, 119, + 122, 0, 0, 0, + ]; + + assert_eq!(get_mimetype_for_bytes(&bytes), Some("image/png")); + assert_eq!(get_mimetype_for_bytes_or_default(&bytes), "image/png"); + } + + #[test] + fn test_mimetype_unknown() { + let bytes : Vec = vec![]; + + assert_eq!(get_mimetype_for_bytes(&bytes), None); + assert_eq!(get_mimetype_for_bytes_or_default(&bytes), "application/octet-stream"); + } +} diff --git a/crates/lib/mimetypes/src/mimetype_for_file.rs b/crates/lib/mimetypes/src/mimetype_for_file.rs new file mode 100644 index 0000000000..79c39374b0 --- /dev/null +++ b/crates/lib/mimetypes/src/mimetype_for_file.rs @@ -0,0 +1,43 @@ +use std::path::Path; + +const DEFAULT_BINARY_MIMETYPE : &'static str = "application/octet-stream"; + +pub fn get_mimetype_for_file>(file: P) -> std::io::Result> { + infer::get_from_path(file) + .map(|maybe_type| maybe_type.map(|typ| typ.mime_type())) +} + +pub fn get_mimetype_for_file_or_default>(file: P) -> std::io::Result<&'static str> { + get_mimetype_for_file(file) + .map(|maybe_type| match maybe_type { + Some(mimetype) => mimetype, + None => DEFAULT_BINARY_MIMETYPE, + }) +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + use crate::mimetype_for_file::get_mimetype_for_file; + + fn test_file(path_from_repo_root: &str) -> PathBuf { + // https://doc.rust-lang.org/cargo/reference/environment-variables.html + let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + path.push(format!("../../../{}", path_from_repo_root)); + path + } + + #[test] + fn test_mimetype_mp3() { + let path = test_file("test_data/audio/mp3/super_mario_rpg_beware_the_forests_mushrooms.mp3"); + + assert_eq!(get_mimetype_for_file(&path).unwrap(), Some("audio/mpeg")); + } + + #[test] + fn test_mimetype_unknown() { + let path = test_file("test_data/video/mp4/golden_sun_garoh.mp4"); + + assert_eq!(get_mimetype_for_file(&path).unwrap(), Some("video/mp4")); + } +} diff --git a/crates/service/web/storyteller_web/src/http_server/endpoints/media_uploads/upload_media.rs b/crates/service/web/storyteller_web/src/http_server/endpoints/media_uploads/upload_media.rs index 5b0f3e8038..efae782004 100644 --- a/crates/service/web/storyteller_web/src/http_server/endpoints/media_uploads/upload_media.rs +++ b/crates/service/web/storyteller_web/src/http_server/endpoints/media_uploads/upload_media.rs @@ -14,8 +14,6 @@ use crate::http_server::web_utils::response_error_helpers::to_simple_json_error; use crate::server_state::ServerState; use crate::validations::model_uploads::validate_model_title; use crate::validations::validate_idempotency_token_format::validate_idempotency_token_format; -use mysql_queries::payloads::media_upload_modification_details::MediaUploadModificationDetails; -use mysql_queries::queries::media_uploads::insert_media_upload::{Args, insert_media_upload}; use enums::by_table::media_uploads::media_upload_type::MediaUploadType; use enums::common::visibility::Visibility; use files::hash::hash_bytes_sha2; @@ -24,6 +22,9 @@ use http_server_common::request::get_request_ip::get_request_ip; use http_server_common::response::serialize_as_json_error::serialize_as_json_error; use log::{info, warn, log, error}; use media::decode_basic_audio_info::decode_basic_audio_info; +use mysql_queries::payloads::media_upload_modification_details::MediaUploadModificationDetails; +use mysql_queries::queries::media_uploads::get_media_upload_by_uuid::{get_media_upload_by_uuid, get_media_upload_by_uuid_with_connection}; +use mysql_queries::queries::media_uploads::insert_media_upload::{Args, insert_media_upload}; use regex::Regex; use sqlx::error::DatabaseError; use sqlx::error::Error::Database; @@ -35,7 +36,6 @@ use symphonia::core::formats::FormatOptions; use symphonia::core::io::{MediaSourceStream, ReadOnlySource}; use symphonia::core::meta::MetadataOptions; use symphonia::core::probe::Hint; -use mysql_queries::queries::media_uploads::get_media_upload_by_uuid::{get_media_upload_by_uuid, get_media_upload_by_uuid_with_connection}; use tokens::files::media_upload::MediaUploadToken; #[derive(Serialize)]