1#![expect(deprecated)]
2
3use axum::extract::State;
4use reqwest::Url;
5use ruma::{
6 Mxc,
7 api::client::media::{
8 get_content, get_content_as_filename, get_content_thumbnail, get_media_config,
9 get_media_preview,
10 },
11};
12use tuwunel_core::{
13 Err, Result, err,
14 utils::{content_disposition::make_content_disposition, math::ruma_from_usize},
15};
16use tuwunel_service::media::{CACHE_CONTROL_IMMUTABLE, CORP_CROSS_ORIGIN, Dim, Media};
17
18use crate::{ClientIp, Ruma};
19
20pub(crate) async fn get_media_config_legacy_route(
24 State(services): State<crate::State>,
25 _body: Ruma<get_media_config::v3::Request>,
26) -> Result<get_media_config::v3::Response> {
27 Ok(get_media_config::v3::Response {
28 upload_size: ruma_from_usize(services.server.config.max_request_size),
29 })
30}
31
32#[tracing::instrument(skip_all, fields(%client), name = "url_preview_legacy", level = "debug")]
36pub(crate) async fn get_media_preview_legacy_route(
37 State(services): State<crate::State>,
38 ClientIp(client): ClientIp,
39 body: Ruma<get_media_preview::v3::Request>,
40) -> Result<get_media_preview::v3::Response> {
41 let sender_user = body.sender_user();
42
43 let url = &body.url;
44 let url = Url::parse(&body.url).map_err(|e| {
45 err!(Request(InvalidParam(
46 debug_warn!(%sender_user, %url, "Requested URL is not valid: {e}")
47 )))
48 })?;
49
50 if !services.media.url_preview_allowed(&url) {
51 return Err!(Request(Forbidden(
52 debug_warn!(%sender_user, %url, "URL is not allowed to be previewed")
53 )));
54 }
55
56 let preview = services
57 .media
58 .get_url_preview(&url)
59 .await
60 .map_err(|e| {
61 err!(Request(Unknown(
62 debug_error!(%sender_user, %url, "Failed to fetch a URL preview: {e}")
63 )))
64 })?;
65
66 serde_json::value::to_raw_value(&preview)
67 .map(get_media_preview::v3::Response::from_raw_value)
68 .map_err(|error| {
69 err!(Request(Unknown(
70 debug_error!(%sender_user, %url, "Failed to parse URL preview: {error}")
71 )))
72 })
73}
74
75#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy", level = "debug")]
84pub(crate) async fn get_content_legacy_route(
85 State(services): State<crate::State>,
86 ClientIp(client): ClientIp,
87 body: Ruma<get_content::v3::Request>,
88) -> Result<get_content::v3::Response> {
89 let mxc = Mxc {
90 server_name: &body.server_name,
91 media_id: &body.media_id,
92 };
93
94 match services
95 .media
96 .get(&mxc, Some(body.timeout_ms))
97 .await
98 {
99 | Ok(Media {
100 content,
101 content_type,
102 content_disposition,
103 }) => {
104 let content_disposition = make_content_disposition(
105 content_disposition.as_ref(),
106 content_type.as_deref(),
107 None,
108 );
109
110 Ok(get_content::v3::Response {
111 file: content,
112 content_type: content_type.map(Into::into),
113 content_disposition: Some(content_disposition),
114 cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()),
115 cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
116 })
117 },
118 | Err(e) =>
119 if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
120 let response = services
121 .media
122 .fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms)
123 .await
124 .map_err(|e| {
125 err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
126 })?;
127
128 let content_disposition = make_content_disposition(
129 response.content_disposition.as_ref(),
130 response.content_type.as_deref(),
131 None,
132 );
133
134 Ok(get_content::v3::Response {
135 file: response.file,
136 content_type: response.content_type,
137 content_disposition: Some(content_disposition),
138 cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()),
139 cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
140 })
141 } else {
142 Err(e)
143 },
144 }
145}
146
147#[tracing::instrument(skip_all, fields(%client), name = "media_get_legacy", level = "debug")]
156pub(crate) async fn get_content_as_filename_legacy_route(
157 State(services): State<crate::State>,
158 ClientIp(client): ClientIp,
159 body: Ruma<get_content_as_filename::v3::Request>,
160) -> Result<get_content_as_filename::v3::Response> {
161 let mxc = Mxc {
162 server_name: &body.server_name,
163 media_id: &body.media_id,
164 };
165
166 match services
167 .media
168 .get(&mxc, Some(body.timeout_ms))
169 .await
170 {
171 | Ok(Media {
172 content,
173 content_type,
174 content_disposition,
175 }) => {
176 let content_disposition = make_content_disposition(
177 content_disposition.as_ref(),
178 content_type.as_deref(),
179 Some(&body.filename),
180 );
181
182 Ok(get_content_as_filename::v3::Response {
183 file: content,
184 content_type: content_type.map(Into::into),
185 content_disposition: Some(content_disposition),
186 cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()),
187 cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
188 })
189 },
190 | Err(e) =>
191 if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
192 let response = services
193 .media
194 .fetch_remote_content_legacy(&mxc, body.allow_redirect, body.timeout_ms)
195 .await
196 .map_err(|e| {
197 err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
198 })?;
199
200 let content_disposition = make_content_disposition(
201 response.content_disposition.as_ref(),
202 response.content_type.as_deref(),
203 None,
204 );
205
206 Ok(get_content_as_filename::v3::Response {
207 content_disposition: Some(content_disposition),
208 content_type: response.content_type,
209 file: response.file,
210 cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()),
211 cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
212 })
213 } else {
214 Err(e)
215 },
216 }
217}
218
219#[tracing::instrument(skip_all, fields(%client), name = "media_thumbnail_get_legacy", level = "debug")]
228pub(crate) async fn get_content_thumbnail_legacy_route(
229 State(services): State<crate::State>,
230 ClientIp(client): ClientIp,
231 body: Ruma<get_content_thumbnail::v3::Request>,
232) -> Result<get_content_thumbnail::v3::Response> {
233 let mxc = Mxc {
234 server_name: &body.server_name,
235 media_id: &body.media_id,
236 };
237
238 let dim = Dim::from_ruma(body.width, body.height, body.method.clone())?;
239 match services
240 .media
241 .get_thumbnail(&mxc, &dim, Some(body.timeout_ms))
242 .await
243 {
244 | Ok(Media {
245 content,
246 content_type,
247 content_disposition,
248 }) => {
249 let content_disposition = make_content_disposition(
250 content_disposition.as_ref(),
251 content_type.as_deref(),
252 None,
253 );
254
255 Ok(get_content_thumbnail::v3::Response {
256 file: content,
257 content_type: content_type.map(Into::into),
258 cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()),
259 cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
260 content_disposition: Some(content_disposition),
261 })
262 },
263 | Err(e) =>
264 if !services.globals.server_is_ours(&body.server_name) && body.allow_remote {
265 let response = services
266 .media
267 .fetch_remote_thumbnail_legacy(&body)
268 .await
269 .map_err(|e| {
270 err!(Request(NotFound(debug_warn!(%mxc, "Fetching media failed: {e:?}"))))
271 })?;
272
273 let content_disposition = make_content_disposition(
274 response.content_disposition.as_ref(),
275 response.content_type.as_deref(),
276 None,
277 );
278
279 Ok(get_content_thumbnail::v3::Response {
280 file: response.file,
281 content_type: response.content_type,
282 cross_origin_resource_policy: Some(CORP_CROSS_ORIGIN.into()),
283 cache_control: Some(CACHE_CONTROL_IMMUTABLE.into()),
284 content_disposition: Some(content_disposition),
285 })
286 } else {
287 Err(e)
288 },
289 }
290}