ducaale/xh · error
Content-Range has wrong end
Error message
Content-Range has wrong end: {:?} What it means
For a complete (non-wildcard) Content-Range, the declared complete_length must be exactly last_byte_pos + 1 — the range must end at the total. If it doesn't (and the range is otherwise positive), the header claims an inconsistent end and the resume is aborted.
Solutions
- Delete the partial file and restart the download from scratch
- Confirm the file hasn't changed on the server between the initial request and the resume (retry with a fresh ETag/size)
- If the server intentionally sends partial totals, it should use the wildcard form 'bytes <start>-<end>/*'
Defensive patterns
Strategy: try-catch
Validate before calling
fn content_range_end_consistent(last: u64, complete_length: Option<u64>) -> bool {
match complete_length {
None => true,
Some(cl) => cl == last + 1 || cl == 0, // wildcard handled elsewhere
}
} Try / catch
match total_for_content_range(header, expected_start) {
Ok(total) => resume_from(expected_start, total),
Err(e) if e.to_string().contains("wrong end") => {
eprintln!("Content-Range end mismatch (file may have changed); restarting");
delete_partial_and_restart();
}
Err(e) => return Err(e),
} Prevention
- For partial totals the server should emit 'bytes <start>-<end>/*'
- Check that the remote file size/ETag is unchanged before resuming
- Treat wrong-end responses as a signal to discard the partial file
When it happens
Trigger: Server sends 'bytes 0-99/200' — a complete_length that doesn't match last_byte_pos + 1 (should be /100 for a full body, or use 'bytes 0-99/*' for a partial total).
Common situations: Servers that report the total of a different resource version than the range, proxies synthesizing ranges, resume attempts against files that changed size on the server.
Related errors
- Invalid Content-Range
- Can't parse Content-Range header, can't resume download
- Content-Range has wrong start
- message-signature: Duplicate covered component identifier
- unknown compression type
AI-assisted analysis of ducaale/xh@2404aceecc (2026-09-13).
Data as JSON: /api/errors/cb4ba62a499850a7.
Report an issue: GitHub.
Appendix: source
Thrown at src/download.rs:150
let complete_length: Option<u64> = caps
.name("complete_length")
.map(|num| {
num.as_str()
.parse()
.context("Can't parse Content-Range complete_length")
})
.transpose()?;
// Note that last_byte_pos must be strictly less than complete_length
// If first_byte_pos == last_byte_pos exactly one byte is sent
if first_byte_pos > last_byte_pos {
return Err(anyhow!("Invalid Content-Range: {:?}", header));
}
if let Some(complete_length) = complete_length {
if last_byte_pos >= complete_length {
return Err(anyhow!("Invalid Content-Range: {:?}", header));
}
if complete_length != last_byte_pos + 1 {
return Err(anyhow!("Content-Range has wrong end: {:?}", header));
}
}
if expected_start != first_byte_pos {
return Err(anyhow!("Content-Range has wrong start: {:?}", header));
}
Ok(last_byte_pos + 1)
}
const BAR_TEMPLATE: &str =
"{spinner:.green} {percent}% [{wide_bar:.cyan/blue}] {bytes} {bytes_per_sec} ETA {eta}";
const UNCOLORED_BAR_TEMPLATE: &str =
"{spinner} {percent}% [{wide_bar}] {bytes} {bytes_per_sec} ETA {eta}";
const SPINNER_TEMPLATE: &str = "{spinner:.green} {bytes} {bytes_per_sec} {wide_msg}";
const UNCOLORED_SPINNER_TEMPLATE: &str = "{spinner} {bytes} {bytes_per_sec} {wide_msg}";
pub fn download_file(
response: Response,
file_name: Option<PathBuf>,View on GitHub (pinned to 2404aceecc)