#![allow(deprecated)] use axum::extract::State; use axum_client_ip::InsecureClientIp; use conduwuit::{ Err, Result, err, utils::{content_disposition::make_content_disposition, math::ruma_from_usize}, }; use conduwuit_service::media::{CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, Dim, FileMeta}; use reqwest::Url; use ruma::{ Mxc, api::client::media::{ create_content, get_content, get_content_as_filename, get_content_thumbnail, get_media_config, get_media_preview, }, }; use crate::{Ruma, RumaResponse, client::create_content_route}; /// # `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.server.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", level = "debug")] 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; let url = Url::parse(&body.url).map_err(|e| { err!(Request(InvalidParam( debug_warn!(%sender_user, %url, "Requested URL is not valid: {e}") ))) })?; 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}") ))) })?; serde_json::value::to_raw_value(&preview) .map(get_media_preview::v3::Response::from_raw_value) .map_err(|error| { err!(Request(Unknown( debug_error!(%sender_user, %url, "Failed to parse URL preview: {error}") ))) }) } /// # `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", level = "debug")] 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, }; match services.media.get(&mxc).await? { | Some(FileMeta { content, content_type, content_disposition, }) => { 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()), }) }, | _ => 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", level = "debug")] 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", level = "debug")] 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, }; match services.media.get(&mxc).await? { | Some(FileMeta { content, content_type, content_disposition, }) => { 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()), }) }, | _ => 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", level = "debug")] 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())?; match services.media.get_thumbnail(&mxc, &dim).await? { | Some(FileMeta { content, content_type, content_disposition, }) => { 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), }) }, | _ => 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) }