diff --git a/src/api/client_server/media.rs b/src/api/client_server/media.rs index 75a62d9c..48b4f56a 100644 --- a/src/api/client_server/media.rs +++ b/src/api/client_server/media.rs @@ -4,9 +4,10 @@ use std::time::Duration; use crate::{ - service::media::{FileMeta, UrlPreviewData}, config::UrlPreviewPermission, - services, utils, Error, Result, Ruma}; + service::media::{FileMeta, UrlPreviewData}, + services, utils, Error, Result, Ruma, +}; use hickory_resolver::error::ResolveErrorKind; use http::header::{CONTENT_DISPOSITION, CONTENT_TYPE}; use ruma::{ @@ -16,9 +17,7 @@ use ruma::{ get_content, get_content_as_filename, get_content_thumbnail, get_media_config, }, error::ErrorKind, - media::{ - self, create_content, get_media_preview, - }, + media::{self, create_content, get_media_preview}, }, federation::authenticated_media::{self as federation_media, FileOrLocation}, }, @@ -27,12 +26,10 @@ use ruma::{ ServerName, UInt, }; -use { - webpage::HTML, - reqwest::Url, - std::{io::Cursor, net::IpAddr, sync::Arc}, - image::io::Reader as ImgReader, -}; +use image::io::Reader as ImgReader; +use reqwest::Url; +use std::{io::Cursor, net::IpAddr, sync::Arc}; +use webpage::HTML; const MXC_LENGTH: usize = 32; @@ -58,17 +55,15 @@ pub async fn get_media_config_auth_route( }) } -async fn download_image( - client: &reqwest::Client, - url: &str, -) -> Result { +async fn download_image(client: &reqwest::Client, url: &str) -> Result { let image = client.get(url).send().await?.bytes().await?; let mxc = format!( "mxc://{}/{}", services().globals.server_name(), utils::random_string(MXC_LENGTH) ); - services().media + services() + .media .create(mxc.clone(), None, None, &image) .await?; @@ -89,10 +84,7 @@ async fn download_image( }) } -async fn download_html( - client: &reqwest::Client, - url: &str, -) -> Result { +async fn download_html(client: &reqwest::Client, url: &str) -> Result { let max_download_size = 300_000; let mut response = client.get(url).send().await?; @@ -122,7 +114,11 @@ async fn download_html( let props = html.opengraph.properties; /* use OpenGraph title/description, but fall back to HTML if not available */ - data.title = props.get("title").cloned().or(html.title).unwrap_or(String::from(url)); + data.title = props + .get("title") + .cloned() + .or(html.title) + .unwrap_or(String::from(url)); data.description = props.get("description").cloned().or(html.description); Ok(data) } @@ -169,7 +165,7 @@ fn is_ip_external(addr: &IpAddr) -> bool { // AS112-v6 (`2001:4:112::/48`) || matches!(ip6.segments(), [0x2001, 4, 0x112, _, _, _, _, _]) // ORCHIDv2 (`2001:20::/28`) - || matches!(ip6.segments(), [0x2001, b, _, _, _, _, _, _] if b >= 0x20 && b <= 0x2F) + || matches!(ip6.segments(), [0x2001, b, _, _, _, _, _, _] if (0x20..=0x2f).contains(&b)) )) || ((ip6.segments()[0] == 0x2001) && (ip6.segments()[1] == 0xdb8)) // is_documentation() || ((ip6.segments()[0] & 0xfe00) == 0xfc00) // is_unique_local() @@ -187,28 +183,39 @@ async fn request_url_preview(url: &Url) -> Result { let dns_resolver = services().globals.dns_resolver(); match dns_resolver.lookup_ip(format!("{host}.")).await { Err(_) => { - return Err(Error::BadServerResponse("Failed to resolve media preview host")); - }, + return Err(Error::BadServerResponse( + "Failed to resolve media preview host", + )); + } Ok(lookup) if lookup.iter().any(|ip| !is_ip_external(&ip)) => { - return Err(Error::BadRequest(ErrorKind::Unknown, "Requesting from this address forbidden")); - }, - Ok(_) => { }, + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Requesting from this address forbidden", + )); + } + Ok(_) => {} } // Spamhaus API is over DNS. Query the API domain, no result = no block // https://docs.spamhaus.com/datasets/docs/source/70-access-methods/data-query-service/040-dqs-queries.html if services().globals.url_previews().use_spamhaus_denylist { let resolver = services().globals.dns_resolver(); - match resolver.lookup_ip(format!("{host}.dbl.spamhaus.org.")).await { + match resolver + .lookup_ip(format!("{host}.dbl.spamhaus.org.")) + .await + { Err(e) => { - if let ResolveErrorKind::NoRecordsFound { .. } = e.kind() { } - else { + if let ResolveErrorKind::NoRecordsFound { .. } = e.kind() { + } else { tracing::log::warn!("Failed to check Spamhaus denylist: {}", e); } - }, + } Ok(_) => { - return Err(Error::BadRequest(ErrorKind::Unknown, "Domain fails reputation check")); - }, + return Err(Error::BadRequest( + ErrorKind::Unknown, + "Domain fails reputation check", + )); + } } } @@ -239,7 +246,10 @@ async fn request_url_preview(url: &Url) -> Result { } }; - services().media.set_url_preview(url.as_str(), &data).await?; + services() + .media + .set_url_preview(url.as_str(), &data) + .await?; Ok(data) } @@ -264,7 +274,7 @@ async fn get_url_preview(url: &Url) -> Result { match services().media.get_url_preview(url.as_str()).await { Some(preview) => Ok(preview), - None => request_url_preview(url).await + None => request_url_preview(url).await, } } @@ -276,10 +286,10 @@ fn url_preview_allowed(url: &Url) -> bool { match preview_config.default { UrlPreviewPermission::Forbid => { preview_config.exceptions.iter().any(|ex| ex.matches(&host)) - }, + } UrlPreviewPermission::Allow => { !preview_config.exceptions.iter().any(|ex| ex.matches(&host)) - }, + } } } @@ -291,21 +301,14 @@ pub async fn get_media_preview_route( ) -> Result { let url = match Url::parse(&body.url) { Err(_) => { + return Err(Error::BadRequest(ErrorKind::Unknown, "Not a valid URL")); + } + Ok(u) if u.scheme() != "http" && u.scheme() != "https" || u.host().is_none() => { return Err(Error::BadRequest( - ErrorKind::Unknown, - "Not a valid URL", - )); - }, - Ok(u) - if u.scheme() != "http" - && u.scheme() != "https" - || u.host().is_none() - => { - return Err(Error::BadRequest( - ErrorKind::Unknown, + ErrorKind::Unknown, "Not a valid HTTP URL", )); - }, + } Ok(url) => url, }; @@ -320,13 +323,11 @@ pub async fn get_media_preview_route( Ok(preview) => { let res = serde_json::value::to_raw_value(&preview).expect("Converting to JSON failed"); Ok(get_media_preview::v3::Response::from_raw_value(res)) - }, - Err(_) => { - Err(Error::BadRequest( - ErrorKind::NotFound, - "Failed to find preview data", - )) - }, + } + Err(_) => Err(Error::BadRequest( + ErrorKind::NotFound, + "Failed to find preview data", + )), } } diff --git a/src/config/mod.rs b/src/config/mod.rs index 8cd2d422..20c9c241 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,11 +4,11 @@ use std::{ net::{IpAddr, Ipv4Addr}, }; -use wild_carded_domain::WildCardedDomain; use ruma::{OwnedServerName, RoomVersionId}; use serde::{de::IgnoredAny, Deserialize}; use tracing::warn; use url::Url; +use wild_carded_domain::WildCardedDomain; mod proxy; mod wild_carded_domain; diff --git a/src/config/proxy.rs b/src/config/proxy.rs index 05762e40..0b604647 100644 --- a/src/config/proxy.rs +++ b/src/config/proxy.rs @@ -1,8 +1,8 @@ use reqwest::{Proxy, Url}; use serde::Deserialize; -use crate::Result; use super::wild_carded_domain::WildCardedDomain; +use crate::Result; /// ## Examples: /// - No proxy (default): diff --git a/src/database/key_value/media.rs b/src/database/key_value/media.rs index 165eebb3..f229633d 100644 --- a/src/database/key_value/media.rs +++ b/src/database/key_value/media.rs @@ -1,6 +1,10 @@ use ruma::{api::client::error::ErrorKind, http_headers::ContentDisposition}; -use crate::{database::KeyValueDatabase, service::{self, media::UrlPreviewData}, utils, Error, Result}; +use crate::{ + database::KeyValueDatabase, + service::{self, media::UrlPreviewData}, + utils, Error, Result, +}; impl service::media::Data for KeyValueDatabase { fn create_file_metadata( @@ -73,13 +77,16 @@ impl service::media::Data for KeyValueDatabase { self.url_previews.remove(url.as_bytes()) } - fn set_url_preview(&self, url: &str, data: &UrlPreviewData, timestamp: std::time::Duration) -> Result<()> { + fn set_url_preview( + &self, + url: &str, + data: &UrlPreviewData, + timestamp: std::time::Duration, + ) -> Result<()> { let mut value = Vec::::new(); value.extend_from_slice(×tamp.as_secs().to_be_bytes()); value.push(0xff); - value.extend_from_slice( - data.title.as_bytes(), - ); + value.extend_from_slice(data.title.as_bytes()); value.push(0xff); value.extend_from_slice( data.description @@ -88,9 +95,7 @@ impl service::media::Data for KeyValueDatabase { .unwrap_or_default(), ); value.push(0xff); - value.extend_from_slice( - data.image.as_bytes(), - ); + value.extend_from_slice(data.image.as_bytes()); value.push(0xff); value.extend_from_slice(&data.image_size.unwrap_or(0).to_be_bytes()); value.push(0xff); diff --git a/src/service/globals/mod.rs b/src/service/globals/mod.rs index 88359afa..3048d076 100644 --- a/src/service/globals/mod.rs +++ b/src/service/globals/mod.rs @@ -7,8 +7,7 @@ use ruma::{ use crate::api::server_server::DestinationResponse; -use crate::config::UrlPreviewConfig; -use crate::{services, Config, Error, Result}; +use crate::{config::UrlPreviewConfig, services, Config, Error, Result}; use futures_util::FutureExt; use hickory_resolver::TokioAsyncResolver; use hyper_util::client::legacy::connect::dns::{GaiResolver, Name as HyperName}; diff --git a/src/service/media/data.rs b/src/service/media/data.rs index 1d0f87ab..97da0a26 100644 --- a/src/service/media/data.rs +++ b/src/service/media/data.rs @@ -20,10 +20,7 @@ pub trait Data: Send + Sync { height: u32, ) -> Result<(ContentDisposition, Option, Vec)>; - fn remove_url_preview( - &self, - url: &str - ) -> Result<()>; + fn remove_url_preview(&self, url: &str) -> Result<()>; fn set_url_preview( &self, @@ -32,8 +29,5 @@ pub trait Data: Send + Sync { timestamp: std::time::Duration, ) -> Result<()>; - fn get_url_preview( - &self, - url: &str - ) -> Option; + fn get_url_preview(&self, url: &str) -> Option; } diff --git a/src/service/media/mod.rs b/src/service/media/mod.rs index 23fb2236..33d64446 100644 --- a/src/service/media/mod.rs +++ b/src/service/media/mod.rs @@ -1,7 +1,7 @@ mod data; use std::{ - io::Cursor, collections::HashMap, + io::Cursor, sync::{Arc, RwLock}, time::SystemTime, }; @@ -12,12 +12,12 @@ use ruma::http_headers::{ContentDisposition, ContentDispositionType}; use crate::{services, Result}; use image::imageops::FilterType; +use serde::Serialize; use tokio::{ fs::File, io::{AsyncReadExt, AsyncWriteExt, BufReader}, sync::Mutex, }; -use serde::Serialize; pub struct FileMeta { pub content_disposition: ContentDisposition, @@ -27,18 +27,14 @@ pub struct FileMeta { #[derive(Serialize, Default)] pub struct UrlPreviewData { - #[serde( - rename(serialize = "og:title") - )] + #[serde(rename(serialize = "og:title"))] pub title: String, #[serde( skip_serializing_if = "Option::is_none", rename(serialize = "og:description") )] pub description: Option, - #[serde( - rename(serialize = "og:image") - )] + #[serde(rename(serialize = "og:image"))] pub image: String, #[serde( skip_serializing_if = "Option::is_none",