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

  1. Delete the partial file and restart the download from scratch
  2. Confirm the file hasn't changed on the server between the initial request and the resume (retry with a fresh ETag/size)
  3. 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

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


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)