Skip to main content

parse_range_header

Function parse_range_header 

Source
pub fn parse_range_header(
    range_header_value: &str,
) -> Result<ParsedRanges, RangeUnsatisfiableError>
Expand description

Function that parses the content of a range header.

Follows the spec here

And RFC-9110 here

Will only accept byte-ranges, will update when this spec changes to allow other units.

Parses ranges strictly.

Ranges such as bytes=0-15, 16-20, abc will be rejected immediately.

It preserves the range ordering, the specification leaves it open to the server to determine whether ranges that are out of order are correct or not, ie bytes=20-30, 0-15

Allows a maximum of 100 ranges per request to limit DoS-risk, if more ranges are desired use parse_range_header_with_max_ranges.

§Example no trailing whitespace

// Ok
assert!(http_range_header::parse_range_header("bytes=0-15").is_ok());
// Not allowed
assert!(http_range_header::parse_range_header("bytes=0-15 ").is_err());
// Leading is allowed
assert!(http_range_header::parse_range_header("bytes= 0-15").is_ok());

§Example No negative values, no plus-sign

// No negatives
assert!(http_range_header::parse_range_header("bytes=-12-15").is_err());
// Leading zeroes are allowed
assert!(http_range_header::parse_range_header("bytes=00-15").is_ok());
// No plus sign
assert!(http_range_header::parse_range_header("bytes=+0-15").is_err());

§Example Max 30 digits per position

The rule exists to prevent unbounded string searching:

// u64::MAX and some extra is fine
assert!(http_range_header::parse_range_header("bytes=00-0018446744073709551615").is_ok());
// above 30 digits are rejected
assert!(http_range_header::parse_range_header("bytes=00-000000000000000000000000000000000000000000000000000001").is_err());

Makes two passes and parses ranges strictly. On the first pass, if any range is malformed returns an Err.

On the second pass it filters out unsatisfiable ranges. Invalid ranges produce an error.

§Example with a standard valid range

let input = "bytes=0-15";
let file_size_bytes = 512;
let parsed_ranges = http_range_header::parse_range_header(input);

match parsed_ranges {
    Ok(ranges) => {
        match ranges.validate(file_size_bytes) {
            Ok(valid_ranges) => {
                for range in valid_ranges {
                    // do something with ranges
                    assert_eq!(0..=15, range)
                }
            }
            Err(_err) => {
                // Do something when ranges doesn't make sense
                panic!("Weird range!")
            }
        }
    }
    Err(_err) => {
        // Do something with malformed ranges
        panic!("Malformed range!")
    }
}

The parser makes two passes, one without a known file-size, ensuring all ranges are syntactically correct. The returned struct will through its validate method accept a file-size and figure out whether or not the syntactically correct ranges actually makes sense in context

The range bytes=0-20 on a file with 15 bytes will be accepted in the first pass as the content size is unknown. On the second pass (validate) it will be truncated to file_size - 1 as per the spec.

§Example range truncates in validate because it exceeds

let input = "bytes=0-20";
let file_size_bytes = 15;
let parsed_ranges = http_range_header::parse_range_header(input)
    // Is syntactically correct
    .unwrap();
let validated = parsed_ranges.validate(file_size_bytes).unwrap();
assert_eq!(vec![0..=14], validated);

The range bytes=0-20, 5-10 will become two syntactically correct overlapping ranges. However, bytes=0-20, 5-10, 12-13 will be rejected on validate.

This is an opinionated implementation, the spec allows a server to determine its implementation of more than two overlapping ranges, this api currently does not allow it. It will accept at most 2 overlapping ranges.

§Example multipart-range validate on a single overlap, but not two

let input = "bytes=0-15, 10-20, 30-50";
let file_size_bytes = 512;
let parsed_ranges = http_range_header::parse_range_header(input)
    // Is syntactically correct
    .unwrap();
let validated = parsed_ranges.validate(file_size_bytes);
assert_eq!(Ok(vec![0..=15, 10..=20, 30..=50]), validated);
let input = "bytes=0-15, 10-20, 30-50, 45-";
let file_size_bytes = 512;
let parsed_ranges = http_range_header::parse_range_header(input)
    // Is syntactically correct
    .unwrap();
let validated = parsed_ranges.validate(file_size_bytes);
// Some ranges overlap, producing an error
assert!(validated.is_err());

§Errors

Will return an error if the range_header_value cannot be strictly parsed into a range per the http spec, or the header’s shape is not tolerated.