render: resolve protocol-relative image URLs

An <img src="//host/path"> (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.
This commit is contained in:
SGavrl
2026-07-11 22:01:14 +00:00
parent 281a22e247
commit e559dc2b64

View File

@@ -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<Vec<u8>> {
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;