Skip to main content

http_range_header/
lib.rs

1#![warn(clippy::pedantic)]
2// Lints old vs new rust causes issues
3#![allow(unknown_lints)]
4#![allow(clippy::uninlined_format_args, clippy::single_range_in_vec_init)]
5use core::fmt::{Debug, Display, Formatter};
6use core::ops::RangeInclusive;
7use std::num::{IntErrorKind, NonZeroUsize};
8
9const UNIT_SEP: &str = "bytes=";
10/// Function that parses the content of a range header.
11///
12/// Follows the [spec here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Range)
13///
14/// And [RFC-9110 here](https://www.rfc-editor.org/rfc/rfc9110.html)
15///
16/// Will only accept byte-ranges, will update when [this spec](https://www.iana.org/assignments/http-parameters/http-parameters.xhtml) changes to allow other units.
17///
18/// Parses ranges strictly.
19///
20/// Ranges such as `bytes=0-15, 16-20, abc` will be rejected immediately.
21///
22/// It preserves the range ordering, the specification leaves it open to the server to determine whether
23/// ranges that are out of order are correct or not, ie `bytes=20-30, 0-15`
24///
25/// Allows a maximum of `100` ranges per request to limit DoS-risk, if more ranges are desired
26/// use [`parse_range_header_with_max_ranges`].
27///
28/// # Example no trailing whitespace
29/// ```
30/// // Ok
31/// assert!(http_range_header::parse_range_header("bytes=0-15").is_ok());
32/// // Not allowed
33/// assert!(http_range_header::parse_range_header("bytes=0-15 ").is_err());
34/// // Leading is allowed
35/// assert!(http_range_header::parse_range_header("bytes= 0-15").is_ok());
36/// ```
37///
38/// # Example No negative values, no plus-sign
39/// ```
40/// // No negatives
41/// assert!(http_range_header::parse_range_header("bytes=-12-15").is_err());
42/// // Leading zeroes are allowed
43/// assert!(http_range_header::parse_range_header("bytes=00-15").is_ok());
44/// // No plus sign
45/// assert!(http_range_header::parse_range_header("bytes=+0-15").is_err());
46/// ```
47///
48/// # Example Max 30 digits per position
49/// The rule exists to prevent unbounded string searching:
50/// ```
51/// // u64::MAX and some extra is fine
52/// assert!(http_range_header::parse_range_header("bytes=00-0018446744073709551615").is_ok());
53/// // above 30 digits are rejected
54/// assert!(http_range_header::parse_range_header("bytes=00-000000000000000000000000000000000000000000000000000001").is_err());
55/// ```
56///
57/// Makes two passes and parses ranges strictly. On the first pass, if any range is malformed returns an `Err`.
58///
59/// On the second pass it filters out unsatisfiable ranges. Invalid ranges produce an error.
60/// # Example with a standard valid range
61///
62/// ```
63/// let input = "bytes=0-15";
64/// let file_size_bytes = 512;
65/// let parsed_ranges = http_range_header::parse_range_header(input);
66///
67/// match parsed_ranges {
68///     Ok(ranges) => {
69///         match ranges.validate(file_size_bytes) {
70///             Ok(valid_ranges) => {
71///                 for range in valid_ranges {
72///                     // do something with ranges
73///                     assert_eq!(0..=15, range)
74///                 }
75///             }
76///             Err(_err) => {
77///                 // Do something when ranges doesn't make sense
78///                 panic!("Weird range!")
79///             }
80///         }
81///     }
82///     Err(_err) => {
83///         // Do something with malformed ranges
84///         panic!("Malformed range!")
85///     }
86/// }
87/// ```
88///
89/// The parser makes two passes, one without a known file-size, ensuring all ranges are syntactically correct.
90/// The returned struct will through its `validate` method accept a file-size and figure out whether or not the
91/// syntactically correct ranges actually makes sense in context
92///
93/// The range `bytes=0-20` on a file with 15 bytes will be accepted in the first pass as the content size is unknown.
94/// On the second pass (`validate`) it will be truncated to `file_size - 1` as per [the spec](https://httpwg.org/specs/rfc9110.html#rfc.section.14.1.2).
95/// # Example range truncates in `validate` because it exceeds
96/// ```
97/// let input = "bytes=0-20";
98/// let file_size_bytes = 15;
99/// let parsed_ranges = http_range_header::parse_range_header(input)
100///     // Is syntactically correct
101///     .unwrap();
102/// let validated = parsed_ranges.validate(file_size_bytes).unwrap();
103/// assert_eq!(vec![0..=14], validated);
104/// ```
105///
106/// The range `bytes=0-20, 5-10` will become two syntactically correct overlapping ranges.
107/// However, `bytes=0-20, 5-10, 12-13` will be rejected on `validate`.
108///
109/// This is an opinionated implementation, [the spec](https://www.rfc-editor.org/rfc/rfc9110.html)
110/// allows a server to determine its implementation of more than two overlapping ranges,
111/// this api currently does not allow it. It will accept at most 2 overlapping ranges.
112///
113/// # Example multipart-range `validate` on a single overlap, but not two
114/// ```
115/// let input = "bytes=0-15, 10-20, 30-50";
116/// let file_size_bytes = 512;
117/// let parsed_ranges = http_range_header::parse_range_header(input)
118///     // Is syntactically correct
119///     .unwrap();
120/// let validated = parsed_ranges.validate(file_size_bytes);
121/// assert_eq!(Ok(vec![0..=15, 10..=20, 30..=50]), validated);
122/// let input = "bytes=0-15, 10-20, 30-50, 45-";
123/// let file_size_bytes = 512;
124/// let parsed_ranges = http_range_header::parse_range_header(input)
125///     // Is syntactically correct
126///     .unwrap();
127/// let validated = parsed_ranges.validate(file_size_bytes);
128/// // Some ranges overlap, producing an error
129/// assert!(validated.is_err());
130/// ```
131/// # Errors
132/// Will return an error if the `range_header_value` cannot be strictly parsed into a range
133/// per the http spec, or the header's shape is not tolerated.
134#[allow(clippy::missing_panics_doc)]
135pub fn parse_range_header(
136    range_header_value: &str,
137) -> Result<ParsedRanges, RangeUnsatisfiableError> {
138    // Const unwrap not available in 1.60.0
139    const NZ_100: NonZeroUsize = match NonZeroUsize::new(100) {
140        None => {
141            unreachable!();
142        }
143        Some(nz) => nz,
144    };
145    // Picked somewhat arbitrarily, on my bench machine this comes in at sub 2 micros (parse + validate).
146    // A value that should be async-safe based on: <https://ryhl.io/blog/async-what-is-blocking/>
147    parse_range_header_with_max_ranges(range_header_value, NZ_100)
148}
149
150/// Like [`parse_range_header`] with a configurable maximum amount of acceptable ranges
151/// in a single header value.
152/// # Errors
153/// Same as [`parse_range_header`]
154pub fn parse_range_header_with_max_ranges(
155    range_header_value: &str,
156    max_ranges: NonZeroUsize,
157) -> Result<ParsedRanges, RangeUnsatisfiableError> {
158    const COMMA: char = ',';
159    let indicated_range = match range_header_value.get(..UNIT_SEP.len()) {
160        Some(r) => {
161            if !r.eq_ignore_ascii_case(UNIT_SEP) {
162                return Err(RangeUnsatisfiableError::Invalid(
163                    RangeInvalid::DoesNotStartWithToken,
164                ));
165            }
166            match range_header_value.get(UNIT_SEP.len()..) {
167                Some(r) => r,
168                None => {
169                    // Unreachable, but `split_at_checked` is not possible at `Rust 1.60`
170                    return Err(RangeUnsatisfiableError::Invalid(
171                        RangeInvalid::DoesNotStartWithToken,
172                    ));
173                }
174            }
175        }
176        None => {
177            return Err(RangeUnsatisfiableError::Invalid(
178                RangeInvalid::DoesNotStartWithToken,
179            ));
180        }
181    };
182    let mut last_err = None;
183    let mut num_empty = 0;
184    let mut ranges = Vec::new();
185    for range in indicated_range.split(COMMA) {
186        if num_empty >= MAX_EMPTY {
187            return Err(RangeUnsatisfiableError::Rejected(
188                RangeRejected::TooManyUnparseable,
189            ));
190        }
191        let val = if let Some(trimmed) = trim(range) {
192            if trimmed.is_empty() {
193                num_empty += 1;
194                continue;
195            }
196            match parse_inner(trimmed) {
197                Ok(parsed) => parsed,
198                Err(e) => {
199                    last_err = Some(e);
200                    num_empty += 1;
201                    continue;
202                }
203            }
204        } else {
205            last_err = Some(RangeUnsatisfiableError::Invalid(
206                RangeInvalid::IllegalWhitespace,
207            ));
208            num_empty += 1;
209            continue;
210        };
211        ranges.push(val);
212        if ranges.len() > max_ranges.get() {
213            return Err(RangeUnsatisfiableError::Rejected(RangeRejected::TooMany));
214        }
215    }
216    if let Some(last_err) = last_err {
217        return Err(last_err);
218    }
219    if indicated_range.ends_with(space_or_tab) {
220        // Having a final empty group with whitespace is allowed
221        if !indicated_range
222            .trim_end_matches(space_or_tab)
223            .ends_with(',')
224        {
225            return Err(RangeUnsatisfiableError::Invalid(
226                RangeInvalid::IllegalWhitespace,
227            ));
228        }
229    }
230    if ranges.is_empty() {
231        Err(RangeUnsatisfiableError::Invalid(RangeInvalid::NoNonempty))
232    } else {
233        Ok(ParsedRanges::new(ranges))
234    }
235}
236
237const MAX_EMPTY: usize = 15;
238
239fn trim(s: &str) -> Option<&str> {
240    let trimmed = s.trim_matches(space_or_tab);
241    if trimmed.starts_with(char::is_whitespace) || trimmed.ends_with(char::is_whitespace) {
242        None
243    } else {
244        Some(trimmed)
245    }
246}
247
248fn space_or_tab(ch: char) -> bool {
249    ch == ' ' || ch == '\t'
250}
251
252#[derive(Debug, Copy, Clone, Eq, PartialEq)]
253pub enum SyntacticallyCorrectRange {
254    FromStart(u64),
255    FromLast(u64),
256    // Guaranteed 0 <= 1
257    Indexed(u64, u64),
258}
259
260#[inline]
261fn parse_inner(range: &str) -> Result<SyntacticallyCorrectRange, RangeUnsatisfiableError> {
262    if let Some((start, end)) = range.split_once('-') {
263        if start.is_empty() {
264            let end = strict_parse_u64(end)?;
265            return Ok(SyntacticallyCorrectRange::FromLast(end));
266        }
267        let start = strict_parse_u64(start)?;
268        if end.is_empty() {
269            return Ok(SyntacticallyCorrectRange::FromStart(start));
270        }
271        let end = strict_parse_u64(end)?;
272        if start > end {
273            return Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Reversed));
274        }
275        return Ok(SyntacticallyCorrectRange::Indexed(start, end));
276    }
277    Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable))
278}
279
280fn strict_parse_u64(s: &str) -> Result<u64, RangeUnsatisfiableError> {
281    const MAX_DIGITS: usize = 30;
282    if s.starts_with('+') {
283        return Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable));
284    }
285    if s.len() > MAX_DIGITS {
286        // We don't actually know if there are only digits in there yet
287        return Err(RangeUnsatisfiableError::Rejected(
288            RangeRejected::TooManyDigits,
289        ));
290    }
291    match s.parse::<u64>() {
292        Ok(o) => Ok(o),
293        Err(e)
294            if e.kind() == &IntErrorKind::PosOverflow
295                && s.chars().all(|ch| ch.is_ascii_digit()) =>
296        {
297            // We're relying on the file not actually being larger than u64::MAX
298            // implicitly treating an overflow as full-file (which is the correct spec-following behavior).
299            // This does mean that a reversed range will be hidden, though it will still be a
300            // `Unsatisfiable` error if it's the only range, so this is just pedantics.
301            Ok(u64::MAX)
302        }
303        Err(_) => Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable)),
304    }
305}
306
307#[derive(Debug, Clone, Eq, PartialEq)]
308pub struct ParsedRanges {
309    ranges: Vec<SyntacticallyCorrectRange>,
310}
311
312impl ParsedRanges {
313    fn new(ranges: Vec<SyntacticallyCorrectRange>) -> Self {
314        ParsedRanges { ranges }
315    }
316
317    #[must_use]
318    #[allow(clippy::len_without_is_empty)]
319    pub fn len(&self) -> usize {
320        self.ranges.len()
321    }
322
323    #[must_use]
324    pub fn into_ranges(self) -> Vec<SyntacticallyCorrectRange> {
325        self.ranges
326    }
327
328    /// Validates a parsed range for a given file-size in bytes.
329    /// Can return an empty vector if no ranges are usable (only possible on `file_size_bytes=0`).
330    /// A spec-compliant realistic way of responding in that case is a `200` with an empty body.
331    /// # Errors
332    /// If all ranges are unsatisfiable or the ranges are intolerable
333    pub fn validate(
334        &self,
335        file_size_bytes: u64,
336    ) -> Result<Vec<RangeInclusive<u64>>, RangeUnsatisfiableError> {
337        let len = self.ranges.len();
338        if file_size_bytes == 0 {
339            return match self.zero_len_validate() {
340                Some(e) => Err(e),
341                None => Ok(vec![]),
342            };
343        }
344        let mut validated = Vec::with_capacity(len);
345        let mut bad_range = false;
346        for parsed in &self.ranges {
347            let (start, end) = match *parsed {
348                SyntacticallyCorrectRange::FromStart(i) => {
349                    if i > file_size_bytes.saturating_sub(1) {
350                        bad_range = true;
351                        continue;
352                    }
353                    let end = file_size_bytes.saturating_sub(1);
354                    (i, end)
355                }
356                SyntacticallyCorrectRange::FromLast(i) => {
357                    if i == 0 {
358                        bad_range = true;
359                        continue;
360                    }
361                    // RFC 9110 14.1.2: a suffix longer than the representation selects all of it
362                    (
363                        file_size_bytes.saturating_sub(i),
364                        file_size_bytes.saturating_sub(1),
365                    )
366                }
367                SyntacticallyCorrectRange::Indexed(start, end) => {
368                    let end = core::cmp::min(end, file_size_bytes.saturating_sub(1));
369                    // This is not a range-reversal, that's already checked
370                    if start > end {
371                        bad_range = true;
372                        continue;
373                    }
374                    (start, end)
375                }
376            };
377            let valid = RangeInclusive::new(start, end);
378            validated.push(valid);
379        }
380
381        if validated.is_empty() && bad_range {
382            return Err(RangeUnsatisfiableError::Unsatisfiable);
383        }
384        match validate_ranges(validated.as_slice()) {
385            RangeValidationResult::Valid => Ok(validated),
386            RangeValidationResult::Overlapping => Err(RangeUnsatisfiableError::Rejected(
387                RangeRejected::TooManyOverlapping,
388            )),
389        }
390    }
391
392    #[inline(never)]
393    fn zero_len_validate(&self) -> Option<RangeUnsatisfiableError> {
394        let mut last_error = None;
395        let mut any_passing = false;
396        for parsed in &self.ranges {
397            match *parsed {
398                SyntacticallyCorrectRange::FromStart(_)
399                | SyntacticallyCorrectRange::Indexed(_, _) => {
400                    last_error = Some(RangeUnsatisfiableError::Unsatisfiable);
401                }
402                SyntacticallyCorrectRange::FromLast(i) => {
403                    if i == 0 {
404                        // RFC 9110 14.1.2
405                        // > When a selected representation has zero length,
406                        // > the only satisfiable form of range-spec in a GET request is a
407                        // > suffix-range with a non-zero suffix-length.
408                        // This is unsatisfiable, but not invalid, so it doesn't cause an immediate
409                        // exit. This is mostly pedantics since it's an empty file, any other ranges
410                        // will not produce a particularly interesting result even if we continue.
411                        last_error = Some(RangeUnsatisfiableError::Unsatisfiable);
412                        continue;
413                    }
414                    any_passing = true;
415                }
416            }
417        }
418        match (any_passing, last_error) {
419            (false, Some(e)) => Some(e),
420            _ => None,
421        }
422    }
423}
424
425/// A server MAY always ignore a range header <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.2-3>
426/// which means falling back to ignoring the range header is spec-compliant.
427#[derive(Debug, Copy, Clone, Eq, PartialEq)]
428#[non_exhaustive]
429pub enum RangeUnsatisfiableError {
430    Invalid(RangeInvalid),
431    Rejected(RangeRejected),
432    /// Spec says this SHOULD be a 416 <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.2-13>
433    Unsatisfiable,
434}
435
436/// Unless the variant says otherwise, the spec says implementors
437/// MAY ignore or reject <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.2-6>
438#[derive(Debug, Copy, Clone, Eq, PartialEq)]
439#[non_exhaustive]
440pub enum RangeInvalid {
441    Reversed,
442    IllegalWhitespace,
443    /// This error cannot be disambiguated from certain other malformed starts of ranges.
444    /// Therefore, the only spec-compliant way of handling this error-code is ignoring the range
445    /// <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.2-5>
446    DoesNotStartWithToken,
447    NoNonempty,
448    Unparseable,
449}
450
451/// Rejected because of tolerance, possibly an abusive client.
452#[derive(Debug, Copy, Clone, Eq, PartialEq)]
453#[non_exhaustive]
454pub enum RangeRejected {
455    /// Spec says ignore or reject <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.2-6>.
456    /// Spec says reject with a 416 (if rejecting) <https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.17>
457    TooManyOverlapping,
458    TooManyUnparseable,
459    TooManyDigits,
460    /// Spec says ignore or reject (with a 416) <https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.17>
461    TooMany,
462}
463
464impl Display for RangeUnsatisfiableError {
465    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
466        match self {
467            RangeUnsatisfiableError::Invalid(r) => match r {
468                RangeInvalid::Reversed => f.write_str("range invalid: reversed"),
469                RangeInvalid::IllegalWhitespace => f.write_str("range invalid: illegal whitespace"),
470                RangeInvalid::DoesNotStartWithToken => f.write_fmt(format_args!(
471                    "range invalid: range does not start with token '{UNIT_SEP}'"
472                )),
473                RangeInvalid::NoNonempty => f.write_str("range invalid: no nonempty ranges remain"),
474                RangeInvalid::Unparseable => {
475                    f.write_str("range invalid: provided input did not make sense")
476                }
477            },
478            RangeUnsatisfiableError::Rejected(r) => match r {
479                RangeRejected::TooManyOverlapping => {
480                    f.write_str("range rejected: too many overlapping ranges")
481                }
482                RangeRejected::TooManyUnparseable => {
483                    f.write_str("range rejected: too many unparseable ranges")
484                }
485                RangeRejected::TooMany => f.write_str("range rejected: too many ranges"),
486                RangeRejected::TooManyDigits => {
487                    f.write_str("range rejected: too many characters in the range digit-position")
488                }
489            },
490            RangeUnsatisfiableError::Unsatisfiable => f.write_str("range unsatisfiable"),
491        }
492    }
493}
494
495impl std::error::Error for RangeUnsatisfiableError {}
496
497enum RangeValidationResult {
498    Valid,
499    Overlapping,
500}
501
502fn validate_ranges(ranges: &[RangeInclusive<u64>]) -> RangeValidationResult {
503    let mut sorted = true;
504    let mut prev_start = 0;
505    for range in ranges {
506        let start = *range.start();
507        sorted &= prev_start <= start;
508        prev_start = start;
509    }
510    if ranges.len() == 1 {
511        return RangeValidationResult::Valid;
512    }
513    let bounds = ranges.iter().map(|r| (*r.start(), *r.end()));
514    let too_many = if sorted {
515        has_multiple_overlaps(bounds)
516    } else {
517        let mut by_start: Vec<_> = bounds.collect();
518        by_start.sort_unstable_by_key(|&(start, _)| start);
519        has_multiple_overlaps(by_start.into_iter())
520    };
521    if too_many {
522        RangeValidationResult::Overlapping
523    } else {
524        RangeValidationResult::Valid
525    }
526}
527
528fn has_multiple_overlaps(mut by_start: impl Iterator<Item = (u64, u64)>) -> bool {
529    // MSRV
530    #[allow(clippy::manual_let_else)]
531    let mut max_end = match by_start.next() {
532        Some((_, end)) => end,
533        None => return false,
534    };
535    let mut seen_overlap = false;
536    for (start, end) in by_start {
537        if start <= max_end {
538            if seen_overlap {
539                return true;
540            }
541            seen_overlap = true;
542        }
543        max_end = max_end.max(end);
544    }
545    false
546}
547
548#[cfg(test)]
549mod tests {
550    use crate::{
551        parse_range_header, parse_range_header_with_max_ranges, ParsedRanges, RangeInvalid,
552        RangeRejected, RangeUnsatisfiableError, SyntacticallyCorrectRange,
553    };
554    use core::ops::RangeInclusive;
555    use rand::seq::SliceRandom;
556    use std::fmt::Write;
557    use std::num::NonZeroUsize;
558
559    const TEST_FILE_LENGTH: u64 = 10_000;
560    /// Testing standard range compliance against <https://datatracker.ietf.org/doc/html/rfc7233>
561    #[test]
562    fn rfc_7233_standard_test1() {
563        let input = "bytes=0-499";
564        let expect = SyntacticallyCorrectRange::Indexed(0, 499);
565        let actual = parse_range_header(input).unwrap();
566        assert_eq!(single_range(expect), actual);
567        let expect = RangeInclusive::new(0, 499);
568        let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
569        assert_eq!(expect, actual);
570    }
571
572    #[test]
573    fn rfc_7233_standard_test2() {
574        let input = "bytes=500-999";
575        let expect = SyntacticallyCorrectRange::Indexed(500, 999);
576        let actual = parse_range_header(input).unwrap();
577        assert_eq!(single_range(expect), actual);
578        let expect = RangeInclusive::new(500, 999);
579        let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
580        assert_eq!(expect, actual);
581    }
582
583    /// Testing suffix compliance against <https://datatracker.ietf.org/doc/html/rfc7233>
584    #[test]
585    fn rfc_7233_suffixed_test() {
586        let input = "bytes=-500";
587        let expect = SyntacticallyCorrectRange::FromLast(500);
588        let actual = parse_range_header(input).unwrap();
589        assert_eq!(single_range(expect), actual);
590        let expect = RangeInclusive::new(9500, 9999);
591        let actual = actual.validate(10_000).unwrap()[0].clone();
592        assert_eq!(expect, actual);
593    }
594
595    /// Testing open range compliance against <https://datatracker.ietf.org/doc/html/rfc7233>
596    #[test]
597    fn rfc_7233_open_range_test() {
598        let input = "bytes=9500-";
599        let expect = SyntacticallyCorrectRange::FromStart(9500);
600        let actual = parse_range_header(input).unwrap();
601        assert_eq!(single_range(expect), actual);
602        let expect = RangeInclusive::new(9500, 9999);
603        let actual = actual.validate(10_000).unwrap()[0].clone();
604        assert_eq!(expect, actual);
605    }
606
607    /// Testing first and last bytes compliance against <https://datatracker.ietf.org/doc/html/rfc7233>
608    #[test]
609    fn rfc_7233_first_and_last() {
610        let input = "bytes=0-0, -1";
611        let expect = vec![
612            SyntacticallyCorrectRange::Indexed(0, 0),
613            SyntacticallyCorrectRange::FromLast(1),
614        ];
615        let actual = parse_range_header(input).unwrap();
616        assert_eq!(expect, actual.ranges);
617        let expect = vec![0..=0, 9999..=9999];
618        let actual = actual.validate(10_000).unwrap();
619        assert_eq!(expect, actual);
620    }
621
622    #[test]
623    fn parse_standard_range() {
624        let input = "bytes=0-1023";
625        let expect = SyntacticallyCorrectRange::Indexed(0, 1023);
626        let actual = parse_range_header(input).unwrap();
627        assert_eq!(single_range(expect), actual);
628        let expect = RangeInclusive::new(0, 1023);
629        let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
630        assert_eq!(expect, actual);
631    }
632
633    #[test]
634    fn parse_open_ended_range() {
635        let input = "bytes=0-";
636        let expect = SyntacticallyCorrectRange::FromStart(0);
637        let actual = parse_range_header(input).unwrap();
638        assert_eq!(single_range(expect), actual);
639        let expect = RangeInclusive::new(0, TEST_FILE_LENGTH - 1);
640        let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
641        assert_eq!(expect, actual);
642    }
643
644    #[test]
645    fn parse_suffix_range_edge() {
646        let input = &format!("bytes=-{}", TEST_FILE_LENGTH);
647        let expect = SyntacticallyCorrectRange::FromLast(TEST_FILE_LENGTH);
648        let actual = parse_range_header(input).unwrap();
649        assert_eq!(single_range(expect), actual);
650        let expect = RangeInclusive::new(0, TEST_FILE_LENGTH - 1);
651        let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
652        assert_eq!(expect, actual);
653    }
654
655    #[test]
656    fn parse_empty_as_invalid() {
657        let input = "";
658        let parsed = parse_range_header(input);
659        assert_eq!(
660            parsed,
661            Err(RangeUnsatisfiableError::Invalid(
662                RangeInvalid::DoesNotStartWithToken
663            ))
664        );
665    }
666
667    #[test]
668    fn parse_empty_range_as_invalid() {
669        let input = "bytes=";
670        let parsed = parse_range_header(input);
671        assert_eq!(
672            parsed,
673            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::NoNonempty))
674        );
675    }
676
677    #[test]
678    fn parse_range_starting_with_whitespace_is_valid() {
679        let input = "bytes= 0-15";
680        let parsed = parse_range_header(input);
681        assert_eq!(Ok(vec![0..=15]), parsed.unwrap().validate(TEST_FILE_LENGTH));
682    }
683
684    #[test]
685    fn parse_range_token_starting_with_whitespace_as_invalid() {
686        let input = " bytes=0-15";
687        let parsed = parse_range_header(input);
688        assert_eq!(
689            parsed,
690            Err(RangeUnsatisfiableError::Invalid(
691                RangeInvalid::DoesNotStartWithToken
692            ))
693        );
694    }
695
696    #[test]
697    fn parse_range_strict_parse_numerical() {
698        let input = "bytes=+0-15";
699        let parsed = parse_range_header(input);
700        assert_eq!(
701            parsed,
702            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable))
703        );
704    }
705
706    #[test]
707    fn parse_bad_unit_as_invalid() {
708        let input = "abcde=0-10";
709        let parsed = parse_range_header(input);
710        assert_eq!(
711            parsed,
712            Err(RangeUnsatisfiableError::Invalid(
713                RangeInvalid::DoesNotStartWithToken
714            ))
715        );
716    }
717
718    #[test]
719    fn parse_missing_equals_as_malformed() {
720        let input = "bytes0-10";
721        let parsed = parse_range_header(input);
722        assert_eq!(
723            parsed,
724            Err(RangeUnsatisfiableError::Invalid(
725                RangeInvalid::DoesNotStartWithToken
726            ))
727        );
728    }
729
730    #[test]
731    fn parse_negative_bad_characters_in_range_as_malformed() {
732        let input = "bytes=1-10a";
733        let parsed = parse_range_header(input);
734        assert_eq!(
735            parsed,
736            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable))
737        );
738    }
739
740    #[test]
741    fn parse_negative_numbers_as_malformed() {
742        let input = "bytes=-1-10";
743        let parsed = parse_range_header(input);
744        // Becomes bad eor, since -1 signals suffixed range
745        assert_eq!(
746            parsed,
747            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable))
748        );
749    }
750
751    #[test]
752    fn parse_bad_characters_in_start_of_range() {
753        let input = "bytes=a1-10";
754        let parsed = parse_range_header(input);
755        // Becomes bad eor, since -1 signals suffixed range
756        assert_eq!(
757            parsed,
758            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable))
759        );
760    }
761
762    #[test]
763    fn parse_out_of_bounds_overrun_as_content_length() {
764        let input = &format!("bytes=0-{}", TEST_FILE_LENGTH);
765        let expect = vec![RangeInclusive::new(0, TEST_FILE_LENGTH - 1)];
766        let actual = parse_range_header(input)
767            .unwrap()
768            .validate(TEST_FILE_LENGTH)
769            .unwrap();
770        assert_eq!(expect, actual);
771    }
772
773    #[test]
774    fn parse_suffix_longer_than_file_as_whole_file() {
775        let input = &format!("bytes=-{}", TEST_FILE_LENGTH + 1);
776        let parsed = parse_range_header(input)
777            .unwrap()
778            .validate(TEST_FILE_LENGTH)
779            .unwrap();
780        assert_eq!(parsed, vec![0..=TEST_FILE_LENGTH - 1]);
781    }
782
783    #[test]
784    fn parse_suffix_of_empty_file_as_satisfiable_returns_no_ranges() {
785        let parsed = parse_range_header("bytes=-1").unwrap().validate(0);
786        assert_eq!(parsed, Ok(vec![]));
787    }
788
789    #[test]
790    fn parse_zero_length_suffix_as_unsatisfiable() {
791        let input = "bytes=-0";
792        let parsed = parse_range_header(input).unwrap();
793        assert_eq!(
794            parsed.validate(TEST_FILE_LENGTH),
795            Err(RangeUnsatisfiableError::Unsatisfiable)
796        );
797    }
798
799    #[test]
800    fn parse_zero_length_suffix_retains_satisfiable_ranges() {
801        let input = "bytes=15-20,-0";
802        let parsed = parse_range_header(input).unwrap();
803        assert_eq!(Ok(vec![15..=20]), parsed.validate(TEST_FILE_LENGTH));
804    }
805
806    #[test]
807    fn retains_satisfiable_ranges() {
808        let input = "bytes=10-20,0-0";
809        let parsed = parse_range_header(input).unwrap();
810        assert_eq!(Ok(vec![0..=0]), parsed.validate(10));
811        let parsed = parse_range_header(input).unwrap();
812        assert_eq!(Ok(vec![10..=19, 0..=0]), parsed.validate(20));
813        let parsed = parse_range_header(input).unwrap();
814        assert_eq!(Ok(vec![10..=20, 0..=0]), parsed.validate(21));
815    }
816
817    #[test]
818    fn rejects_invalid_range_even_when_oob() {
819        let input = "bytes=20-10,0-0";
820        assert_eq!(
821            parse_range_header(input),
822            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Reversed)),
823        );
824    }
825
826    #[test]
827    fn retains_satisfiable_ranges_reordered() {
828        let input = "bytes=0-0,10-20";
829        let parsed = parse_range_header(input).unwrap();
830        assert_eq!(Ok(vec![0..=0]), parsed.validate(10));
831        let parsed = parse_range_header(input).unwrap();
832        assert_eq!(Ok(vec![0..=0, 10..=19]), parsed.validate(20));
833        let parsed = parse_range_header(input).unwrap();
834        assert_eq!(Ok(vec![0..=0, 10..=20]), parsed.validate(21));
835    }
836
837    #[test]
838    fn parse_single_reversed_as_invalid() {
839        let input = "bytes=15-0";
840        assert_eq!(
841            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Reversed)),
842            parse_range_header(input),
843        );
844    }
845
846    #[test]
847    fn parse_zero_range_as_invalid() {
848        let input = "bytes=15-";
849        let parsed = parse_range_header(input).unwrap();
850        assert_eq!(
851            parsed.validate(0),
852            Err(RangeUnsatisfiableError::Unsatisfiable)
853        );
854    }
855
856    #[test]
857    fn empty_file_quirks() {
858        let parsed = parse_range_header("bytes=-0").unwrap();
859        assert_eq!(
860            parsed.validate(0),
861            Err(RangeUnsatisfiableError::Unsatisfiable)
862        );
863        let parsed = parse_range_header("bytes=-0,-1").unwrap();
864        assert_eq!(parsed.validate(0), Ok(vec![]));
865    }
866
867    #[test]
868    fn parse_multi_range() {
869        let input = "bytes=0-1023, 2015-3000, 4000-4500, 8000-9999";
870        let expected_ranges = vec![
871            SyntacticallyCorrectRange::Indexed(0, 1023),
872            SyntacticallyCorrectRange::Indexed(2015, 3000),
873            SyntacticallyCorrectRange::Indexed(4000, 4500),
874            SyntacticallyCorrectRange::Indexed(8000, 9999),
875        ];
876        let parsed = parse_range_header(input).unwrap();
877        assert_eq!(expected_ranges, parsed.ranges);
878        let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
879        assert_eq!(
880            vec![0..=1023, 2015..=3000, 4000..=4500, 8000..=9999],
881            validated
882        );
883    }
884
885    #[test]
886    fn parse_multi_range_with_open() {
887        let input = "bytes=0-1023, 1024-";
888        let expected_ranges = vec![
889            SyntacticallyCorrectRange::Indexed(0, 1023),
890            SyntacticallyCorrectRange::FromStart(1024),
891        ];
892        let parsed = parse_range_header(input).unwrap();
893        assert_eq!(expected_ranges, parsed.ranges);
894        let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
895        assert_eq!(vec![0..=1023, 1024..=9999], validated);
896    }
897
898    #[test]
899    fn parse_multi_range_with_suffix() {
900        let input = "bytes=0-1023, -1000";
901        let expected_ranges = vec![
902            SyntacticallyCorrectRange::Indexed(0, 1023),
903            SyntacticallyCorrectRange::FromLast(1000),
904        ];
905        let parsed = parse_range_header(input).unwrap();
906        assert_eq!(expected_ranges, parsed.ranges);
907        let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
908        assert_eq!(vec![0..=1023, 9000..=9999], validated);
909    }
910
911    #[test]
912    fn parse_overlapping_multi_range_as_unsatisfiable_standard() {
913        let prepared = parse_range_header("bytes=500-700,601-999").unwrap();
914        assert_eq!(
915            Ok(vec![500..=700, 601..=999]),
916            prepared.validate(TEST_FILE_LENGTH)
917        );
918        let prepared = parse_range_header("bytes=0-1023, 500-800").unwrap();
919        assert_eq!(
920            Ok(vec![0..=1023, 500..=800]),
921            prepared.validate(TEST_FILE_LENGTH)
922        );
923        let prepared = parse_range_header("bytes=0-0, 0-15").unwrap();
924        assert_eq!(Ok(vec![0..=0, 0..=15]), prepared.validate(TEST_FILE_LENGTH));
925        let prepared = parse_range_header("bytes=0-20, 20-35").unwrap();
926        assert_eq!(
927            Ok(vec![0..=20, 20..=35]),
928            prepared.validate(TEST_FILE_LENGTH)
929        );
930        let input = "bytes=0-1023, 500-800, 450-650";
931        assert_validation_err(
932            input,
933            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
934        );
935        let input = "bytes=0-1023, 500-800, 1000-1001";
936        assert_validation_err(
937            input,
938            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
939        );
940        let input = "bytes=0-1023, 500-800, 1000-";
941        assert_validation_err(
942            input,
943            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
944        );
945        let input = "bytes=0-, 0-0, 0-15";
946        assert_validation_err(
947            input,
948            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
949        );
950        let input = "bytes=0-20, 20-35, 35-";
951        assert_validation_err(
952            input,
953            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
954        );
955    }
956
957    #[test]
958    fn parse_overlapping_multi_range_as_unsatisfiable_open() {
959        let input = "bytes=0-, 1-, 5000-6000";
960        assert_validation_err(
961            input,
962            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
963        );
964        let parsed = parse_range_header("bytes=0-, 5000-6000").unwrap();
965        assert_eq!(
966            Ok(vec![0..=TEST_FILE_LENGTH - 1, 5000..=6000]),
967            parsed.validate(TEST_FILE_LENGTH)
968        );
969    }
970
971    #[test]
972    fn parse_overlapping_multi_range_as_unsatisfiable_suffixed() {
973        let parsed = parse_range_header("bytes=8000-9000, -1001").unwrap();
974        assert_eq!(
975            Ok(vec![8000..=9000, 8999..=TEST_FILE_LENGTH - 1]),
976            parsed.validate(TEST_FILE_LENGTH)
977        );
978        let parsed = parse_range_header("bytes=8000-9000, -1000").unwrap();
979        assert_eq!(
980            Ok(vec![8000..=9000, 9000..=TEST_FILE_LENGTH - 1]),
981            parsed.validate(TEST_FILE_LENGTH)
982        );
983        let input = "bytes=8000-9000, -1001, -500";
984        assert_validation_err(
985            input,
986            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
987        );
988        let input = "bytes=8000-9000, -1000, -400";
989        assert_validation_err(
990            input,
991            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
992        );
993    }
994
995    #[test]
996    fn parse_overlapping_multi_range_as_unsatisfiable_suffixed_open() {
997        let parsed = parse_range_header("bytes=0-, -1").unwrap();
998        assert_eq!(
999            Ok(vec![
1000                0..=TEST_FILE_LENGTH - 1,
1001                TEST_FILE_LENGTH - 1..=TEST_FILE_LENGTH - 1
1002            ]),
1003            parsed.validate(TEST_FILE_LENGTH)
1004        );
1005        let input = "bytes=0-, 5-6, -1";
1006        assert_validation_err(
1007            input,
1008            RangeUnsatisfiableError::Rejected(RangeRejected::TooManyOverlapping),
1009        );
1010    }
1011
1012    #[test]
1013    fn parse_start_boundaries() {
1014        let parsed = parse_range_header("bytes=9999-").unwrap();
1015        assert_eq!(Ok(vec![9999..=9999]), parsed.validate(TEST_FILE_LENGTH));
1016        let parsed = parse_range_header("bytes=10000-").unwrap();
1017        assert_eq!(
1018            Err(RangeUnsatisfiableError::Unsatisfiable),
1019            parsed.validate(TEST_FILE_LENGTH)
1020        );
1021    }
1022
1023    #[test]
1024    fn parse_multi_range_with_a_reversed_as_invalid() {
1025        let input = "bytes=0-15, 30-20";
1026        assert_eq!(
1027            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Reversed)),
1028            parse_range_header(input)
1029        );
1030    }
1031
1032    #[track_caller]
1033    fn assert_validation_err(input: &str, err: RangeUnsatisfiableError) {
1034        let parsed = parse_range_header(input)
1035            .unwrap()
1036            .validate(TEST_FILE_LENGTH);
1037        assert_eq!(Err(err), parsed);
1038    }
1039
1040    #[test]
1041    fn parse_multi_range_rejects_invalid() {
1042        let input = "bytes=0-15, 25, 9, ";
1043        let parsed = parse_range_header(input);
1044        assert!(parsed.is_err());
1045    }
1046
1047    #[quickcheck_macros::quickcheck]
1048    #[allow(clippy::needless_pass_by_value)]
1049    fn never_panics(input: String) -> quickcheck::TestResult {
1050        if let Ok(passed_first_pass) = parse_range_header(&input) {
1051            let _v = passed_first_pass.validate(u64::MAX);
1052            quickcheck::TestResult::passed()
1053        } else {
1054            quickcheck::TestResult::passed()
1055        }
1056    }
1057
1058    #[quickcheck_macros::quickcheck]
1059    #[allow(clippy::needless_pass_by_value)]
1060    fn never_panics_empty_validate(input: String) -> quickcheck::TestResult {
1061        if let Ok(passed_first_pass) = parse_range_header(&input) {
1062            let _v = passed_first_pass.validate(0);
1063            quickcheck::TestResult::passed()
1064        } else {
1065            quickcheck::TestResult::passed()
1066        }
1067    }
1068
1069    #[test]
1070    #[allow(clippy::cast_possible_truncation)]
1071    fn zero_length_file_does_not_return_an_unusable_range_on_suffixed() {
1072        const BYTES: &[u8; 0] = &[];
1073        let parsed = parse_range_header("bytes=-1").unwrap();
1074        let val = parsed.validate(0).unwrap();
1075        for range in val {
1076            for val in range {
1077                let value = BYTES[val as usize];
1078                // This is just a dummy value, the array isn't indexable and should explode
1079                assert_eq!(15, value);
1080            }
1081        }
1082    }
1083
1084    #[test]
1085    fn zero_length_file_does_not_accept_any_staring_range() {
1086        let parsed = parse_range_header("bytes=0-").unwrap();
1087        assert_eq!(
1088            Err(RangeUnsatisfiableError::Unsatisfiable),
1089            parsed.validate(0)
1090        );
1091        let parsed = parse_range_header("bytes=0-0").unwrap();
1092        assert_eq!(
1093            Err(RangeUnsatisfiableError::Unsatisfiable),
1094            parsed.validate(0)
1095        );
1096        let parsed = parse_range_header("bytes=0-100").unwrap();
1097        assert_eq!(
1098            Err(RangeUnsatisfiableError::Unsatisfiable),
1099            parsed.validate(0)
1100        );
1101    }
1102
1103    #[test]
1104    fn zero_length_file_will_not_give_an_error_if_any_range_is_satisfiable() {
1105        // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-12
1106        let parsed = parse_range_header("bytes=0-0,-1").unwrap();
1107        assert_eq!(Ok(vec![]), parsed.validate(0));
1108        let parsed = parse_range_header("bytes=1-,-1").unwrap();
1109        assert_eq!(Ok(vec![]), parsed.validate(0));
1110    }
1111
1112    #[test]
1113    fn zero_length_file_will_not_accept_reversed_ranges() {
1114        assert_eq!(
1115            parse_range_header("bytes=15-0,-1"),
1116            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Reversed)),
1117        );
1118    }
1119
1120    #[test]
1121    fn accepts_any_case() {
1122        let _parsed = parse_range_header("bytes=0-0").unwrap();
1123        let _parsed = parse_range_header("Bytes=0-0").unwrap();
1124        let _parsed = parse_range_header("ByTeS=0-0").unwrap();
1125    }
1126
1127    #[test]
1128    fn accepts_leading_zeroes() {
1129        // https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.1
1130        // Spec only says one or more digits
1131        let parsed = parse_range_header("bytes=1-2").unwrap();
1132        assert_eq!(Ok(vec![1..=2]), parsed.validate(TEST_FILE_LENGTH));
1133        let parsed = parse_range_header("bytes=01-2").unwrap();
1134        assert_eq!(Ok(vec![1..=2]), parsed.validate(TEST_FILE_LENGTH));
1135        let parsed = parse_range_header("bytes=001-2").unwrap();
1136        assert_eq!(Ok(vec![1..=2]), parsed.validate(TEST_FILE_LENGTH));
1137        let parsed = parse_range_header("bytes=00001-2").unwrap();
1138        assert_eq!(Ok(vec![1..=2]), parsed.validate(TEST_FILE_LENGTH));
1139    }
1140
1141    #[test]
1142    fn accepts_whitespace_around_separators() {
1143        assert_eq!(
1144            Err(RangeUnsatisfiableError::Invalid(
1145                RangeInvalid::IllegalWhitespace
1146            )),
1147            parse_range_header("bytes=1-2 ")
1148        );
1149        parse_range_header("bytes= , 0-1,3-4").unwrap();
1150        parse_range_header("bytes=0-1,3-4").unwrap();
1151        parse_range_header("bytes=0-1, 3-4").unwrap();
1152        parse_range_header("bytes=0-1,  3-4").unwrap();
1153        parse_range_header("bytes=0-1 , 3-4").unwrap();
1154        parse_range_header("bytes=0-1  ,3-4").unwrap();
1155        parse_range_header("bytes=0-1,\t3-4").unwrap();
1156        parse_range_header("bytes=0-1,\t\t3-4").unwrap();
1157        parse_range_header("bytes=0-1 ,\t3-4").unwrap();
1158        parse_range_header("bytes=0-1 \t,3-4").unwrap();
1159        assert_eq!(
1160            Err(RangeUnsatisfiableError::Invalid(
1161                RangeInvalid::IllegalWhitespace
1162            )),
1163            parse_range_header("bytes=0-1,\n3-4")
1164        );
1165        assert_eq!(
1166            Err(RangeUnsatisfiableError::Invalid(
1167                RangeInvalid::IllegalWhitespace
1168            )),
1169            parse_range_header("bytes=0-1,\n 3-4")
1170        );
1171        assert_eq!(
1172            Err(RangeUnsatisfiableError::Invalid(
1173                RangeInvalid::IllegalWhitespace
1174            )),
1175            parse_range_header("bytes=0-1\n, 3-4")
1176        );
1177        assert_eq!(
1178            Err(RangeUnsatisfiableError::Invalid(
1179                RangeInvalid::IllegalWhitespace
1180            )),
1181            parse_range_header("bytes=0-1,3-4\n")
1182        );
1183    }
1184
1185    #[test]
1186    fn rejects_long_number_even_if_its_not_a_number() {
1187        // Guards against an infinite `0`'s string
1188        let expect = ParsedRanges {
1189            ranges: vec![SyntacticallyCorrectRange::Indexed(0, 15)],
1190        };
1191        assert_eq!(
1192            Ok(expect),
1193            parse_range_header("bytes=0-000000000000000000000000000015")
1194        );
1195        assert_eq!(
1196            Err(RangeUnsatisfiableError::Rejected(
1197                RangeRejected::TooManyDigits
1198            )),
1199            parse_range_header("bytes=0-0000000000000000000000000000015")
1200        );
1201        assert_eq!(
1202            Err(RangeUnsatisfiableError::Rejected(
1203                RangeRejected::TooManyDigits
1204            )),
1205            parse_range_header("bytes=0-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")
1206        );
1207    }
1208
1209    #[test]
1210    fn accepts_max_u64_value() {
1211        let parsed = parse_range_header("bytes=0-18446744073709551615").unwrap();
1212        assert_eq!(
1213            Ok(vec![0..=TEST_FILE_LENGTH.saturating_sub(1)]),
1214            parsed.validate(TEST_FILE_LENGTH)
1215        );
1216    }
1217    #[test]
1218    fn does_not_accept_a_completely_empty_range() {
1219        assert_eq!(
1220            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::NoNonempty)),
1221            parse_range_header("bytes=")
1222        );
1223    }
1224
1225    #[test]
1226    fn accepts_a_few_empty_elements() {
1227        let parsed = parse_range_header("bytes=0-1,,3-4,").unwrap();
1228        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1229        let parsed = parse_range_header("bytes=,0-1,,3-4").unwrap();
1230        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1231        let parsed = parse_range_header("bytes=0-1,,3-4").unwrap();
1232        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1233        let parsed = parse_range_header("bytes=0-1 , , 3-4").unwrap();
1234        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1235        let parsed = parse_range_header("bytes=0-1 ,, , 3-4").unwrap();
1236        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1237        let parsed = parse_range_header("bytes=0-1 , , , 3-4").unwrap();
1238        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1239        let parsed = parse_range_header("bytes=0-1 ,, ,, 3-4").unwrap();
1240        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1241        let parsed = parse_range_header("bytes=0-1 ,,,,, 3-4").unwrap();
1242        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1243
1244        let parsed = parse_range_header("bytes=0-1,,,,,,,,,,,,,,,3-4").unwrap();
1245        assert_eq!(Ok(vec![0..=1, 3..=4]), parsed.validate(TEST_FILE_LENGTH));
1246        assert_eq!(
1247            Err(RangeUnsatisfiableError::Rejected(
1248                RangeRejected::TooManyUnparseable
1249            )),
1250            parse_range_header("bytes=0-1,,,,,,,,,,,,,,,,3-4")
1251        );
1252    }
1253
1254    #[test]
1255    fn regression_empty_err_on_only_comma() {
1256        let parsed = parse_range_header("bytes=,,");
1257        assert_eq!(
1258            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::NoNonempty)),
1259            parsed
1260        );
1261        let parsed = parse_range_header("bytes=,  ,");
1262        assert_eq!(
1263            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::NoNonempty)),
1264            parsed
1265        );
1266    }
1267
1268    #[test]
1269    fn regression_disallowed_valid_white_spacing_allowed_invalid_white_spacing() {
1270        let _parsed = parse_range_header("bytes=0-1 ,").unwrap();
1271        // No trailing whitespace
1272        let parsed = parse_range_header("bytes=0-1 ");
1273        assert_eq!(
1274            Err(RangeUnsatisfiableError::Invalid(
1275                RangeInvalid::IllegalWhitespace
1276            )),
1277            parsed
1278        );
1279        // An empty group is allowed
1280        let _parsed = parse_range_header("bytes=0-1, ").unwrap();
1281    }
1282
1283    #[test]
1284    fn regression_u64_overflow_clamps_to_end_of_file() {
1285        let parsed = parse_range_header("bytes=0-18446744073709551615000000000").unwrap();
1286        assert_eq!(Ok(vec![0..=9999]), parsed.validate(TEST_FILE_LENGTH));
1287        let parsed = parse_range_header("bytes=-18446744073709551615000000000").unwrap();
1288        assert_eq!(Ok(vec![0..=9999]), parsed.validate(TEST_FILE_LENGTH));
1289        let parsed =
1290            parse_range_header("bytes=18446744073709551615000000000-18446744073709551615000000000")
1291                .unwrap();
1292        assert_eq!(
1293            Err(RangeUnsatisfiableError::Unsatisfiable),
1294            parsed.validate(TEST_FILE_LENGTH)
1295        );
1296    }
1297
1298    #[test]
1299    fn regression_u64_parsing_allows_garbage() {
1300        assert_eq!(
1301            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable)),
1302            parse_range_header("bytes=0-99999999999999999999abc")
1303        );
1304        assert_eq!(
1305            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable)),
1306            parse_range_header("bytes=0-99999999999999999999-5")
1307        );
1308        assert_eq!(
1309            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable)),
1310            parse_range_header("bytes=0-99999999999999999999 5")
1311        );
1312        assert_eq!(
1313            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable)),
1314            parse_range_header("bytes=-99999999999999999999abc")
1315        );
1316        assert_eq!(
1317            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable)),
1318            parse_range_header("bytes=99999999999999999999abc-,0-1")
1319        );
1320        assert_eq!(
1321            Err(RangeUnsatisfiableError::Invalid(RangeInvalid::Unparseable)),
1322            parse_range_header("bytes=0-18446744073709551615x")
1323        );
1324    }
1325
1326    #[test]
1327    fn regression_char_boundary_check() {
1328        assert_eq!(
1329            Err(RangeUnsatisfiableError::Invalid(
1330                RangeInvalid::DoesNotStartWithToken
1331            )),
1332            parse_range_header("bytesé=0-1")
1333        );
1334    }
1335
1336    #[test]
1337    fn spec_contradiction() {
1338        // The spec gives this as an example:
1339        // <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2-9.3.1>
1340        // But in the definition <https://www.rfc-editor.org/rfc/rfc9110.html#section-5.6.1.2-2>
1341        // it does not define optional leading whitespace.
1342        // Edit: Errata here, this should pass
1343        // <https://errata.rfc-editor.org/eid7306/>
1344        assert_eq!(
1345            Ok(vec![0..=999, 4500..=5499, 9000..=9999]),
1346            parse_range_header("bytes= 0-999, 4500-5499, -1000")
1347                .unwrap()
1348                .validate(TEST_FILE_LENGTH)
1349        );
1350    }
1351
1352    #[test]
1353    fn default_100_ranges() {
1354        let mut spec = "bytes=".to_string();
1355        let cases = 200;
1356        for i in (0..cases).step_by(2) {
1357            let start = i;
1358            let end = i + 1;
1359            spec.write_fmt(format_args!("{}-{},", start, end)).unwrap();
1360        }
1361        spec.pop();
1362        let parsed = parse_range_header(&spec).unwrap();
1363        parsed.validate(TEST_FILE_LENGTH).unwrap();
1364        let mut spec = "bytes=".to_string();
1365        let cases = 200;
1366        for i in (0..cases).rev().step_by(2) {
1367            let start = i;
1368            let end = i + 1;
1369            spec.write_fmt(format_args!("{}-{},", start, end)).unwrap();
1370        }
1371        spec.pop();
1372        let parsed = parse_range_header(&spec).unwrap();
1373        parsed.validate(TEST_FILE_LENGTH).unwrap();
1374        let mut spec = "bytes=".to_string();
1375        let cases = 201;
1376        for i in (0..cases).rev().step_by(2) {
1377            let start = i;
1378            let end = i + 1;
1379            spec.write_fmt(format_args!("{}-{},", start, end)).unwrap();
1380        }
1381        spec.pop();
1382
1383        assert_eq!(
1384            Err(RangeUnsatisfiableError::Rejected(RangeRejected::TooMany)),
1385            parse_range_header(&spec)
1386        );
1387    }
1388
1389    #[test]
1390    fn random_ranges_overlap_check() {
1391        // These ranges don't overlap, and shouldn't in any order
1392        let mut ranges = Vec::with_capacity(100);
1393        let cases = 200;
1394        for i in (0..cases).rev().step_by(2) {
1395            let start = i;
1396            let end = i + 1;
1397            ranges.push(format!("{}-{},", start, end));
1398        }
1399        let mut rng = rand::rng();
1400        ranges.shuffle(&mut rng);
1401        let mut spec = "bytes=".to_string();
1402        for val in ranges {
1403            spec.push_str(&val);
1404        }
1405        spec.pop();
1406        let parsed = parse_range_header(&spec).unwrap();
1407        parsed.validate(TEST_FILE_LENGTH).unwrap();
1408    }
1409
1410    #[test]
1411    fn configurable_ranges() {
1412        assert!(
1413            parse_range_header_with_max_ranges("bytes=1-2", NonZeroUsize::new(1).unwrap()).is_ok()
1414        );
1415        assert_eq!(
1416            Err(RangeUnsatisfiableError::Rejected(RangeRejected::TooMany)),
1417            parse_range_header_with_max_ranges("bytes=1-2,2-3", NonZeroUsize::new(1).unwrap())
1418        );
1419    }
1420
1421    fn single_range(syntactically_correct: SyntacticallyCorrectRange) -> ParsedRanges {
1422        ParsedRanges::new(vec![syntactically_correct])
1423    }
1424}