Skip to main content

tuwunel_api/client/admin/media/
query_media.rs

1use axum::extract::State;
2use ruma::{Mxc, ServerName, UInt};
3use synapse_admin_api::media::query_media::v1::{MediaInfo, Request, Response};
4use tuwunel_core::{Result, err};
5use tuwunel_service::media::UserMediaEntry;
6
7use crate::{Ruma, client::admin::require_admin};
8
9/// # `GET /_synapse/admin/v1/media/{server_name}/{media_id}`
10pub(crate) async fn admin_query_media_route(
11	State(services): State<crate::State>,
12	body: Ruma<Request>,
13) -> Result<Response> {
14	require_admin(&services, body.sender_user()).await?;
15
16	let mxc = Mxc {
17		server_name: &body.server_name,
18		media_id: &body.media_id,
19	};
20
21	let entry = services
22		.media
23		.media_entry(&mxc)
24		.await
25		.ok_or_else(|| err!(Request(NotFound("Unknown media"))))?;
26
27	let local = services.globals.server_is_ours(&body.server_name);
28
29	Ok(Response {
30		media_info: into_media_info(local, &body.server_name, entry),
31	})
32}
33
34fn into_media_info(local: bool, server_name: &ServerName, entry: UserMediaEntry) -> MediaInfo {
35	MediaInfo {
36		media_origin: (!local).then(|| server_name.to_owned()),
37		user_id: entry.user_id.filter(|_| local),
38		media_id: entry
39			.mxc
40			.media_id()
41			.unwrap_or_default()
42			.to_owned(),
43		media_type: entry.media_type.unwrap_or_default(),
44		media_length: entry
45			.media_length
46			.and_then(|len| UInt::try_from(len).ok()),
47		upload_name: entry.upload_name,
48		created_ts: UInt::try_from(entry.created_ts).unwrap_or(UInt::MAX),
49		filesystem_id: None,
50		url_cache: None,
51		last_access_ts: None,
52		quarantined_by: None,
53		authenticated: None,
54		safe_from_quarantine: local.then_some(false),
55		sha256: None,
56	}
57}
58
59#[cfg(test)]
60mod tests {
61	use ruma::{UInt, server_name, user_id};
62
63	use super::{UserMediaEntry, into_media_info};
64
65	fn entry(
66		user: bool,
67		media_type: Option<&str>,
68		upload_name: Option<&str>,
69		media_length: Option<u64>,
70		created_ts: u64,
71	) -> UserMediaEntry {
72		UserMediaEntry {
73			mxc: "mxc://example.org/abc123".into(),
74			media_type: media_type.map(ToOwned::to_owned),
75			upload_name: upload_name.map(ToOwned::to_owned),
76			media_length,
77			created_ts,
78			user_id: user.then(|| user_id!("@alice:example.org").to_owned()),
79		}
80	}
81
82	#[test]
83	fn local_media_fills_uploader_and_constants() {
84		let info = into_media_info(
85			true,
86			server_name!("example.org"),
87			entry(true, Some("image/png"), Some("pic.png"), Some(1024), 1_600_000_000_000),
88		);
89
90		assert_eq!(info.media_origin, None);
91		assert_eq!(info.user_id, Some(user_id!("@alice:example.org").to_owned()));
92		assert_eq!(info.media_id, "abc123");
93		assert_eq!(info.media_type, "image/png");
94		assert_eq!(info.media_length, Some(UInt::from(1024_u32)));
95		assert_eq!(info.upload_name.as_deref(), Some("pic.png"));
96		assert_eq!(info.created_ts, UInt::try_from(1_600_000_000_000_u64).unwrap());
97		assert_eq!(info.safe_from_quarantine, Some(false));
98		assert_eq!(info.filesystem_id, None);
99		assert_eq!(info.url_cache, None);
100		assert_eq!(info.last_access_ts, None);
101		assert_eq!(info.quarantined_by, None);
102		assert_eq!(info.authenticated, None);
103		assert_eq!(info.sha256, None);
104	}
105
106	#[test]
107	fn remote_media_sets_origin_and_drops_uploader() {
108		let info = into_media_info(
109			false,
110			server_name!("remote.example.com"),
111			entry(true, Some("image/png"), Some("pic.png"), Some(1024), 1_600_000_000_000),
112		);
113
114		assert_eq!(info.media_origin, Some(server_name!("remote.example.com").to_owned()));
115		assert_eq!(info.user_id, None);
116		assert_eq!(info.safe_from_quarantine, None);
117	}
118
119	#[test]
120	fn absent_derivables_stay_null() {
121		let info =
122			into_media_info(true, server_name!("example.org"), entry(false, None, None, None, 0));
123
124		assert_eq!(info.media_type, "");
125		assert_eq!(info.media_length, None);
126		assert_eq!(info.upload_name, None);
127		assert_eq!(info.user_id, None);
128		assert_eq!(info.created_ts, UInt::from(0_u32));
129	}
130
131	#[test]
132	fn oversized_lengths_saturate() {
133		let info = into_media_info(
134			true,
135			server_name!("example.org"),
136			entry(true, Some("image/png"), Some("pic.png"), Some(u64::MAX), u64::MAX),
137		);
138
139		assert_eq!(info.media_length, None);
140		assert_eq!(info.created_ts, UInt::MAX);
141	}
142}