From db837e370993cf9b306cbadfbed409d61b14fceb Mon Sep 17 00:00:00 2001 From: timedout Date: Tue, 3 Nov 2026 18:42:48 +0000 Subject: [PATCH] fix: Don't fail entire preview requests when auxiliary requests fail --- src/api/client/media.rs | 3 +- src/service/client/mod.rs | 2 +- src/service/media/preview.rs | 71 ++++++++++++++++++++---------------- 3 files changed, 42 insertions(+), 34 deletions(-) diff --git a/src/api/client/media.rs b/src/api/client/media.rs index 824d98541..68b459abb 100644 --- a/src/api/client/media.rs +++ b/src/api/client/media.rs @@ -2,7 +2,7 @@ use std::time::Duration; use axum::extract::State; use conduwuit::{ - Err, Result, err, + Err, Result, err, info, utils::{self, content_disposition::make_content_disposition, math::ruma_from_usize}, }; use conduwuit_core::error; @@ -219,6 +219,7 @@ pub(crate) async fn get_media_preview_route( ))); } + info!("Fetching a URL preview for {url} on behalf of {sender_user}"); let preview = services .media .get_url_preview(&url) diff --git a/src/service/client/mod.rs b/src/service/client/mod.rs index f258f5195..76d183734 100644 --- a/src/service/client/mod.rs +++ b/src/service/client/mod.rs @@ -111,7 +111,7 @@ impl crate::Service for Service { .local_address(url_preview_bind_addr) .timeout(Duration::from_secs(config.url_preview_timeout)) .redirect(redirect::Policy::limited(3)) - .user_agent(url_preview_user_agent) + .user_agent(url_preview_user_agent.replace("", conduwuit::version_ua())) .build()?, external_resource: base(config, reqwest_dns_resolver.clone())? diff --git a/src/service/media/preview.rs b/src/service/media/preview.rs index 084aa9a1b..e48df437b 100644 --- a/src/service/media/preview.rs +++ b/src/service/media/preview.rs @@ -7,7 +7,7 @@ use std::time::SystemTime; -use conduwuit::{Err, Result, debug, err, utils::response::LimitReadExt}; +use conduwuit::{Err, Result, debug, err, utils::response::LimitReadExt, info}; use ipaddress::IPAddress; use ruma::OwnedMxcUri; use serde::Serialize; @@ -100,7 +100,9 @@ impl Service { } let Some(content_type) = response.headers().get(reqwest::header::CONTENT_TYPE) else { - return Err!(Request(Unknown("Unknown or invalid Content-Type header"))); + return Err!(Request(Unknown( + "Response contains unknown, invalid, or missing Content-Type header" + ))); }; let content_type = content_type @@ -109,12 +111,10 @@ impl Service { let data = match content_type { | html if html.starts_with("text/html") => self.download_html(url.as_str()).await?, - | img if img.starts_with("image/") => self.download_image(url.as_str(), None).await?, - | video if video.starts_with("video/") => - self.download_video(url.as_str(), None).await?, - | audio if audio.starts_with("audio/") => - self.download_audio(url.as_str(), None).await?, - | _ => return Err!(Request(Unknown("Unsupported Content-Type"))), + | img if img.starts_with("image/") => self.download_image(url.as_str()).await?, + | video if video.starts_with("video/") => self.download_video(url.as_str()).await?, + | audio if audio.starts_with("audio/") => self.download_audio(url.as_str()).await?, + | other => return Err!(Request(Unknown("Unsupported Content-Type: {other}"))), }; self.set_url_preview(url.as_str(), &data).await?; @@ -123,17 +123,13 @@ impl Service { } #[cfg(feature = "url_preview")] - pub async fn download_image( - &self, - url: &str, - preview_data: Option, - ) -> Result { + pub async fn download_image(&self, url: &str) -> Result { use conduwuit::utils::random_string; use image::ImageReader; use crate::media::mxc::Mxc; - let mut preview_data = preview_data.unwrap_or_default(); + let mut preview_data = UrlPreviewData::default(); let response = self.services.client.url_preview.get(url).send().await?; if !response.status().is_success() { @@ -179,12 +175,8 @@ impl Service { } #[cfg(feature = "url_preview")] - pub async fn download_video( - &self, - url: &str, - preview_data: Option, - ) -> Result { - let mut preview_data = preview_data.unwrap_or_default(); + pub async fn download_video(&self, url: &str) -> Result { + let mut preview_data = UrlPreviewData::default(); if self.services.globals.url_preview_allow_audio_video() { let (url, size) = self.download_media(url).await?; @@ -196,12 +188,8 @@ impl Service { } #[cfg(feature = "url_preview")] - pub async fn download_audio( - &self, - url: &str, - preview_data: Option, - ) -> Result { - let mut preview_data = preview_data.unwrap_or_default(); + pub async fn download_audio(&self, url: &str) -> Result { + let mut preview_data = UrlPreviewData::default(); if self.services.globals.url_preview_allow_audio_video() { let (url, size) = self.download_media(url).await?; @@ -286,24 +274,43 @@ impl Service { .expect("u64 should fit in usize"), ) .await?; - let Ok(html) = HTML::from_string(body.clone(), Some(url.to_owned())) else { + let Ok(html) = HTML::from_string(body.clone(), Some(url.to_owned())) + .inspect_err(|e| debug!(error=?e, "Failed to parse HTML data")) + else { return Err!(Request(Unknown("Failed to parse HTML"))); }; let mut preview_data = UrlPreviewData::default(); if let Some(obj) = html.opengraph.images.first() { - preview_data = self.download_image(&obj.url, Some(preview_data)).await?; + if let Ok(image_data) = self.download_image(&obj.url).await.inspect_err( + |e| info!(error=?e, asset_url=%obj.url, "Could not download URL preview image"), + ) { + preview_data.image = image_data.image; + preview_data.image_size = image_data.image_size; + preview_data.image_width = image_data.image_width; + preview_data.image_height = image_data.image_height; + } } if let Some(obj) = html.opengraph.videos.first() { - preview_data = self.download_video(&obj.url, Some(preview_data)).await?; - preview_data.video_width = obj.properties.get("width").and_then(|v| v.parse().ok()); - preview_data.video_height = obj.properties.get("height").and_then(|v| v.parse().ok()); + if let Ok(video_data) = self.download_video(&obj.url).await.inspect_err( + |e| info!(error=?e, asset_url=%obj.url, "Could not download URL preview video"), + ) { + preview_data.video = video_data.video; + preview_data.video_width = video_data.video_width; + preview_data.video_height = video_data.video_height; + preview_data.video_size = video_data.video_size; + } } if let Some(obj) = html.opengraph.audios.first() { - preview_data = self.download_audio(&obj.url, Some(preview_data)).await?; + if let Ok(audio_data) = self.download_audio(&obj.url).await.inspect_err( + |e| info!(error=?e, asset_url=%obj.url, "Could not download URL preview audio"), + ) { + preview_data.audio = audio_data.audio; + preview_data.audio_size = audio_data.audio_size; + } } let props = html.opengraph.properties;