From e559dc2b649e0ba496a4591b9b7775a3526d5a4e Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Sat, 11 Jul 2026 22:01:14 +0000 Subject: [PATCH] render: resolve protocol-relative image URLs An (ubiquitous on Wikipedia and CDN-hosted media) now inherits the document scheme, defaulting to https for non-network bases so a file:// page does not turn it into file://host. Previously url::join against a file base produced an unfetchable URL. --- crates/obscura-render/src/paint.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/crates/obscura-render/src/paint.rs b/crates/obscura-render/src/paint.rs index 2adc53b..1fd1d80 100644 --- a/crates/obscura-render/src/paint.rs +++ b/crates/obscura-render/src/paint.rs @@ -739,6 +739,17 @@ fn fetch_bytes( // or mask/background reference silently fails to fetch. let resolved = if src.starts_with("http://") || src.starts_with("https://") { Some(src.to_string()) + } else if let Some(rest) = src.strip_prefix("//") { + // Protocol-relative URL (`//upload.wikimedia.org/...`, ubiquitous on + // Wikipedia and CDN-hosted media): inherit the document scheme, but + // never `file:`/other non-network schemes (a `file://` base would give + // `file://host/...` and fail), so default to https for those. + let scheme = base_url + .and_then(|b| url::Url::parse(b).ok()) + .map(|u| u.scheme().to_string()) + .filter(|s| s == "http" || s == "https") + .unwrap_or_else(|| "https".to_string()); + Some(format!("{scheme}://{rest}")) } else { base_url .and_then(|b| url::Url::parse(b).ok()) @@ -779,12 +790,17 @@ fn http_get_bytes(url: &str) -> Option> { use std::io::Read; return resp.into_reader().read_to_end(&mut buf).ok().map(|_| buf); } - // 429 (rate limit) and 5xx are transient: back off and retry. + // 429 (rate limit) and 5xx are transient: a short backoff clears a + // brief blip. A sustained limit (Wikimedia 429s a 60-image burst + // from a datacenter IP hard, with `Retry-After: 1`) is NOT worth + // waiting out here: honoring the hint stalls the whole render for + // minutes, so fast-fail to the grey placeholder instead. Real + // fidelity for that case needs an HTTP/2 image client (multiplexing + // like Chrome), not blocking retries. Err(ureq::Error::Status(code, _)) if matches!(code, 429 | 500 | 502 | 503 | 504) && attempt < 2 => { std::thread::sleep(backoff); backoff *= 2; } - // A network/transport error is also worth one more try. Err(ureq::Error::Transport(_)) if attempt < 2 => { std::thread::sleep(backoff); backoff *= 2;