Add MxcData helper

This commit is contained in:
Lambda 2024-07-21 17:56:49 +00:00
parent 64b3c357dd
commit 94204415ee
2 changed files with 78 additions and 28 deletions

View file

@ -15,7 +15,12 @@ use ruma::{
}; };
use tracing::error; use tracing::error;
use crate::{service::media::FileMeta, services, utils, Ar, Error, Ra, Result}; use crate::{
service::media::FileMeta,
services,
utils::{self, MxcData},
Ar, Error, Ra, Result,
};
const MXC_LENGTH: usize = 32; const MXC_LENGTH: usize = 32;
@ -133,16 +138,13 @@ pub(crate) async fn get_media_config_route(
pub(crate) async fn create_content_route( pub(crate) async fn create_content_route(
body: Ar<create_content::v3::Request>, body: Ar<create_content::v3::Request>,
) -> Result<Ra<create_content::v3::Response>> { ) -> Result<Ra<create_content::v3::Response>> {
let mxc = format!( let media_id = utils::random_string(MXC_LENGTH);
"mxc://{}/{}", let mxc = MxcData::new(services().globals.server_name(), &media_id)?;
services().globals.server_name(),
utils::random_string(MXC_LENGTH)
);
services() services()
.media .media
.create( .create(
mxc.clone(), mxc.to_string(),
body.filename body.filename
.clone() .clone()
.map(|filename| ContentDisposition { .map(|filename| ContentDisposition {
@ -163,18 +165,16 @@ pub(crate) async fn create_content_route(
#[allow(deprecated)] // unauthenticated media #[allow(deprecated)] // unauthenticated media
pub(crate) async fn get_remote_content( pub(crate) async fn get_remote_content(
mxc: &str, mxc: &MxcData<'_>,
server_name: &ruma::ServerName,
media_id: String,
) -> Result<legacy_media::get_content::v3::Response, Error> { ) -> Result<legacy_media::get_content::v3::Response, Error> {
let content_response = services() let content_response = services()
.sending .sending
.send_federation_request( .send_federation_request(
server_name, mxc.server_name,
legacy_media::get_content::v3::Request { legacy_media::get_content::v3::Request {
allow_remote: false, allow_remote: false,
server_name: server_name.to_owned(), server_name: mxc.server_name.to_owned(),
media_id, media_id: mxc.media_id.to_owned(),
timeout_ms: Duration::from_secs(20), timeout_ms: Duration::from_secs(20),
allow_redirect: false, allow_redirect: false,
}, },
@ -184,7 +184,7 @@ pub(crate) async fn get_remote_content(
services() services()
.media .media
.create( .create(
mxc.to_owned(), mxc.to_string(),
content_response.content_disposition.as_ref(), content_response.content_disposition.as_ref(),
content_response.content_type.as_deref(), content_response.content_type.as_deref(),
&content_response.file, &content_response.file,
@ -225,13 +225,13 @@ pub(crate) async fn get_content_route(
async fn get_content_route_ruma( async fn get_content_route_ruma(
body: Ar<legacy_media::get_content::v3::Request>, body: Ar<legacy_media::get_content::v3::Request>,
) -> Result<legacy_media::get_content::v3::Response> { ) -> Result<legacy_media::get_content::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); let mxc = MxcData::new(&body.server_name, &body.media_id)?;
if let Some(FileMeta { if let Some(FileMeta {
content_type, content_type,
file, file,
.. ..
}) = services().media.get(mxc.clone()).await? }) = services().media.get(mxc.to_string()).await?
{ {
Ok(legacy_media::get_content::v3::Response { Ok(legacy_media::get_content::v3::Response {
file, file,
@ -245,9 +245,7 @@ async fn get_content_route_ruma(
} else if &*body.server_name != services().globals.server_name() } else if &*body.server_name != services().globals.server_name()
&& body.allow_remote && body.allow_remote
{ {
let remote_content_response = let remote_content_response = get_remote_content(&mxc).await?;
get_remote_content(&mxc, &body.server_name, body.media_id.clone())
.await?;
Ok(legacy_media::get_content::v3::Response { Ok(legacy_media::get_content::v3::Response {
file: remote_content_response.file, file: remote_content_response.file,
content_disposition: Some(content_disposition_for( content_disposition: Some(content_disposition_for(
@ -288,13 +286,13 @@ pub(crate) async fn get_content_as_filename_route(
pub(crate) async fn get_content_as_filename_route_ruma( pub(crate) async fn get_content_as_filename_route_ruma(
body: Ar<legacy_media::get_content_as_filename::v3::Request>, body: Ar<legacy_media::get_content_as_filename::v3::Request>,
) -> Result<legacy_media::get_content_as_filename::v3::Response> { ) -> Result<legacy_media::get_content_as_filename::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); let mxc = MxcData::new(&body.server_name, &body.media_id)?;
if let Some(FileMeta { if let Some(FileMeta {
content_type, content_type,
file, file,
.. ..
}) = services().media.get(mxc.clone()).await? }) = services().media.get(mxc.to_string()).await?
{ {
Ok(legacy_media::get_content_as_filename::v3::Response { Ok(legacy_media::get_content_as_filename::v3::Response {
file, file,
@ -308,9 +306,7 @@ pub(crate) async fn get_content_as_filename_route_ruma(
} else if &*body.server_name != services().globals.server_name() } else if &*body.server_name != services().globals.server_name()
&& body.allow_remote && body.allow_remote
{ {
let remote_content_response = let remote_content_response = get_remote_content(&mxc).await?;
get_remote_content(&mxc, &body.server_name, body.media_id.clone())
.await?;
Ok(legacy_media::get_content_as_filename::v3::Response { Ok(legacy_media::get_content_as_filename::v3::Response {
content_disposition: Some(content_disposition_for( content_disposition: Some(content_disposition_for(
@ -366,7 +362,7 @@ pub(crate) async fn get_content_thumbnail_route(
async fn get_content_thumbnail_route_ruma( async fn get_content_thumbnail_route_ruma(
body: Ar<legacy_media::get_content_thumbnail::v3::Request>, body: Ar<legacy_media::get_content_thumbnail::v3::Request>,
) -> Result<legacy_media::get_content_thumbnail::v3::Response> { ) -> Result<legacy_media::get_content_thumbnail::v3::Response> {
let mxc = format!("mxc://{}/{}", body.server_name, body.media_id); let mxc = MxcData::new(&body.server_name, &body.media_id)?;
if let Some(FileMeta { if let Some(FileMeta {
content_type, content_type,
@ -375,7 +371,7 @@ async fn get_content_thumbnail_route_ruma(
}) = services() }) = services()
.media .media
.get_thumbnail( .get_thumbnail(
mxc.clone(), mxc.to_string(),
body.width.try_into().map_err(|_| { body.width.try_into().map_err(|_| {
Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid.") Error::BadRequest(ErrorKind::InvalidParam, "Width is invalid.")
})?, })?,
@ -414,7 +410,7 @@ async fn get_content_thumbnail_route_ruma(
services() services()
.media .media
.upload_thumbnail( .upload_thumbnail(
mxc, mxc.to_string(),
None, None,
get_thumbnail_response.content_type.as_deref(), get_thumbnail_response.content_type.as_deref(),
body.width.try_into().expect("all UInts are valid u32s"), body.width.try_into().expect("all UInts are valid u32s"),

View file

@ -13,9 +13,12 @@ use cmp::Ordering;
use rand::{prelude::*, rngs::OsRng}; use rand::{prelude::*, rngs::OsRng};
use ring::digest; use ring::digest;
use ruma::{ use ruma::{
canonical_json::try_from_json_map, CanonicalJsonError, CanonicalJsonObject, api::client::error::ErrorKind, canonical_json::try_from_json_map,
CanonicalJsonError, CanonicalJsonObject, MxcUri, MxcUriError, OwnedMxcUri,
}; };
use crate::{Error, Result};
// Hopefully we have a better chat protocol in 530 years // Hopefully we have a better chat protocol in 530 years
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)] #[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
pub(crate) fn millis_since_unix_epoch() -> u64 { pub(crate) fn millis_since_unix_epoch() -> u64 {
@ -243,6 +246,57 @@ pub(crate) fn dbg_truncate_str(s: &str, mut max_len: usize) -> Cow<'_, str> {
} }
} }
/// Data that makes up an `mxc://` URL.
#[derive(Debug, Clone)]
pub(crate) struct MxcData<'a> {
pub(crate) server_name: &'a ruma::ServerName,
pub(crate) media_id: &'a str,
}
impl<'a> MxcData<'a> {
pub(crate) fn new(
server_name: &'a ruma::ServerName,
media_id: &'a str,
) -> Result<Self> {
if !media_id.bytes().all(|b| {
matches!(b,
b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' | b'-' | b'_'
)
}) {
return Err(Error::BadRequest(
ErrorKind::InvalidParam,
"Invalid MXC media id",
));
}
Ok(Self {
server_name,
media_id,
})
}
}
impl fmt::Display for MxcData<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "mxc://{}/{}", self.server_name, self.media_id)
}
}
impl From<MxcData<'_>> for OwnedMxcUri {
fn from(value: MxcData<'_>) -> Self {
value.to_string().into()
}
}
impl<'a> TryFrom<&'a MxcUri> for MxcData<'a> {
type Error = MxcUriError;
fn try_from(value: &'a MxcUri) -> Result<Self, Self::Error> {
Ok(Self::new(value.server_name()?, value.media_id()?)
.expect("validated MxcUri should always be valid MxcData"))
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::utils::dbg_truncate_str; use crate::utils::dbg_truncate_str;