diff --git a/src/api/client/media.rs b/src/api/client/media.rs index 41a34d75..b8140ff7 100644 --- a/src/api/client/media.rs +++ b/src/api/client/media.rs @@ -1,102 +1,13 @@ -#![allow(deprecated)] - use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduit::{ - err, - utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize}, - Err, Result, + utils::{self, content_disposition::make_content_disposition}, + Result, }; -use ruma::{ - api::client::media::{ - create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config, - get_media_preview, - }, - Mxc, -}; -use service::media::{Dim, FileMeta, MXC_LENGTH}; +use conduit_service::media::MXC_LENGTH; +use ruma::{api::client::media::create_content, Mxc}; -use crate::{Ruma, RumaResponse}; - -/// Cache control for immutable objects -const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable"; - -const CORP_CROSS_ORIGIN: &str = "cross-origin"; - -/// # `GET /_matrix/media/v3/config` -/// -/// Returns max upload size. -pub(crate) async fn get_media_config_route( - State(services): State, _body: Ruma, -) -> Result { - Ok(get_media_config::v3::Response { - upload_size: ruma_from_usize(services.globals.config.max_request_size), - }) -} - -/// # `GET /_matrix/media/v1/config` -/// -/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or -/// clients may call. conduwuit adds these for compatibility purposes. -/// See -/// -/// Returns max upload size. -pub(crate) async fn get_media_config_v1_route( - State(services): State, body: Ruma, -) -> Result> { - get_media_config_route(State(services), body) - .await - .map(RumaResponse) -} - -/// # `GET /_matrix/media/v3/preview_url` -/// -/// Returns URL preview. -#[tracing::instrument(skip_all, fields(%client), name = "url_preview")] -pub(crate) async fn get_media_preview_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result { - let sender_user = body.sender_user.as_ref().expect("user is authenticated"); - - let url = &body.url; - if !services.media.url_preview_allowed(url) { - return Err!(Request(Forbidden( - debug_warn!(%sender_user, %url, "URL is not allowed to be previewed") - ))); - } - - let preview = services.media.get_url_preview(url).await.map_err(|e| { - err!(Request(Unknown( - debug_error!(%sender_user, %url, "Failed to fetch a URL preview: {e}") - ))) - })?; - - let res = serde_json::value::to_raw_value(&preview).map_err(|e| { - err!(Request(Unknown( - debug_error!(%sender_user, %url, "Failed to parse a URL preview: {e}") - ))) - })?; - - Ok(get_media_preview::v3::Response::from_raw_value(res)) -} - -/// # `GET /_matrix/media/v1/preview_url` -/// -/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or -/// clients may call. conduwuit adds these for compatibility purposes. -/// See -/// -/// Returns URL preview. -#[tracing::instrument(skip_all, fields(%client), name = "url_preview")] -pub(crate) async fn get_media_preview_v1_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result> { - get_media_preview_route(State(services), InsecureClientIp(client), body) - .await - .map(RumaResponse) -} +use crate::Ruma; /// # `POST /_matrix/media/v3/upload` /// @@ -132,256 +43,3 @@ pub(crate) async fn create_content_route( blurhash: None, }) } - -/// # `POST /_matrix/media/v1/upload` -/// -/// Permanently save media in the server. -/// -/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or -/// clients may call. conduwuit adds these for compatibility purposes. -/// See -/// -/// - Some metadata will be saved in the database -/// - Media will be saved in the media/ directory -#[tracing::instrument(skip_all, fields(%client), name = "media_upload")] -pub(crate) async fn create_content_v1_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result> { - create_content_route(State(services), InsecureClientIp(client), body) - .await - .map(RumaResponse) -} - -/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}` -/// -/// Load media from our server or over federation. -/// -/// - Only allows federation if `allow_remote` is true -/// - Only redirects if `allow_redirect` is true -/// - Uses client-provided `timeout_ms` if available, else defaults to 20 -/// seconds -#[tracing::instrument(skip_all, fields(%client), name = "media_get")] -pub(crate) async fn get_content_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result { - let mxc = Mxc { - server_name: &body.server_name, - media_id: &body.media_id, - }; - - if let Some(FileMeta { - content, - content_type, - content_disposition, - }) = services.media.get(&mxc).await? - { - let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None); - - Ok(get_content::v3::Response { - file: content.expect("entire file contents"), - content_type: content_type.map(Into::into), - content_disposition: Some(content_disposition), - cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), - cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), - }) - } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote { - let response = services - .media - .fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms) - .await - .map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?; - - let content_disposition = - make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None); - - Ok(get_content::v3::Response { - file: response.file, - content_type: response.content_type, - content_disposition: Some(content_disposition), - cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), - cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), - }) - } else { - Err!(Request(NotFound("Media not found."))) - } -} - -/// # `GET /_matrix/media/v1/download/{serverName}/{mediaId}` -/// -/// Load media from our server or over federation. -/// -/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or -/// clients may call. conduwuit adds these for compatibility purposes. -/// See -/// -/// - Only allows federation if `allow_remote` is true -/// - Only redirects if `allow_redirect` is true -/// - Uses client-provided `timeout_ms` if available, else defaults to 20 -/// seconds -#[tracing::instrument(skip_all, fields(%client), name = "media_get")] -pub(crate) async fn get_content_v1_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result> { - get_content_route(State(services), InsecureClientIp(client), body) - .await - .map(RumaResponse) -} - -/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}` -/// -/// Load media from our server or over federation, permitting desired filename. -/// -/// - Only allows federation if `allow_remote` is true -/// - Only redirects if `allow_redirect` is true -/// - Uses client-provided `timeout_ms` if available, else defaults to 20 -/// seconds -#[tracing::instrument(skip_all, fields(%client), name = "media_get")] -pub(crate) async fn get_content_as_filename_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result { - let mxc = Mxc { - server_name: &body.server_name, - media_id: &body.media_id, - }; - - if let Some(FileMeta { - content, - content_type, - content_disposition, - }) = services.media.get(&mxc).await? - { - let content_disposition = - make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), Some(&body.filename)); - - Ok(get_content_as_filename::v3::Response { - file: content.expect("entire file contents"), - content_type: content_type.map(Into::into), - content_disposition: Some(content_disposition), - cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), - cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), - }) - } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote { - let response = services - .media - .fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms) - .await - .map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?; - - let content_disposition = - make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None); - - Ok(get_content_as_filename::v3::Response { - content_disposition: Some(content_disposition), - content_type: response.content_type, - file: response.file, - cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), - cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), - }) - } else { - Err!(Request(NotFound("Media not found."))) - } -} - -/// # `GET /_matrix/media/v1/download/{serverName}/{mediaId}/{fileName}` -/// -/// Load media from our server or over federation, permitting desired filename. -/// -/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or -/// clients may call. conduwuit adds these for compatibility purposes. -/// See -/// -/// - Only allows federation if `allow_remote` is true -/// - Only redirects if `allow_redirect` is true -/// - Uses client-provided `timeout_ms` if available, else defaults to 20 -/// seconds -#[tracing::instrument(skip_all, fields(%client), name = "media_get")] -pub(crate) async fn get_content_as_filename_v1_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result> { - get_content_as_filename_route(State(services), InsecureClientIp(client), body) - .await - .map(RumaResponse) -} - -/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}` -/// -/// Load media thumbnail from our server or over federation. -/// -/// - Only allows federation if `allow_remote` is true -/// - Only redirects if `allow_redirect` is true -/// - Uses client-provided `timeout_ms` if available, else defaults to 20 -/// seconds -#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")] -pub(crate) async fn get_content_thumbnail_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result { - let mxc = Mxc { - server_name: &body.server_name, - media_id: &body.media_id, - }; - - let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?; - if let Some(FileMeta { - content, - content_type, - content_disposition, - }) = services.media.get_thumbnail(&mxc, &dim).await? - { - let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None); - - Ok(get_content_thumbnail::v3::Response { - file: content.expect("entire file contents"), - content_type: content_type.map(Into::into), - cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), - cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), - content_disposition: Some(content_disposition), - }) - } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote { - let response = services - .media - .fetch_remote_thumbnail_legacy(&body) - .await - .map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?; - - let content_disposition = - make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None); - - Ok(get_content_thumbnail::v3::Response { - file: response.file, - content_type: response.content_type, - cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), - cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), - content_disposition: Some(content_disposition), - }) - } else { - Err!(Request(NotFound("Media not found."))) - } -} - -/// # `GET /_matrix/media/v1/thumbnail/{serverName}/{mediaId}` -/// -/// Load media thumbnail from our server or over federation. -/// -/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or -/// clients may call. conduwuit adds these for compatibility purposes. -/// See -/// -/// - Only allows federation if `allow_remote` is true -/// - Only redirects if `allow_redirect` is true -/// - Uses client-provided `timeout_ms` if available, else defaults to 20 -/// seconds -#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get")] -pub(crate) async fn get_content_thumbnail_v1_route( - State(services): State, InsecureClientIp(client): InsecureClientIp, - body: Ruma, -) -> Result> { - get_content_thumbnail_route(State(services), InsecureClientIp(client), body) - .await - .map(RumaResponse) -} diff --git a/src/api/client/media_legacy.rs b/src/api/client/media_legacy.rs new file mode 100644 index 00000000..e87b9a2b --- /dev/null +++ b/src/api/client/media_legacy.rs @@ -0,0 +1,343 @@ +#![allow(deprecated)] + +use axum::extract::State; +use axum_client_ip::InsecureClientIp; +use conduit::{ + err, + utils::{content_disposition::make_content_disposition, math::ruma_from_usize}, + Err, Result, +}; +use conduit_service::media::{Dim, FileMeta, CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN}; +use ruma::{ + api::client::media::{ + create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config, + get_media_preview, + }, + Mxc, +}; + +use crate::{client::create_content_route, Ruma, RumaResponse}; + +/// # `GET /_matrix/media/v3/config` +/// +/// Returns max upload size. +pub(crate) async fn get_media_config_legacy_route( + State(services): State, _body: Ruma, +) -> Result { + Ok(get_media_config::v3::Response { + upload_size: ruma_from_usize(services.globals.config.max_request_size), + }) +} + +/// # `GET /_matrix/media/v1/config` +/// +/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or +/// clients may call. conduwuit adds these for compatibility purposes. +/// See +/// +/// Returns max upload size. +pub(crate) async fn get_media_config_legacy_legacy_route( + State(services): State, body: Ruma, +) -> Result> { + get_media_config_legacy_route(State(services), body) + .await + .map(RumaResponse) +} + +/// # `GET /_matrix/media/v3/preview_url` +/// +/// Returns URL preview. +#[tracing::instrument(skip_all, fields(%client), name = "url_preview_legacy")] +pub(crate) async fn get_media_preview_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result { + let sender_user = body.sender_user.as_ref().expect("user is authenticated"); + + let url = &body.url; + if !services.media.url_preview_allowed(url) { + return Err!(Request(Forbidden( + debug_warn!(%sender_user, %url, "URL is not allowed to be previewed") + ))); + } + + let preview = services.media.get_url_preview(url).await.map_err(|e| { + err!(Request(Unknown( + debug_error!(%sender_user, %url, "Failed to fetch a URL preview: {e}") + ))) + })?; + + let res = serde_json::value::to_raw_value(&preview).map_err(|e| { + err!(Request(Unknown( + debug_error!(%sender_user, %url, "Failed to parse a URL preview: {e}") + ))) + })?; + + Ok(get_media_preview::v3::Response::from_raw_value(res)) +} + +/// # `GET /_matrix/media/v1/preview_url` +/// +/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or +/// clients may call. conduwuit adds these for compatibility purposes. +/// See +/// +/// Returns URL preview. +pub(crate) async fn get_media_preview_legacy_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result> { + get_media_preview_legacy_route(State(services), InsecureClientIp(client), body) + .await + .map(RumaResponse) +} + +/// # `POST /_matrix/media/v1/upload` +/// +/// Permanently save media in the server. +/// +/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or +/// clients may call. conduwuit adds these for compatibility purposes. +/// See +/// +/// - Some metadata will be saved in the database +/// - Media will be saved in the media/ directory +pub(crate) async fn create_content_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result> { + create_content_route(State(services), InsecureClientIp(client), body) + .await + .map(RumaResponse) +} + +/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}` +/// +/// Load media from our server or over federation. +/// +/// - Only allows federation if `allow_remote` is true +/// - Only redirects if `allow_redirect` is true +/// - Uses client-provided `timeout_ms` if available, else defaults to 20 +/// seconds +#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")] +pub(crate) async fn get_content_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result { + let mxc = Mxc { + server_name: &body.server_name, + media_id: &body.media_id, + }; + + if let Some(FileMeta { + content, + content_type, + content_disposition, + }) = services.media.get(&mxc).await? + { + let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None); + + Ok(get_content::v3::Response { + file: content.expect("entire file contents"), + content_type: content_type.map(Into::into), + content_disposition: Some(content_disposition), + cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), + cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), + }) + } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote { + let response = services + .media + .fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms) + .await + .map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?; + + let content_disposition = + make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None); + + Ok(get_content::v3::Response { + file: response.file, + content_type: response.content_type, + content_disposition: Some(content_disposition), + cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), + cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), + }) + } else { + Err!(Request(NotFound("Media not found."))) + } +} + +/// # `GET /_matrix/media/v1/download/{serverName}/{mediaId}` +/// +/// Load media from our server or over federation. +/// +/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or +/// clients may call. conduwuit adds these for compatibility purposes. +/// See +/// +/// - Only allows federation if `allow_remote` is true +/// - Only redirects if `allow_redirect` is true +/// - Uses client-provided `timeout_ms` if available, else defaults to 20 +/// seconds +#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")] +pub(crate) async fn get_content_legacy_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result> { + get_content_legacy_route(State(services), InsecureClientIp(client), body) + .await + .map(RumaResponse) +} + +/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}` +/// +/// Load media from our server or over federation, permitting desired filename. +/// +/// - Only allows federation if `allow_remote` is true +/// - Only redirects if `allow_redirect` is true +/// - Uses client-provided `timeout_ms` if available, else defaults to 20 +/// seconds +#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy")] +pub(crate) async fn get_content_as_filename_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result { + let mxc = Mxc { + server_name: &body.server_name, + media_id: &body.media_id, + }; + + if let Some(FileMeta { + content, + content_type, + content_disposition, + }) = services.media.get(&mxc).await? + { + let content_disposition = + make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), Some(&body.filename)); + + Ok(get_content_as_filename::v3::Response { + file: content.expect("entire file contents"), + content_type: content_type.map(Into::into), + content_disposition: Some(content_disposition), + cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), + cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), + }) + } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote { + let response = services + .media + .fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms) + .await + .map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?; + + let content_disposition = + make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None); + + Ok(get_content_as_filename::v3::Response { + content_disposition: Some(content_disposition), + content_type: response.content_type, + file: response.file, + cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), + cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), + }) + } else { + Err!(Request(NotFound("Media not found."))) + } +} + +/// # `GET /_matrix/media/v1/download/{serverName}/{mediaId}/{fileName}` +/// +/// Load media from our server or over federation, permitting desired filename. +/// +/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or +/// clients may call. conduwuit adds these for compatibility purposes. +/// See +/// +/// - Only allows federation if `allow_remote` is true +/// - Only redirects if `allow_redirect` is true +/// - Uses client-provided `timeout_ms` if available, else defaults to 20 +/// seconds +pub(crate) async fn get_content_as_filename_legacy_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result> { + get_content_as_filename_legacy_route(State(services), InsecureClientIp(client), body) + .await + .map(RumaResponse) +} + +/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}` +/// +/// Load media thumbnail from our server or over federation. +/// +/// - Only allows federation if `allow_remote` is true +/// - Only redirects if `allow_redirect` is true +/// - Uses client-provided `timeout_ms` if available, else defaults to 20 +/// seconds +#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get_legacy")] +pub(crate) async fn get_content_thumbnail_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result { + let mxc = Mxc { + server_name: &body.server_name, + media_id: &body.media_id, + }; + + let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?; + if let Some(FileMeta { + content, + content_type, + content_disposition, + }) = services.media.get_thumbnail(&mxc, &dim).await? + { + let content_disposition = make_content_disposition(content_disposition.as_ref(), content_type.as_deref(), None); + + Ok(get_content_thumbnail::v3::Response { + file: content.expect("entire file contents"), + content_type: content_type.map(Into::into), + cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), + cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), + content_disposition: Some(content_disposition), + }) + } else if !services.globals.server_is_ours(&body.server_name) && body.allow_remote { + let response = services + .media + .fetch_remote_thumbnail_legacy(&body) + .await + .map_err(|e| err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}")))))?; + + let content_disposition = + make_content_disposition(response.content_disposition.as_ref(), response.content_type.as_deref(), None); + + Ok(get_content_thumbnail::v3::Response { + file: response.file, + content_type: response.content_type, + cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()), + cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()), + content_disposition: Some(content_disposition), + }) + } else { + Err!(Request(NotFound("Media not found."))) + } +} + +/// # `GET /_matrix/media/v1/thumbnail/{serverName}/{mediaId}` +/// +/// Load media thumbnail from our server or over federation. +/// +/// This is a legacy endpoint ("/v1/") that some very old homeservers and/or +/// clients may call. conduwuit adds these for compatibility purposes. +/// See +/// +/// - Only allows federation if `allow_remote` is true +/// - Only redirects if `allow_redirect` is true +/// - Uses client-provided `timeout_ms` if available, else defaults to 20 +/// seconds +pub(crate) async fn get_content_thumbnail_legacy_legacy_route( + State(services): State, InsecureClientIp(client): InsecureClientIp, + body: Ruma, +) -> Result> { + get_content_thumbnail_legacy_route(State(services), InsecureClientIp(client), body) + .await + .map(RumaResponse) +} diff --git a/src/api/client/mod.rs b/src/api/client/mod.rs index cf13cf7e..03c87e5d 100644 --- a/src/api/client/mod.rs +++ b/src/api/client/mod.rs @@ -9,6 +9,7 @@ pub(super) mod directory; pub(super) mod filter; pub(super) mod keys; pub(super) mod media; +pub(super) mod media_legacy; pub(super) mod membership; pub(super) mod message; pub(super) mod openid; @@ -46,6 +47,7 @@ pub(super) use directory::*; pub(super) use filter::*; pub(super) use keys::*; pub(super) use media::*; +pub(super) use media_legacy::*; pub(super) use membership::*; pub use membership::{join_room_by_id_helper, leave_all_rooms, leave_room, validate_and_add_event_id}; pub(super) use message::*; diff --git a/src/api/router.rs b/src/api/router.rs index a76cac32..05dac3ad 100644 --- a/src/api/router.rs +++ b/src/api/router.rs @@ -19,7 +19,7 @@ use crate::{client, server}; pub fn build(router: Router, server: &Server) -> Router { let config = &server.config; - let router = router + let mut router = router .ruma_route(client::get_supported_versions_route) .ruma_route(client::get_register_available_route) .ruma_route(client::register_route) @@ -138,37 +138,7 @@ pub fn build(router: Router, server: &Server) -> Router { .ruma_route(client::search_events_route) .ruma_route(client::turn_server_route) .ruma_route(client::send_event_to_device_route) - .ruma_route(client::get_media_config_route) - .ruma_route(client::get_media_preview_route) .ruma_route(client::create_content_route) - // legacy v1 media routes - .route( - "/_matrix/media/v1/preview_url", - get(client::get_media_preview_v1_route) - ) - .route( - "/_matrix/media/v1/config", - get(client::get_media_config_v1_route) - ) - .route( - "/_matrix/media/v1/upload", - post(client::create_content_v1_route) - ) - .route( - "/_matrix/media/v1/download/:server_name/:media_id", - get(client::get_content_v1_route) - ) - .route( - "/_matrix/media/v1/download/:server_name/:media_id/:file_name", - get(client::get_content_as_filename_v1_route) - ) - .route( - "/_matrix/media/v1/thumbnail/:server_name/:media_id", - get(client::get_content_thumbnail_v1_route) - ) - .ruma_route(client::get_content_route) - .ruma_route(client::get_content_as_filename_route) - .ruma_route(client::get_content_thumbnail_route) .ruma_route(client::get_devices_route) .ruma_route(client::get_device_route) .ruma_route(client::update_device_route) @@ -202,7 +172,7 @@ pub fn build(router: Router, server: &Server) -> Router { .route("/client/server.json", get(client::syncv3_client_server_json)); if config.allow_federation { - router + router = router .ruma_route(server::get_server_version_route) .route("/_matrix/key/v2/server", get(server::get_server_keys_route)) .route("/_matrix/key/v2/server/:key_id", get(server::get_server_keys_deprecated_route)) @@ -232,14 +202,45 @@ pub fn build(router: Router, server: &Server) -> Router { .ruma_route(server::well_known_server) .ruma_route(server::get_content_route) .ruma_route(server::get_content_thumbnail_route) - .route("/_conduwuit/local_user_count", get(client::conduwuit_local_user_count)) + .route("/_conduwuit/local_user_count", get(client::conduwuit_local_user_count)); } else { - router + router = router .route("/_matrix/federation/*path", any(federation_disabled)) .route("/.well-known/matrix/server", any(federation_disabled)) .route("/_matrix/key/*path", any(federation_disabled)) - .route("/_conduwuit/local_user_count", any(federation_disabled)) + .route("/_conduwuit/local_user_count", any(federation_disabled)); } + + if config.allow_legacy_media { + router = router + .ruma_route(client::get_media_config_legacy_route) + .ruma_route(client::get_media_preview_legacy_route) + .ruma_route(client::get_content_legacy_route) + .ruma_route(client::get_content_as_filename_legacy_route) + .ruma_route(client::get_content_thumbnail_legacy_route) + .route("/_matrix/media/v1/config", get(client::get_media_config_legacy_legacy_route)) + .route("/_matrix/media/v1/upload", post(client::create_content_legacy_route)) + .route( + "/_matrix/media/v1/preview_url", + get(client::get_media_preview_legacy_legacy_route), + ) + .route( + "/_matrix/media/v1/download/:server_name/:media_id", + get(client::get_content_legacy_legacy_route), + ) + .route( + "/_matrix/media/v1/download/:server_name/:media_id/:file_name", + get(client::get_content_as_filename_legacy_legacy_route), + ) + .route( + "/_matrix/media/v1/thumbnail/:server_name/:media_id", + get(client::get_content_thumbnail_legacy_legacy_route), + ); + } else { + router = router.route("/_matrix/media/*path", any(legacy_media_disabled)); + } + + router } async fn initial_sync(_uri: Uri) -> impl IntoResponse { @@ -247,3 +248,7 @@ async fn initial_sync(_uri: Uri) -> impl IntoResponse { } async fn federation_disabled() -> impl IntoResponse { err!(Config("allow_federation", "Federation is disabled.")) } + +async fn legacy_media_disabled() -> impl IntoResponse { + err!(Config("allow_legacy_media", "Unauthenticated media is disabled.")) +} diff --git a/src/core/config/mod.rs b/src/core/config/mod.rs index 8836c2c9..a9469e5b 100644 --- a/src/core/config/mod.rs +++ b/src/core/config/mod.rs @@ -289,6 +289,8 @@ pub struct Config { #[serde(default)] pub allow_guests_auto_join_rooms: bool, + #[serde(default = "true_fn")] + pub allow_legacy_media: bool, #[serde(default = "true_fn")] pub media_startup_check: bool, #[serde(default)] @@ -745,6 +747,7 @@ impl fmt::Display for Config { line("Media integrity checks on startup", &self.media_startup_check.to_string()); line("Media compatibility filesystem links", &self.media_compat_file_link.to_string()); line("Prune missing media from database", &self.prune_missing_media.to_string()); + line("Allow legacy (unauthenticated) media", &self.allow_legacy_media.to_string()); line("Prevent Media Downloads From", { let mut lst = vec![]; for domain in &self.prevent_media_downloads_from { diff --git a/src/service/media/mod.rs b/src/service/media/mod.rs index 04436f7c..2faa13d8 100644 --- a/src/service/media/mod.rs +++ b/src/service/media/mod.rs @@ -47,6 +47,12 @@ struct Services { /// generated MXC ID (`media-id`) length pub const MXC_LENGTH: usize = 32; +/// Cache control for immutable objects. +pub const CACHE_CONTROL_IMMUTABLE: &str = "public,max-age=31536000,immutable"; + +/// Default cross-origin resource policy. +pub const CORP_CROSS_ORIGIN: &str = "cross-origin"; + #[async_trait] impl crate::Service for Service { fn build(args: crate::Args<'_>) -> Result> {