{"id":"30b78b1d8c71ea7c","repo":"actix/actix-web","slug":"invalid-range-header-invalid-syntax","errorCode":null,"errorMessage":"invalid Range header: invalid syntax","messagePattern":"invalid Range header: invalid syntax","errorType":"exception","errorClass":"ParseRangeErr","httpStatus":416,"severity":"warning","filePath":"actix-files/src/range.rs","lineNumber":29,"sourceCode":"\nimpl From<http_range::HttpRangeParseError> for HttpRangeParseError {\n    fn from(err: http_range::HttpRangeParseError) -> Self {\n        match err {\n            http_range::HttpRangeParseError::InvalidRange => Self::InvalidRange,\n            http_range::HttpRangeParseError::NoOverlap => Self::NoOverlap,\n        }\n    }\n}\n\n#[derive(Debug, Clone, Error)]\n#[non_exhaustive]\npub struct ParseRangeErr(#[error(not(source))] HttpRangeParseError);\n\nimpl fmt::Display for ParseRangeErr {\n    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {\n        f.write_str(\"invalid Range header: \")?;\n        f.write_str(match self.0 {\n            HttpRangeParseError::InvalidRange => \"invalid syntax\",\n            HttpRangeParseError::NoOverlap => \"range starts after end of content\",\n        })\n    }\n}\n\n/// HTTP Range header representation.\n#[derive(Debug, Clone, Copy)]\npub struct HttpRange {\n    /// Start of range.\n    pub start: u64,\n\n    /// Length of range.\n    pub length: u64,\n}\n\nimpl HttpRange {\n    /// Parses Range HTTP header string as per RFC 2616.\n    ///","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/actix/actix-web/blob/937960ca67f20e14ffe2a075bf6d4593502be12c/actix-files/src/range.rs#L11-L47","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify the client sends a syntactically valid 'bytes=' range such as 'Range: bytes=0-499' or 'Range: bytes=-500'.","If you control the client, build the header with a tested helper rather than string concatenation.","If the header is genuinely untrusted, the 416 actix returns is correct; no server change is needed.","Log the raw Range header value server-side to identify which client is misbehaving."],"exampleFix":"// before (malformed)\ncurl -H \"Range: 0-99\" http://host/file\n\n// after (valid)\ncurl -H \"Range: bytes=0-99\" http://host/file","handlingStrategy":"validation","validationCode":"// Validate the Range header string shape before relying on it.\nfn valid_range_syntax(h: &str) -> bool {\n    let h = h.trim();\n    let Some(rest) = h.strip_prefix(\"bytes=\") else { return false };\n    if rest.is_empty() { return false }\n    rest.split(',').all(|spec| {\n        let spec = spec.trim();\n        if spec.is_empty() { return false }\n        let (a, b) = spec.split_once('-').unwrap_or((spec, \"\"));\n        (a.is_empty() || a.chars().all(|c| c.is_ascii_digit()))\n            && (b.is_empty() || b.chars().all(|c| c.is_ascii_digit()))\n    })\n}","typeGuard":null,"tryCatchPattern":"// actix-files already converts this to 416; only handle if parsing manually.\nmatch HttpRange::parse(range_header, file_size) {\n    Ok(ranges) => { /* serve first range */ }\n    Err(_) => {\n        return HttpResponse::build(StatusCode::RANGE_NOT_SATISFIABLE)\n            .insert_header((header::CONTENT_RANGE, format!(\"bytes */{}\", file_size)))\n            .finish();\n    }\n}","preventionTips":["Never hand-build the Range header; use a helper that emits 'bytes=<start>-<end>'.","Treat a 416 from actix-files as expected client behaviour, not a server fault.","Log the raw Range value when investigating to spot malformed clients."],"tags":["http","range-header","actix-files","client-error"],"analyzedSha":"937960ca67f20e14ffe2a075bf6d4593502be12c","analyzedAt":"2026-08-06T01:15:46.978Z","schemaVersion":2}