actix/actix-web · warning · ParseRangeErr

invalid Range header: invalid syntax

Error message

invalid Range header: invalid syntax

What it means

Raised by HttpRange::parse (range.rs:50) when the underlying http_range crate returns HttpRangeParseError::InvalidRange, meaning the Range header bytes do not conform to the bytes-ranges grammar from RFC 7233. actix-files surfaces it as ParseRangeErr and, in NamedFile::into_response (named.rs:609-624), converts a failed parse into a 416 Range Not Satisfiable response with a Content-Range of 'bytes */<size>'. It is therefore a client-side protocol error, not a server bug.

Source

Thrown at actix-files/src/range.rs:29

impl From<http_range::HttpRangeParseError> for HttpRangeParseError {
    fn from(err: http_range::HttpRangeParseError) -> Self {
        match err {
            http_range::HttpRangeParseError::InvalidRange => Self::InvalidRange,
            http_range::HttpRangeParseError::NoOverlap => Self::NoOverlap,
        }
    }
}

#[derive(Debug, Clone, Error)]
#[non_exhaustive]
pub struct ParseRangeErr(#[error(not(source))] HttpRangeParseError);

impl fmt::Display for ParseRangeErr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str("invalid Range header: ")?;
        f.write_str(match self.0 {
            HttpRangeParseError::InvalidRange => "invalid syntax",
            HttpRangeParseError::NoOverlap => "range starts after end of content",
        })
    }
}

/// HTTP Range header representation.
#[derive(Debug, Clone, Copy)]
pub struct HttpRange {
    /// Start of range.
    pub start: u64,

    /// Length of range.
    pub length: u64,
}

impl HttpRange {
    /// Parses Range HTTP header string as per RFC 2616.
    ///

View on GitHub (pinned to 937960ca67)

Solutions

  1. Verify the client sends a syntactically valid 'bytes=' range such as 'Range: bytes=0-499' or 'Range: bytes=-500'.
  2. If you control the client, build the header with a tested helper rather than string concatenation.
  3. If the header is genuinely untrusted, the 416 actix returns is correct; no server change is needed.
  4. Log the raw Range header value server-side to identify which client is misbehaving.

Example fix

// before (malformed)
curl -H "Range: 0-99" http://host/file

// after (valid)
curl -H "Range: bytes=0-99" http://host/file
Defensive patterns

Strategy: validation

Validate before calling

// Validate the Range header string shape before relying on it.
fn valid_range_syntax(h: &str) -> bool {
    let h = h.trim();
    let Some(rest) = h.strip_prefix("bytes=") else { return false };
    if rest.is_empty() { return false }
    rest.split(',').all(|spec| {
        let spec = spec.trim();
        if spec.is_empty() { return false }
        let (a, b) = spec.split_once('-').unwrap_or((spec, ""));
        (a.is_empty() || a.chars().all(|c| c.is_ascii_digit()))
            && (b.is_empty() || b.chars().all(|c| c.is_ascii_digit()))
    })
}

Try / catch

// actix-files already converts this to 416; only handle if parsing manually.
match HttpRange::parse(range_header, file_size) {
    Ok(ranges) => { /* serve first range */ }
    Err(_) => {
        return HttpResponse::build(StatusCode::RANGE_NOT_SATISFIABLE)
            .insert_header((header::CONTENT_RANGE, format!("bytes */{}", file_size)))
            .finish();
    }
}

Prevention

When it happens

Trigger: A GET/HEAD request to a Files or NamedFile route carries a Range header whose syntax is malformed, e.g. 'Range: bytes=abc', 'Range: foo', 'Range: bytes=A-Z', or 'Range: bytes=0x01-0x02'. The first byte after 'bytes=' is not a digit, '-' or space, so read_size in chunked.rs:54-69 (mirrored logic) yields InvalidRange. Any of the negative test cases in range.rs:75-91 reproduce it.

Common situations: Custom client code building the Range header by hand (forgetting 'bytes='), browser extensions injecting bad headers, or curl invocations like 'curl -H "Range: 0-99"'. Also seen when a reverse proxy rewrites or truncates the header.

Related errors


AI-assisted analysis of actix/actix-web@937960ca67 (2026-08-06). Data as JSON: /data/errors/30b78b1d8c71ea7c.json. Report an issue: GitHub.