ducaale/xh · error
Missing Content-Range header
Error message
Missing Content-Range header
What it means
download_file requires a Content-Range header when resuming a download. The server's response to the range request lacked the header entirely, so the client cannot determine the total length or confirm the resume position. Without it, resume bookkeeping is impossible.
Solutions
- Restart the download from scratch (offset 0) instead of resuming.
- Check the response status: resume only when the server replies 206 Partial Content.
- Verify the server supports Range requests (Accept-Ranges: bytes) before resuming.
- Bypass intermediaries (proxies/CDNs) that strip or mishandle range headers.
Example fix
// before
let total = total_for_content_range(header, resume)?;
// after
if response.status() == StatusCode::OK {
return restart_download_from_zero();
}
let total = total_for_content_range(header, resume)?; Defensive patterns
Strategy: validation
Validate before calling
let resp = client.get(url).header(header::RANGE, format!("bytes={}-", offset)).send()?;
if resp.status() != StatusCode::PARTIAL_CONTENT || !resp.headers().contains_key(header::CONTENT_RANGE) {
// restart from scratch instead of resuming
} Type guard
fn can_resume(resp: &Response) -> bool {
resp.status() == StatusCode::PARTIAL_CONTENT
&& resp.headers().contains_key(header::CONTENT_RANGE)
} Try / catch
match download_file(url, Some(offset)) {
Err(e) if e.to_string().contains("Content-Range") => download_file(url, None),
other => other,
} Prevention
- Only resume when the response status is 206 Partial Content.
- Check Accept-Ranges: bytes via a HEAD request before resuming.
- Handle 200-to-range responses by restarting the download.
- Bypass proxies known to strip range headers.
When it happens
Trigger: download_file called with Some(resume) and the HTTP response headers contain no CONTENT_RANGE entry (server answered without a Content-Range, e.g. with 200 instead of 206).
Common situations: Server ignores the Range header and returns 200 with the full body; resuming from servers/proxies that strip range support; remote file was deleted or replaced so no partial response was generated.
Related errors
- Can't parse Content-Range header, can't resume download
- Content-Range has wrong start
- Bad Content-Range header
- Invalid Content-Range
- Content-Range has wrong end
AI-assisted analysis of ducaale/xh@2404aceecc (2026-09-13).
Data as JSON: /api/errors/7a6d89b2f2a91769.
Report an issue: GitHub.
Appendix: source
Thrown at src/download.rs:210
dest_name = file_name;
buffer = Box::new(open_opts.open(&dest_name)?);
} else if test_pretend_term() || io::stdout().is_terminal() {
let (new_name, handle) = open_new_file(get_file_name(&response, orig_url).into())?;
dest_name = new_name;
buffer = Box::new(handle);
} else {
dest_name = "<stdout>".into();
buffer = Box::new(io::stdout());
}
let starting_length: u64;
let total_length: Option<u64>;
if let Some(resume) = resume {
let header = response
.headers()
.get(CONTENT_RANGE)
.ok_or_else(|| anyhow!("Missing Content-Range header"))?
.to_str()
.map_err(|_| anyhow!("Bad Content-Range header"))?;
starting_length = resume;
total_length = Some(total_for_content_range(header, starting_length)?);
} else {
starting_length = 0;
total_length = get_content_length(response.headers());
}
let starting_time = Instant::now();
let pb = if quiet {
// Still counts the downloaded bytes, it just doesn't display anything.
ProgressBar::hidden()
} else if let Some(total_length) = total_length {
eprintln!(
"Downloading {} to {:?}",
HumanBytes(total_length - starting_length),View on GitHub (pinned to 2404aceecc)