diff --git a/src/activitypub/fetcher/fetchers.rs b/src/activitypub/fetcher/fetchers.rs index cee41b6..a5ad72b 100644 --- a/src/activitypub/fetcher/fetchers.rs +++ b/src/activitypub/fetcher/fetchers.rs @@ -9,7 +9,7 @@ use crate::activitypub::actors::types::{Actor, ActorAddress}; use crate::activitypub::constants::ACTIVITY_CONTENT_TYPE; use crate::config::Instance; use crate::http_signatures::create::{create_http_signature, SignatureError}; -use crate::utils::files::{save_file, FileError}; +use crate::utils::files::save_file; use crate::webfinger::types::JsonResourceDescriptor; const FETCHER_CONNECTION_TIMEOUT: u64 = 30; @@ -26,7 +26,7 @@ pub enum FetchError { JsonParseError(#[from] serde_json::Error), #[error(transparent)] - FileError(#[from] FileError), + FileError(#[from] std::io::Error), #[error("{0}")] OtherError(&'static str), @@ -86,7 +86,7 @@ pub async fn fetch_file( let client = build_client()?; let response = client.get(url).send().await?; let file_data = response.bytes().await?; - let (file_name, media_type) = save_file(file_data.to_vec(), output_dir)?; + let (file_name, media_type) = save_file(file_data.to_vec(), output_dir, None)?; Ok((file_name, media_type)) } diff --git a/src/mastodon_api/accounts/types.rs b/src/mastodon_api/accounts/types.rs index 57aa86c..aca8cc4 100644 --- a/src/mastodon_api/accounts/types.rs +++ b/src/mastodon_api/accounts/types.rs @@ -6,6 +6,7 @@ use uuid::Uuid; use crate::errors::ValidationError; use crate::frontend::get_subscription_page_url; +use crate::mastodon_api::uploads::{UploadError, save_validated_b64_file}; use crate::models::profiles::types::{ DbActorProfile, ExtraField, @@ -20,7 +21,7 @@ use crate::models::users::types::{ validate_local_username, User, }; -use crate::utils::files::{FileError, save_validated_b64_file, get_file_url}; +use crate::utils::files::get_file_url; /// https://docs.joinmastodon.org/entities/field/ #[derive(Serialize)] @@ -182,7 +183,7 @@ fn process_b64_image_field_value( form_value: Option, db_value: Option, output_dir: &Path, -) -> Result, FileError> { +) -> Result, UploadError> { let maybe_file_name = match form_value { Some(b64_data) => { if b64_data.is_empty() { @@ -210,7 +211,7 @@ impl AccountUpdateData { current_identity_proofs: &[IdentityProof], current_payment_options: &[PaymentOption], media_dir: &Path, - ) -> Result { + ) -> Result { let avatar = process_b64_image_field_value( self.avatar, current_avatar.clone(), media_dir, )?; diff --git a/src/mastodon_api/accounts/views.rs b/src/mastodon_api/accounts/views.rs index 1ba6120..1f5176f 100644 --- a/src/mastodon_api/accounts/views.rs +++ b/src/mastodon_api/accounts/views.rs @@ -30,6 +30,7 @@ use crate::mastodon_api::oauth::auth::get_current_user; use crate::mastodon_api::pagination::get_paginated_response; use crate::mastodon_api::statuses::helpers::build_status_list; use crate::mastodon_api::statuses::types::Status; +use crate::mastodon_api::uploads::UploadError; use crate::models::posts::queries::get_posts_by_author; use crate::models::profiles::queries::{ get_profile_by_id, @@ -64,7 +65,6 @@ use crate::utils::crypto::{ generate_private_key, serialize_private_key, }; -use crate::utils::files::FileError; use super::helpers::get_relationship; use super::types::{ Account, @@ -197,10 +197,10 @@ async fn update_credentials( ) .map_err(|err| { match err { - FileError::Base64DecodingError(_) => { + UploadError::Base64DecodingError(_) => { HttpError::ValidationError("base64 decoding error".into()) }, - FileError::InvalidMediaType => { + UploadError::InvalidMediaType => { HttpError::ValidationError("invalid media type".into()) }, _ => HttpError::InternalError, diff --git a/src/mastodon_api/media/views.rs b/src/mastodon_api/media/views.rs index 6cf3d70..260848b 100644 --- a/src/mastodon_api/media/views.rs +++ b/src/mastodon_api/media/views.rs @@ -5,8 +5,8 @@ use crate::config::Config; use crate::database::{Pool, get_database_client}; use crate::errors::HttpError; use crate::mastodon_api::oauth::auth::get_current_user; +use crate::mastodon_api::uploads::{UploadError, save_b64_file}; use crate::models::attachments::queries::create_attachment; -use crate::utils::files::{FileError, save_b64_file}; use super::types::{AttachmentCreateData, Attachment}; #[post("")] @@ -22,7 +22,9 @@ async fn create_attachment_view( &attachment_data.file, &config.media_dir(), ).map_err(|err| match err { - FileError::Base64DecodingError(err) => HttpError::ValidationError(err.to_string()), + UploadError::Base64DecodingError(err) => { + HttpError::ValidationError(err.to_string()) + }, _ => HttpError::InternalError, })?; let db_attachment = create_attachment( diff --git a/src/mastodon_api/mod.rs b/src/mastodon_api/mod.rs index dd3a38b..99a3e30 100644 --- a/src/mastodon_api/mod.rs +++ b/src/mastodon_api/mod.rs @@ -9,5 +9,6 @@ mod pagination; pub mod search; pub mod statuses; pub mod timelines; +mod uploads; const MASTODON_API_VERSION: &str = "3.0.0"; diff --git a/src/mastodon_api/uploads.rs b/src/mastodon_api/uploads.rs new file mode 100644 index 0000000..abb35be --- /dev/null +++ b/src/mastodon_api/uploads.rs @@ -0,0 +1,39 @@ +use std::path::Path; + +use crate::utils::files::{save_file, sniff_media_type}; + +#[derive(thiserror::Error, Debug)] +pub enum UploadError { + #[error(transparent)] + WriteError(#[from] std::io::Error), + + #[error("base64 decoding error")] + Base64DecodingError(#[from] base64::DecodeError), + + #[error("invalid media type")] + InvalidMediaType, +} + +pub fn save_b64_file( + b64data: &str, + output_dir: &Path, +) -> Result<(String, Option), UploadError> { + let data = base64::decode(b64data)?; + Ok(save_file(data, output_dir, None)?) +} + +pub fn save_validated_b64_file( + b64data: &str, + output_dir: &Path, + media_type_prefix: &str, +) -> Result<(String, String), UploadError> { + let data = base64::decode(b64data)?; + let media_type = sniff_media_type(&data) + .ok_or(UploadError::InvalidMediaType)?; + if !media_type.starts_with(media_type_prefix) { + return Err(UploadError::InvalidMediaType); + }; + let (file_name, _) = + save_file(data, output_dir, Some(media_type.clone()))?; + Ok((file_name, media_type)) +} diff --git a/src/utils/files.rs b/src/utils/files.rs index a4b0bc9..db396d1 100644 --- a/src/utils/files.rs +++ b/src/utils/files.rs @@ -4,6 +4,7 @@ use std::fs::{ File, Permissions, }; +use std::io::Error; use std::io::prelude::*; use std::os::unix::fs::PermissionsExt; use std::path::Path; @@ -12,19 +13,7 @@ use mime_guess::get_mime_extensions_str; use mime_sniffer::MimeTypeSniffer; use sha2::{Digest, Sha256}; -#[derive(thiserror::Error, Debug)] -pub enum FileError { - #[error(transparent)] - WriteError(#[from] std::io::Error), - - #[error("base64 decoding error")] - Base64DecodingError(#[from] base64::DecodeError), - - #[error("invalid media type")] - InvalidMediaType, -} - -fn sniff_media_type(data: &[u8]) -> Option { +pub fn sniff_media_type(data: &[u8]) -> Option { data.sniff_mime_type().map(|val| val.to_string()) } @@ -42,13 +31,13 @@ fn get_file_name(data: &[u8], media_type: Option<&str>) -> String { file_name } -pub fn write_file(data: &[u8], file_path: &Path) -> Result<(), FileError> { +pub fn write_file(data: &[u8], file_path: &Path) -> Result<(), Error> { let mut file = File::create(file_path)?; file.write_all(data)?; Ok(()) } -pub fn set_file_permissions(file_path: &Path, mode: u32) -> Result<(), FileError> { +pub fn set_file_permissions(file_path: &Path, mode: u32) -> Result<(), Error> { let permissions = Permissions::from_mode(mode); set_permissions(file_path, permissions)?; Ok(()) @@ -57,43 +46,16 @@ pub fn set_file_permissions(file_path: &Path, mode: u32) -> Result<(), FileError pub fn save_file( data: Vec, output_dir: &Path, -) -> Result<(String, Option), FileError> { - let media_type = sniff_media_type(&data); + media_type: Option, +) -> Result<(String, Option), Error> { + // Sniff media type if not provided + let media_type = media_type.or(sniff_media_type(&data)); let file_name = get_file_name(&data, media_type.as_deref()); let file_path = output_dir.join(&file_name); write_file(&data, &file_path)?; Ok((file_name, media_type)) } -pub fn save_b64_file( - b64data: &str, - output_dir: &Path, -) -> Result<(String, Option), FileError> { - let data = base64::decode(b64data)?; - let media_type = sniff_media_type(&data); - let file_name = get_file_name(&data, media_type.as_deref()); - let file_path = output_dir.join(&file_name); - write_file(&data, &file_path)?; - Ok((file_name, media_type)) -} - -pub fn save_validated_b64_file( - b64data: &str, - output_dir: &Path, - media_type_prefix: &str, -) -> Result<(String, String), FileError> { - let data = base64::decode(b64data)?; - let media_type = sniff_media_type(&data) - .ok_or(FileError::InvalidMediaType)?; - if !media_type.starts_with(media_type_prefix) { - return Err(FileError::InvalidMediaType); - } - let file_name = get_file_name(&data, Some(&media_type)); - let file_path = output_dir.join(&file_name); - write_file(&data, &file_path)?; - Ok((file_name, media_type)) -} - pub fn get_file_url(instance_url: &str, file_name: &str) -> String { format!("{}/media/{}", instance_url, file_name) }