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