1#![warn(clippy::pedantic)]
2#![allow(clippy::uninlined_format_args)]
3use core::fmt::{Debug, Display, Formatter};
4use core::ops::RangeInclusive;
5
6const UNIT_SEP: &str = "bytes=";
7pub fn parse_range_header(
120 range_header_value: &str,
121) -> Result<ParsedRanges, RangeUnsatisfiableError> {
122 const COMMA: char = ',';
123 if let Some((prefix, indicated_range)) = range_header_value.split_once(UNIT_SEP) {
124 if indicated_range.starts_with(char::is_whitespace) {
125 return Err(RangeUnsatisfiableError::StartsWithWhitespace);
126 }
127 if !prefix.is_empty() {
128 return Err(RangeUnsatisfiableError::DoesNotStartWithToken);
129 }
130 let mut last_err = None;
131 let ranges = indicated_range
132 .split(COMMA)
133 .filter_map(|range| {
134 if let Some(trimmed) = trim(range) {
135 match parse_inner(trimmed) {
136 Ok(parsed) => Some(parsed),
137 Err(e) => {
138 last_err = Some(e);
139 None
140 }
141 }
142 } else {
143 last_err = Some(RangeUnsatisfiableError::IllegalWhitespace);
144 None
145 }
146 })
147 .collect::<Vec<SyntacticallyCorrectRange>>();
148 if let Some(last_err) = last_err {
149 return Err(last_err);
150 }
151 if ranges.is_empty() {
152 Err(RangeUnsatisfiableError::Empty)
154 } else {
155 Ok(ParsedRanges::new(ranges))
156 }
157 } else {
158 Err(RangeUnsatisfiableError::DoesNotStartWithToken)
159 }
160}
161
162fn trim(s: &str) -> Option<&str> {
163 if s.ends_with(char::is_whitespace) || s.match_indices(char::is_whitespace).count() > 1 {
164 None
165 } else {
166 Some(s.trim())
167 }
168}
169
170#[inline]
171fn parse_inner(range: &str) -> Result<SyntacticallyCorrectRange, RangeUnsatisfiableError> {
172 if let Some((start, end)) = range.split_once('-') {
173 if start.is_empty() {
174 if let Some(end) = strict_parse_u64(end) {
175 if end == 0 {
176 return Err(RangeUnsatisfiableError::ZeroSuffix);
177 }
178 return Ok(SyntacticallyCorrectRange::new(
179 StartPosition::FromLast(end),
180 EndPosition::LastByte,
181 ));
182 }
183 return Err(RangeUnsatisfiableError::BadEndOfRange);
184 }
185 if let Some(start) = strict_parse_u64(start) {
186 if end.is_empty() {
187 return Ok(SyntacticallyCorrectRange::new(
188 StartPosition::Index(start),
189 EndPosition::LastByte,
190 ));
191 }
192 if let Some(end) = strict_parse_u64(end) {
193 return Ok(SyntacticallyCorrectRange::new(
194 StartPosition::Index(start),
195 EndPosition::Index(end),
196 ));
197 }
198 return Err(RangeUnsatisfiableError::BadEndOfRange);
199 }
200 return Err(RangeUnsatisfiableError::BadStartOfRange);
201 }
202 Err(RangeUnsatisfiableError::UnexpectedNumberOfDashes)
203}
204
205fn strict_parse_u64(s: &str) -> Option<u64> {
206 if !s.starts_with('+') && (s.len() == 1 || !s.starts_with('0')) {
207 return s.parse::<u64>().ok();
208 }
209 None
210}
211
212#[derive(Debug, Clone, Eq, PartialEq)]
213pub struct ParsedRanges {
214 pub ranges: Vec<SyntacticallyCorrectRange>,
215}
216
217impl ParsedRanges {
218 fn new(ranges: Vec<SyntacticallyCorrectRange>) -> Self {
219 ParsedRanges { ranges }
220 }
221
222 pub fn validate(
226 &self,
227 file_size_bytes: u64,
228 ) -> Result<Vec<RangeInclusive<u64>>, RangeUnsatisfiableError> {
229 let len = self.ranges.len();
230 let mut validated = Vec::with_capacity(len);
231 for parsed in &self.ranges {
232 let start = match parsed.start {
233 StartPosition::Index(i) => i,
234 StartPosition::FromLast(i) => {
235 if i > file_size_bytes {
236 return Err(RangeUnsatisfiableError::FileSuffixOutOfBounds);
237 }
238 file_size_bytes.saturating_sub(i)
239 }
240 };
241 let end = match parsed.end {
242 EndPosition::Index(i) => core::cmp::min(i, file_size_bytes.saturating_sub(1)),
243 EndPosition::LastByte => file_size_bytes.saturating_sub(1),
244 };
245
246 let valid = RangeInclusive::new(start, end);
247 validated.push(valid);
248 }
249 #[allow(clippy::match_same_arms)]
251 match validate_ranges(validated.as_slice()) {
252 RangeValidationResult::Valid => Ok(validated),
253 RangeValidationResult::Overlapping => Err(RangeUnsatisfiableError::OverlappingRanges),
254 RangeValidationResult::Reversed => Err(RangeUnsatisfiableError::RangeReversed),
255 }
256 }
257}
258
259#[derive(Debug, Copy, Clone, Eq, PartialEq)]
260pub enum RangeUnsatisfiableError {
261 OverlappingRanges,
262 RangeReversed,
263 FileSuffixOutOfBounds,
264 IllegalWhitespace,
265 StartsWithWhitespace,
266 DoesNotStartWithToken,
267 ZeroSuffix,
268 BadStartOfRange,
269 BadEndOfRange,
270 UnexpectedNumberOfDashes,
271 Empty,
272}
273
274impl Display for RangeUnsatisfiableError {
275 fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
276 match self {
277 RangeUnsatisfiableError::OverlappingRanges => {
278 f.write_str("RangeUnsatisfiable: Ranges overlap")
279 }
280 RangeUnsatisfiableError::RangeReversed => {
281 f.write_str("RangeUnsatisfiable: Reversed range")
282 }
283 RangeUnsatisfiableError::FileSuffixOutOfBounds => f.write_str(
284 "RangeUnsatisfiable: File suffix out of bounds (larger than file bytes)",
285 ),
286 RangeUnsatisfiableError::IllegalWhitespace => {
287 f.write_str("RangeUnsatisfiable: Illegal whitespaces in range")
288 }
289 RangeUnsatisfiableError::StartsWithWhitespace => {
290 f.write_str("RangeUnsatisfiable: Range starts with whitespace")
291 }
292 RangeUnsatisfiableError::DoesNotStartWithToken => f.write_fmt(format_args!(
293 "RangeUnsatisfiable: Range does not start with token '{UNIT_SEP}'"
294 )),
295 RangeUnsatisfiableError::ZeroSuffix => {
296 f.write_str("RangeUnsatisfiable: Range ends at 0")
297 }
298 RangeUnsatisfiableError::BadStartOfRange => {
299 f.write_str("RangeUnsatisfiable: Unparseable start of range")
300 }
301 RangeUnsatisfiableError::BadEndOfRange => {
302 f.write_str("RangeUnsatisfiable: Unparseable end of range")
303 }
304 RangeUnsatisfiableError::UnexpectedNumberOfDashes => {
305 f.write_str("RangeUnsatisfiable: Unexpected number of dashes")
306 }
307 RangeUnsatisfiableError::Empty => f.write_str(
308 "RangeUnsatisfiable: Failed to parse range fallback error, please file an issue",
309 ),
310 }
311 }
312}
313
314impl std::error::Error for RangeUnsatisfiableError {}
315
316enum RangeValidationResult {
317 Valid,
318 Overlapping,
319 Reversed,
320}
321
322fn validate_ranges(ranges: &[RangeInclusive<u64>]) -> RangeValidationResult {
323 let mut bounds = Vec::new();
324 for range in ranges {
325 let start = range.start();
326 let end = range.end();
327 if start > end {
328 return RangeValidationResult::Reversed;
329 } else if ranges.len() == 1 {
330 return RangeValidationResult::Valid;
331 }
332 bounds.push((range.start(), range.end()));
333 }
334 for i in 0..bounds.len() {
335 for j in i + 1..bounds.len() {
336 if bounds[i].0 <= bounds[j].1 && bounds[j].0 <= bounds[i].1 {
337 return RangeValidationResult::Overlapping;
338 }
339 }
340 }
341 RangeValidationResult::Valid
342}
343
344#[derive(Debug, Copy, Clone, Eq, PartialEq)]
345pub struct SyntacticallyCorrectRange {
346 pub start: StartPosition,
347 pub end: EndPosition,
348}
349
350impl SyntacticallyCorrectRange {
351 fn new(start: StartPosition, end: EndPosition) -> Self {
352 SyntacticallyCorrectRange { start, end }
353 }
354}
355
356#[derive(Debug, Copy, Clone, Eq, PartialEq)]
357pub enum StartPosition {
358 Index(u64),
359 FromLast(u64),
360}
361
362#[derive(Debug, Copy, Clone, Eq, PartialEq)]
363pub enum EndPosition {
364 Index(u64),
365 LastByte,
366}
367
368#[cfg(test)]
369mod tests {
370 use crate::{
371 parse_range_header, EndPosition, ParsedRanges, RangeUnsatisfiableError, StartPosition,
372 SyntacticallyCorrectRange,
373 };
374 use core::ops::RangeInclusive;
375
376 const TEST_FILE_LENGTH: u64 = 10_000;
377 #[test]
379 fn rfc_7233_standard_test1() {
380 let input = "bytes=0-499";
381 let expect =
382 SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(499));
383 let actual = parse_range_header(input).unwrap();
384 assert_eq!(single_range(expect), actual);
385 let expect = RangeInclusive::new(0, 499);
386 let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
387 assert_eq!(expect, actual);
388 }
389
390 #[test]
391 fn rfc_7233_standard_test2() {
392 let input = "bytes=500-999";
393 let expect =
394 SyntacticallyCorrectRange::new(StartPosition::Index(500), EndPosition::Index(999));
395 let actual = parse_range_header(input).unwrap();
396 assert_eq!(single_range(expect), actual);
397 let expect = RangeInclusive::new(500, 999);
398 let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
399 assert_eq!(expect, actual);
400 }
401
402 #[test]
404 fn rfc_7233_suffixed_test() {
405 let input = "bytes=-500";
406 let expect =
407 SyntacticallyCorrectRange::new(StartPosition::FromLast(500), EndPosition::LastByte);
408 let actual = parse_range_header(input).unwrap();
409 assert_eq!(single_range(expect), actual);
410 let expect = RangeInclusive::new(9500, 9999);
411 let actual = actual.validate(10_000).unwrap()[0].clone();
412 assert_eq!(expect, actual);
413 }
414
415 #[test]
417 fn rfc_7233_open_range_test() {
418 let input = "bytes=9500-";
419 let expect =
420 SyntacticallyCorrectRange::new(StartPosition::Index(9500), EndPosition::LastByte);
421 let actual = parse_range_header(input).unwrap();
422 assert_eq!(single_range(expect), actual);
423 let expect = RangeInclusive::new(9500, 9999);
424 let actual = actual.validate(10_000).unwrap()[0].clone();
425 assert_eq!(expect, actual);
426 }
427
428 #[test]
430 fn rfc_7233_first_and_last() {
431 let input = "bytes=0-0, -1";
432 let expect = vec![
433 SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(0)),
434 SyntacticallyCorrectRange::new(StartPosition::FromLast(1), EndPosition::LastByte),
435 ];
436 let actual = parse_range_header(input).unwrap();
437 assert_eq!(expect, actual.ranges);
438 let expect = vec![0..=0, 9999..=9999];
439 let actual = actual.validate(10_000).unwrap();
440 assert_eq!(expect, actual);
441 }
442
443 #[test]
444 fn parse_standard_range() {
445 let input = "bytes=0-1023";
446 let expect =
447 SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(1023));
448 let actual = parse_range_header(input).unwrap();
449 assert_eq!(single_range(expect), actual);
450 let expect = RangeInclusive::new(0, 1023);
451 let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
452 assert_eq!(expect, actual);
453 }
454
455 #[test]
456 fn parse_open_ended_range() {
457 let input = "bytes=0-";
458 let expect = SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::LastByte);
459 let actual = parse_range_header(input).unwrap();
460 assert_eq!(single_range(expect), actual);
461 let expect = RangeInclusive::new(0, TEST_FILE_LENGTH - 1);
462 let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
463 assert_eq!(expect, actual);
464 }
465
466 #[test]
467 fn parse_suffix_range_edge() {
468 let input = &format!("bytes=-{}", TEST_FILE_LENGTH);
469 let expect = SyntacticallyCorrectRange::new(
470 StartPosition::FromLast(TEST_FILE_LENGTH),
471 EndPosition::LastByte,
472 );
473 let actual = parse_range_header(input).unwrap();
474 assert_eq!(single_range(expect), actual);
475 let expect = RangeInclusive::new(0, TEST_FILE_LENGTH - 1);
476 let actual = actual.validate(TEST_FILE_LENGTH).unwrap()[0].clone();
477 assert_eq!(expect, actual);
478 }
479
480 #[test]
481 fn parse_empty_as_invalid() {
482 let input = "";
483 let parsed = parse_range_header(input);
484 assert_eq!(parsed, Err(RangeUnsatisfiableError::DoesNotStartWithToken));
485 }
486
487 #[test]
488 fn parse_empty_range_as_invalid() {
489 let input = "bytes=";
490 let parsed = parse_range_header(input);
491 assert_eq!(
493 parsed,
494 Err(RangeUnsatisfiableError::UnexpectedNumberOfDashes)
495 );
496 }
497
498 #[test]
499 fn parse_range_starting_with_whitespace_as_invalid() {
500 let input = "bytes= 0-15";
501 let parsed = parse_range_header(input);
502 assert_eq!(parsed, Err(RangeUnsatisfiableError::StartsWithWhitespace));
504 }
505
506 #[test]
507 fn parse_range_token_starting_with_whitespace_as_invalid() {
508 let input = " bytes=0-15";
509 let parsed = parse_range_header(input);
510 assert_eq!(parsed, Err(RangeUnsatisfiableError::DoesNotStartWithToken));
512 }
513
514 #[test]
515 fn parse_range_strict_parse_numerical() {
516 let input = "bytes=+0-15";
517 let parsed = parse_range_header(input);
518 assert_eq!(parsed, Err(RangeUnsatisfiableError::BadStartOfRange));
520 }
521
522 #[test]
523 fn parse_bad_unit_as_invalid() {
524 let input = "abcde=0-10";
525 let parsed = parse_range_header(input);
526 assert_eq!(parsed, Err(RangeUnsatisfiableError::DoesNotStartWithToken));
527 }
528
529 #[test]
530 fn parse_missing_equals_as_malformed() {
531 let input = "bytes0-10";
532 let parsed = parse_range_header(input);
533 assert_eq!(parsed, Err(RangeUnsatisfiableError::DoesNotStartWithToken));
534 }
535
536 #[test]
537 fn parse_negative_bad_characters_in_range_as_malformed() {
538 let input = "bytes=1-10a";
539 let parsed = parse_range_header(input);
540 assert_eq!(parsed, Err(RangeUnsatisfiableError::BadEndOfRange));
541 }
542
543 #[test]
544 fn parse_negative_numbers_as_malformed() {
545 let input = "bytes=-1-10";
546 let parsed = parse_range_header(input);
547 assert_eq!(parsed, Err(RangeUnsatisfiableError::BadEndOfRange));
549 }
550
551 #[test]
552 fn parse_bad_characters_in_start_of_range() {
553 let input = "bytes=a1-10";
554 let parsed = parse_range_header(input);
555 assert_eq!(parsed, Err(RangeUnsatisfiableError::BadStartOfRange));
557 }
558
559 #[test]
560 fn parse_out_of_bounds_overrun_as_content_length() {
561 let input = &format!("bytes=0-{}", TEST_FILE_LENGTH);
562 let expect = vec![RangeInclusive::new(0, TEST_FILE_LENGTH - 1)];
563 let actual = parse_range_header(input)
564 .unwrap()
565 .validate(TEST_FILE_LENGTH)
566 .unwrap();
567 assert_eq!(expect, actual);
568 }
569
570 #[test]
571 fn parse_out_of_bounds_suffix_overrun_as_unsatisfiable() {
572 let input = &format!("bytes=-{}", TEST_FILE_LENGTH + 1);
573 let parsed = parse_range_header(input)
574 .unwrap()
575 .validate(TEST_FILE_LENGTH);
576 assert_eq!(parsed, Err(RangeUnsatisfiableError::FileSuffixOutOfBounds));
577 }
578
579 #[test]
580 fn parse_zero_length_suffix_as_unsatisfiable() {
581 let input = "bytes=-0";
582 let parsed = parse_range_header(input);
583 assert_eq!(parsed, Err(RangeUnsatisfiableError::ZeroSuffix));
584 }
585
586 #[test]
587 fn parse_single_reversed_as_invalid() {
588 let input = "bytes=15-0";
589 let parsed = parse_range_header(input).unwrap();
590 assert_eq!(
591 parsed.validate(TEST_FILE_LENGTH),
592 Err(RangeUnsatisfiableError::RangeReversed)
593 );
594 }
595
596 #[test]
597 fn parse_zero_range_as_invalid() {
598 let input = "bytes=15-";
599 let parsed = parse_range_header(input).unwrap();
600 assert_eq!(
601 parsed.validate(0),
602 Err(RangeUnsatisfiableError::RangeReversed)
603 );
604 }
605
606 #[test]
607 fn parse_zero_range_last_byte_valid_if_file_size_0() {
608 let input = "bytes=0-";
609 let expect = SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::LastByte);
610 let actual = parse_range_header(input).unwrap().ranges[0];
611 assert_eq!(actual, expect);
612 }
613
614 #[test]
615 fn parse_zero_range_closed_valid_if_file_size_0() {
616 let input = "bytes=0-0";
617 let expect = SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(0));
618 let actual = parse_range_header(input).unwrap().ranges[0];
619 assert_eq!(actual, expect);
620 }
621
622 #[test]
623 fn parse_multi_range() {
624 let input = "bytes=0-1023, 2015-3000, 4000-4500, 8000-9999";
625 let expected_ranges = vec![
626 SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(1023)),
627 SyntacticallyCorrectRange::new(StartPosition::Index(2015), EndPosition::Index(3000)),
628 SyntacticallyCorrectRange::new(StartPosition::Index(4000), EndPosition::Index(4500)),
629 SyntacticallyCorrectRange::new(StartPosition::Index(8000), EndPosition::Index(9999)),
630 ];
631 let parsed = parse_range_header(input).unwrap();
632 assert_eq!(expected_ranges, parsed.ranges);
633 let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
634 assert_eq!(
635 vec![0..=1023, 2015..=3000, 4000..=4500, 8000..=9999],
636 validated
637 );
638 }
639
640 #[test]
641 fn parse_multi_range_with_open() {
642 let input = "bytes=0-1023, 1024-";
643 let expected_ranges = vec![
644 SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(1023)),
645 SyntacticallyCorrectRange::new(StartPosition::Index(1024), EndPosition::LastByte),
646 ];
647 let parsed = parse_range_header(input).unwrap();
648 assert_eq!(expected_ranges, parsed.ranges);
649 let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
650 assert_eq!(vec![0..=1023, 1024..=9999], validated);
651 }
652
653 #[test]
654 fn parse_multi_range_with_suffix() {
655 let input = "bytes=0-1023, -1000";
656 let expected_ranges = vec![
657 SyntacticallyCorrectRange::new(StartPosition::Index(0), EndPosition::Index(1023)),
658 SyntacticallyCorrectRange::new(StartPosition::FromLast(1000), EndPosition::LastByte),
659 ];
660 let parsed = parse_range_header(input).unwrap();
661 assert_eq!(expected_ranges, parsed.ranges);
662 assert_eq!(expected_ranges, parsed.ranges);
663 let validated = parsed.validate(TEST_FILE_LENGTH).unwrap();
664 assert_eq!(vec![0..=1023, 9000..=9999], validated);
665 }
666
667 #[test]
668 fn parse_overlapping_multi_range_as_unsatisfiable_standard() {
669 let input = "bytes=0-1023, 500-800";
670 assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
671 let input = "bytes=0-0, 0-15";
672 assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
673 let input = "bytes=0-20, 20-35";
674 assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
675 }
676
677 #[test]
678 fn parse_overlapping_multi_range_as_unsatisfiable_open() {
679 let input = "bytes=0-, 5000-6000";
680 assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
681 }
682
683 #[test]
684 fn parse_overlapping_multi_range_as_unsatisfiable_suffixed() {
685 let input = "bytes=8000-9000, -1001";
686 assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
687 let input = "bytes=8000-9000, -1000";
688 assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
689 let input = "bytes=8000-9000, -999";
691 parse_range_header(input)
692 .unwrap()
693 .validate(TEST_FILE_LENGTH)
694 .unwrap();
695 }
696
697 #[test]
698 fn parse_overlapping_multi_range_as_unsatisfiable_suffixed_open() {
699 let input = "bytes=0-, -1";
700 assert_validation_err(input, RangeUnsatisfiableError::OverlappingRanges);
701 }
702
703 #[test]
704 fn parse_multi_range_with_a_reversed_as_invalid() {
705 let input = "bytes=0-15, 30-20";
706 assert_validation_err(input, RangeUnsatisfiableError::RangeReversed);
707 }
708
709 fn assert_validation_err(input: &str, err: RangeUnsatisfiableError) {
710 let parsed = parse_range_header(input)
711 .unwrap()
712 .validate(TEST_FILE_LENGTH);
713 assert_eq!(Err(err), parsed);
714 }
715
716 #[test]
717 fn parse_multi_range_rejects_invalid() {
718 let input = "bytes=0-15, 25, 9, ";
719 let parsed = parse_range_header(input);
720 assert!(parsed.is_err());
721 }
722
723 #[quickcheck_macros::quickcheck]
724 #[allow(clippy::needless_pass_by_value)]
725 fn always_errs_on_random_input(input: String) -> quickcheck::TestResult {
726 let acceptable = regex::Regex::new(
728 "^bytes=((\\d+-\\d+,\\s?)|(\\d+-,\\s?)|(-\\d+,\\s?))*((\\d+-\\d+)|(\\d+-)|(-\\d+))+$",
729 )
730 .unwrap();
731 if acceptable.is_match(&input) {
732 quickcheck::TestResult::discard()
733 } else if let Ok(passed_first_pass) = parse_range_header(&input) {
734 quickcheck::TestResult::from_bool(passed_first_pass.validate(u64::MAX).is_err())
735 } else {
736 quickcheck::TestResult::passed()
737 }
738 }
739
740 fn single_range(syntactically_correct: SyntacticallyCorrectRange) -> ParsedRanges {
741 ParsedRanges::new(vec![syntactically_correct])
742 }
743}