ensure proxy urls have a host in front and not simple "/" path

This commit is contained in:
Simon
2026-06-23 06:33:03 +00:00
parent 614361f0f3
commit 0402e5ac76
3 changed files with 79 additions and 0 deletions

View File

@@ -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

View File

@@ -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<ProviderChannelMetadata> {
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"
);
}
}

View File

@@ -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",