implement legacy "v1" media request routes
if https://github.com/ruma/ruma/issues/1240 is to be trusted, there are apparently still servers and clients that may call these endpoints. i'm unable to read the history of that matrix.to link so i don't know the full context, but this is trivial to implement so.. Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
parent
496a9c7af8
commit
c58f93105a
3 changed files with 238 additions and 3 deletions
|
@ -78,4 +78,5 @@
|
||||||
- Startup check if conduwuit running in a container and is listening on 127.0.0.1
|
- Startup check if conduwuit running in a container and is listening on 127.0.0.1
|
||||||
- Make `CONDUIT_CONFIG` optional, relevant for container users that configure only by environment variables and no longer need to set `CONDUIT_CONFIG` to an empty string.
|
- Make `CONDUIT_CONFIG` optional, relevant for container users that configure only by environment variables and no longer need to set `CONDUIT_CONFIG` to an empty string.
|
||||||
- Config option to change Conduit's behaviour of homeserver key fetching (`query_trusted_key_servers_first`). This option sets whether conduwuit will query trusted notary key servers first before the individual homeserver(s), or vice versa.
|
- Config option to change Conduit's behaviour of homeserver key fetching (`query_trusted_key_servers_first`). This option sets whether conduwuit will query trusted notary key servers first before the individual homeserver(s), or vice versa.
|
||||||
- Implement database flush and cleanup Conduit operations when using RocksDB
|
- Implement database flush and cleanup Conduit operations when using RocksDB
|
||||||
|
- Implement legacy Matrix `/v1/` media endpoints that some clients and servers may still call
|
|
@ -14,7 +14,7 @@ use webpage::HTML;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
service::media::{FileMeta, UrlPreviewData},
|
service::media::{FileMeta, UrlPreviewData},
|
||||||
services, utils, Error, Result, Ruma,
|
services, utils, Error, Result, Ruma, RumaResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// generated MXC ID (`media-id`) length
|
/// generated MXC ID (`media-id`) length
|
||||||
|
@ -90,6 +90,44 @@ pub async fn create_content_route(body: Ruma<create_content::v3::Request>) -> Re
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `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 https://spec.matrix.org/legacy/legacy/#id27
|
||||||
|
///
|
||||||
|
/// - Some metadata will be saved in the database
|
||||||
|
/// - Media will be saved in the media/ directory
|
||||||
|
pub async fn create_content_v1_route(
|
||||||
|
body: Ruma<create_content::v3::Request>,
|
||||||
|
) -> Result<RumaResponse<create_content::v3::Response>> {
|
||||||
|
let mxc = format!(
|
||||||
|
"mxc://{}/{}",
|
||||||
|
services().globals.server_name(),
|
||||||
|
utils::random_string(MXC_LENGTH)
|
||||||
|
);
|
||||||
|
|
||||||
|
services()
|
||||||
|
.media
|
||||||
|
.create(
|
||||||
|
mxc.clone(),
|
||||||
|
body.filename.as_ref().map(|filename| "inline; filename=".to_owned() + filename).as_deref(),
|
||||||
|
body.content_type.as_deref(),
|
||||||
|
&body.file,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let content_uri = mxc.into();
|
||||||
|
|
||||||
|
Ok(create_content::v3::Response {
|
||||||
|
content_uri,
|
||||||
|
blurhash: None,
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
}
|
||||||
|
|
||||||
/// helper method to fetch remote media from other servers over federation
|
/// helper method to fetch remote media from other servers over federation
|
||||||
pub async fn get_remote_content(
|
pub async fn get_remote_content(
|
||||||
mxc: &str, server_name: &ruma::ServerName, media_id: String, allow_redirect: bool, timeout_ms: Duration,
|
mxc: &str, server_name: &ruma::ServerName, media_id: String, allow_redirect: bool, timeout_ms: Duration,
|
||||||
|
@ -169,6 +207,51 @@ pub async fn get_content_route(body: Ruma<get_content::v3::Request>) -> Result<g
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `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 https://spec.matrix.org/legacy/legacy/#id27
|
||||||
|
///
|
||||||
|
/// - 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 async fn get_content_v1_route(
|
||||||
|
body: Ruma<get_content::v3::Request>,
|
||||||
|
) -> Result<RumaResponse<get_content::v3::Response>> {
|
||||||
|
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||||
|
|
||||||
|
if let Some(FileMeta {
|
||||||
|
content_disposition,
|
||||||
|
content_type,
|
||||||
|
file,
|
||||||
|
}) = services().media.get(mxc.clone()).await?
|
||||||
|
{
|
||||||
|
Ok(get_content::v3::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition,
|
||||||
|
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
||||||
|
let remote_content_response = get_remote_content(
|
||||||
|
&mxc,
|
||||||
|
&body.server_name,
|
||||||
|
body.media_id.clone(),
|
||||||
|
body.allow_redirect,
|
||||||
|
body.timeout_ms,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(remote_content_response.into())
|
||||||
|
} else {
|
||||||
|
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}`
|
/// # `GET /_matrix/media/v3/download/{serverName}/{mediaId}/{fileName}`
|
||||||
///
|
///
|
||||||
/// Load media from our server or over federation, permitting desired filename.
|
/// Load media from our server or over federation, permitting desired filename.
|
||||||
|
@ -215,6 +298,58 @@ pub async fn get_content_as_filename_route(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `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 https://spec.matrix.org/legacy/legacy/#id27
|
||||||
|
///
|
||||||
|
/// - 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 async fn get_content_as_filename_v1_route(
|
||||||
|
body: Ruma<get_content_as_filename::v3::Request>,
|
||||||
|
) -> Result<RumaResponse<get_content_as_filename::v3::Response>> {
|
||||||
|
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||||
|
|
||||||
|
if let Some(FileMeta {
|
||||||
|
content_type,
|
||||||
|
file,
|
||||||
|
..
|
||||||
|
}) = services().media.get(mxc.clone()).await?
|
||||||
|
{
|
||||||
|
Ok(get_content_as_filename::v3::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
content_disposition: Some(format!("inline; filename={}", body.filename)),
|
||||||
|
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
||||||
|
let remote_content_response = get_remote_content(
|
||||||
|
&mxc,
|
||||||
|
&body.server_name,
|
||||||
|
body.media_id.clone(),
|
||||||
|
body.allow_redirect,
|
||||||
|
body.timeout_ms,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(get_content_as_filename::v3::Response {
|
||||||
|
content_disposition: Some(format!("inline: filename={}", body.filename)),
|
||||||
|
content_type: remote_content_response.content_type,
|
||||||
|
file: remote_content_response.file,
|
||||||
|
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
} else {
|
||||||
|
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}`
|
/// # `GET /_matrix/media/v3/thumbnail/{serverName}/{mediaId}`
|
||||||
///
|
///
|
||||||
/// Load media thumbnail from our server or over federation.
|
/// Load media thumbnail from our server or over federation.
|
||||||
|
@ -292,6 +427,88 @@ pub async fn get_content_thumbnail_route(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// # `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 https://spec.matrix.org/legacy/legacy/#id27
|
||||||
|
///
|
||||||
|
/// - 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 async fn get_content_thumbnail_v1_route(
|
||||||
|
body: Ruma<get_content_thumbnail::v3::Request>,
|
||||||
|
) -> Result<RumaResponse<get_content_thumbnail::v3::Response>> {
|
||||||
|
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id);
|
||||||
|
|
||||||
|
if let Some(FileMeta {
|
||||||
|
content_type,
|
||||||
|
file,
|
||||||
|
..
|
||||||
|
}) = services()
|
||||||
|
.media
|
||||||
|
.get_thumbnail(
|
||||||
|
mxc.clone(),
|
||||||
|
body.width.try_into().map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid."))?,
|
||||||
|
body.height.try_into().map_err(|_| Error::BadRequest(ErrorKind::InvalidParam, "Height is invalid."))?,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
Ok(get_content_thumbnail::v3::Response {
|
||||||
|
file,
|
||||||
|
content_type,
|
||||||
|
cross_origin_resource_policy: Some("cross-origin".to_owned()),
|
||||||
|
}
|
||||||
|
.into())
|
||||||
|
} else if &*body.server_name != services().globals.server_name() && body.allow_remote {
|
||||||
|
// we'll lie to the client and say the blocked server's media was not found and
|
||||||
|
// log. the client has no way of telling anyways so this is a security bonus.
|
||||||
|
if services().globals.prevent_media_downloads_from().contains(&body.server_name.clone()) {
|
||||||
|
info!(
|
||||||
|
"Received request for remote media `{}` but server is in our media server blocklist. Returning 404.",
|
||||||
|
mxc
|
||||||
|
);
|
||||||
|
return Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."));
|
||||||
|
}
|
||||||
|
|
||||||
|
let get_thumbnail_response = services()
|
||||||
|
.sending
|
||||||
|
.send_federation_request(
|
||||||
|
&body.server_name,
|
||||||
|
get_content_thumbnail::v3::Request {
|
||||||
|
allow_remote: body.allow_remote,
|
||||||
|
height: body.height,
|
||||||
|
width: body.width,
|
||||||
|
method: body.method.clone(),
|
||||||
|
server_name: body.server_name.clone(),
|
||||||
|
media_id: body.media_id.clone(),
|
||||||
|
timeout_ms: body.timeout_ms,
|
||||||
|
allow_redirect: body.allow_redirect,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
services()
|
||||||
|
.media
|
||||||
|
.upload_thumbnail(
|
||||||
|
mxc,
|
||||||
|
None,
|
||||||
|
get_thumbnail_response.content_type.as_deref(),
|
||||||
|
body.width.try_into().expect("all UInts are valid u32s"),
|
||||||
|
body.height.try_into().expect("all UInts are valid u32s"),
|
||||||
|
&get_thumbnail_response.file,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(get_thumbnail_response.into())
|
||||||
|
} else {
|
||||||
|
Err(Error::BadRequest(ErrorKind::NotFound, "Media not found."))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
|
async fn download_image(client: &reqwest::Client, url: &str) -> Result<UrlPreviewData> {
|
||||||
let image = client.get(url).send().await?.bytes().await?;
|
let image = client.get(url).send().await?.bytes().await?;
|
||||||
let mxc = format!(
|
let mxc = format!(
|
||||||
|
|
19
src/main.rs
19
src/main.rs
|
@ -6,7 +6,7 @@ use std::{
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{DefaultBodyLimit, FromRequestParts, MatchedPath},
|
extract::{DefaultBodyLimit, FromRequestParts, MatchedPath},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{get, on, MethodFilter},
|
routing::{get, on, post, MethodFilter},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
use axum_server::{bind, bind_rustls, tls_rustls::RustlsConfig, Handle as ServerHandle};
|
use axum_server::{bind, bind_rustls, tls_rustls::RustlsConfig, Handle as ServerHandle};
|
||||||
|
@ -620,6 +620,23 @@ fn routes() -> Router {
|
||||||
.ruma_route(client_server::get_media_config_route)
|
.ruma_route(client_server::get_media_config_route)
|
||||||
.ruma_route(client_server::get_media_preview_route)
|
.ruma_route(client_server::get_media_preview_route)
|
||||||
.ruma_route(client_server::create_content_route)
|
.ruma_route(client_server::create_content_route)
|
||||||
|
// legacy v1 media routes
|
||||||
|
.route(
|
||||||
|
"/_matrix/media/v1/upload",
|
||||||
|
post(client_server::create_content_v1_route)
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/_matrix/media/v1/download/:server_name/:media_id",
|
||||||
|
get(client_server::get_content_v1_route)
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/_matrix/media/v1/download/:server_name/:media_id/:file_name",
|
||||||
|
get(client_server::get_content_as_filename_v1_route)
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/_matrix/media/v1/thumbnail/:server_name/:media_id",
|
||||||
|
get(client_server::get_content_thumbnail_v1_route)
|
||||||
|
)
|
||||||
.ruma_route(client_server::get_content_route)
|
.ruma_route(client_server::get_content_route)
|
||||||
.ruma_route(client_server::get_content_as_filename_route)
|
.ruma_route(client_server::get_content_as_filename_route)
|
||||||
.ruma_route(client_server::get_content_thumbnail_route)
|
.ruma_route(client_server::get_content_thumbnail_route)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue