actix/actix-web · warning · ParseRangeErr

invalid Range header: invalid syntax

Error message

invalid Range header: invalid syntax

What it means

Returned by HttpRange::parse (range.rs:50) when the Range request header fails the bytes= grammar and the underlying http_range crate reports InvalidRange. In actix-files, NamedFile::into_response calls HttpRange::parse on the Range header (named.rs:609) and, on error, sets Content-Range and responds with 416 Range Not Satisfiable. It means the client sent a malformed range specifier such as bytes=foo or bytes=A-Z rather than a valid bytes=start-end expression.

Solutions

  1. Send a syntactically valid range like bytes=0-499, bytes=500- (open-ended), or bytes=-500 (suffix); or omit the Range header to get the whole file.
  2. If you control the client, only use the bytes=start-end, bytes=start-, bytes=-suffix, or comma-separated combinations of those forms.
  3. On the server side, actix-files already auto-responds 416; no change is needed unless you want custom 416 handling.

Example fix

// before
curl -H "Range: bytes=5-Z" http://host/file

// after
curl -H "Range: bytes=0-499" http://host/file
Defensive patterns

Strategy: validation

Validate before calling

// Validate a Range header before relying on it (client side)
fn valid_range(h: &str) -> bool {
    let s = h.strip_prefix("bytes=").unwrap_or(h);
    !s.is_empty() && s.split(',').all(|p| {
        let p = p.trim();
        match p.split_once('-') {
            Some((a, b)) =>
                (a.is_empty() || a.chars().all(|c| c.is_ascii_digit()))
                && (b.is_empty() || b.chars().all(|c| c.is_ascii_digit())),
            None => false,
        }
    })
}

Try / catch

// Server: actix-files already turns this into a 416; for custom serving:
match actix_files::range::HttpRange::parse(range_header, file_len) {
    Ok(ranges) => { /* serve partial content */ }
    Err(_) => response.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(),
}

Prevention

When it happens

Trigger: A client sends Range: bytes=5-Z, bytes=foo, bytes=7 (incomplete single value), bytes=0x01-0x02, or bytes=A- . The test suite in range.rs:75-92 enumerates these as rejected inputs.

Common situations: Hand-crafted curl commands with a typo in the Range header; custom download managers or media players emitting non-standard specifiers; an intermediate proxy rewriting the header into an invalid form.

Related errors


AI-assisted analysis of actix/actix-web@4d435abc28 (2026-08-09). Data as JSON: /api/errors/30b78b1d8c71ea7c. Report an issue: GitHub.

Appendix: 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 4d435abc28)