From 0402e5ac764f177aab464f6cf90c39f85ec9e1b0 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 23 Jun 2026 06:33:03 +0000 Subject: [PATCH] ensure proxy urls have a host in front and not simple "/" path --- src/api.rs | 15 ++++++++++++++ src/providers/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++ src/videos.rs | 16 +++++++++++++++ 3 files changed, 79 insertions(+) diff --git a/src/api.rs b/src/api.rs index ec14bc2..2dad2a8 100644 --- a/src/api.rs +++ b/src/api.rs @@ -610,6 +610,12 @@ async fn videos_post( } } + if let Some(base) = options.public_url_base.as_deref() { + for video in video_items.iter_mut() { + video.ensure_proxy_host(base); + } + } + videos.pageInfo = PageInfo { hasNextPage: false, resultsPerPage: perPage as u32, @@ -659,6 +665,15 @@ async fn videos_post( video.id = format!("{}:{}", channel, video.id); } + // Cached items may have been built without a request host (e.g. by runtime + // validation), leaving host-relative `/proxy/...` URLs. Re-apply the host + // from this request so proxy URLs are always absolute. + if let Some(base) = options.public_url_base.as_deref() { + for video in video_items.iter_mut() { + video.ensure_proxy_host(base); + } + } + // There is a bug in Hottub38 that makes the client error for a 403-url even though formats work fine if clientversion == ClientVersion::new(38, 0, "Hot%20Tub".to_string()) { // filter out videos without preview for old clients diff --git a/src/providers/mod.rs b/src/providers/mod.rs index 377b088..ee9c160 100644 --- a/src/providers/mod.rs +++ b/src/providers/mod.rs @@ -671,6 +671,22 @@ pub fn build_proxy_url(options: &ServerOptions, proxy: &str, target: &str) -> St } } +/// Re-applies the public host to a (possibly host-relative) proxy URL. +/// +/// Provider results are cached as fully-rendered items, so the `public_url_base` +/// in effect when an item is *first* built gets baked into its proxy URLs. Items +/// first produced by runtime validation (which has no request, so +/// `public_url_base` is `None`) therefore carry host-relative `/proxy/...` URLs. +/// Response handlers call this with the host derived from the incoming request so +/// every proxy URL is absolute, regardless of which context populated the cache. +pub fn ensure_proxy_url_host(url: &str, public_url_base: &str) -> String { + let base = public_url_base.trim_end_matches('/'); + if base.is_empty() || !url.starts_with("/proxy/") { + return url.to_string(); + } + format!("{base}{url}") +} + fn channel_metadata_for(id: &str) -> Option { include!(concat!(env!("OUT_DIR"), "/provider_metadata_fn.rs")) } @@ -1678,4 +1694,36 @@ mod tests { eprintln!("skipped providers:\n{}", skipped.join("\n")); } } + + #[test] + fn ensure_proxy_url_host_prepends_host_to_relative_proxy_urls() { + // Host-relative proxy URL (as produced when public_url_base was None, + // e.g. by runtime validation) gets the request host applied. + assert_eq!( + ensure_proxy_url_host("/proxy/sxyprn/post/abc", "http://hottub:18080"), + "http://hottub:18080/proxy/sxyprn/post/abc" + ); + // A trailing slash on the base is not duplicated. + assert_eq!( + ensure_proxy_url_host("/proxy/sxyprn/post/abc", "http://hottub:18080/"), + "http://hottub:18080/proxy/sxyprn/post/abc" + ); + // Already-absolute URLs are left untouched. + assert_eq!( + ensure_proxy_url_host("https://cdn.example/v.mp4", "http://hottub:18080"), + "https://cdn.example/v.mp4" + ); + assert_eq!( + ensure_proxy_url_host( + "http://other:18080/proxy/sxyprn/post/abc", + "http://hottub:18080" + ), + "http://other:18080/proxy/sxyprn/post/abc" + ); + // Empty base leaves the URL as-is rather than producing a bare host. + assert_eq!( + ensure_proxy_url_host("/proxy/sxyprn/post/abc", ""), + "/proxy/sxyprn/post/abc" + ); + } } diff --git a/src/videos.rs b/src/videos.rs index 7fe2598..b4f5adc 100644 --- a/src/videos.rs +++ b/src/videos.rs @@ -194,6 +194,22 @@ impl VideoItem { self } + /// Re-applies the request host to every host-relative proxy URL on this item + /// (its `url`, `preview`, and each format's `url`). Cached items may have been + /// built without a host; see [`crate::providers::ensure_proxy_url_host`]. + pub fn ensure_proxy_host(&mut self, public_url_base: &str) { + self.url = crate::providers::ensure_proxy_url_host(&self.url, public_url_base); + if let Some(preview) = &self.preview { + self.preview = + Some(crate::providers::ensure_proxy_url_host(preview, public_url_base)); + } + if let Some(formats) = &mut self.formats { + for format in formats.iter_mut() { + format.url = crate::providers::ensure_proxy_url_host(&format.url, public_url_base); + } + } + } + #[cfg(any( not(hottub_single_provider), hottub_provider = "chaturbate",