1use std::fmt;
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36pub struct ByteRange {
37 pub start: u64,
39 pub end: u64,
41}
42
43impl ByteRange {
44 #[must_use]
50 pub fn new(start: u64, end: u64) -> Self {
51 assert!(start <= end, "start must be <= end");
52 Self { start, end }
53 }
54
55 #[must_use]
57 pub fn len(&self) -> u64 {
58 self.end.saturating_sub(self.start).saturating_add(1)
59 }
60
61 #[must_use]
63 pub fn is_empty(&self) -> bool {
64 false }
66
67 #[must_use]
71 pub fn content_range_header(&self, total_size: u64) -> String {
72 format!("bytes {}-{}/{}", self.start, self.end, total_size)
73 }
74}
75
76impl fmt::Display for ByteRange {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "{}-{}", self.start, self.end)
79 }
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub enum RangeError {
85 InvalidSyntax(String),
87 UnsupportedUnit(String),
89 NotSatisfiable {
91 resource_size: u64,
93 },
94 MultipleRangesNotSupported,
96}
97
98impl fmt::Display for RangeError {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match self {
101 Self::InvalidSyntax(msg) => write!(f, "invalid range syntax: {msg}"),
102 Self::UnsupportedUnit(unit) => write!(f, "unsupported range unit: {unit}"),
103 Self::NotSatisfiable { resource_size } => {
104 write!(
105 f,
106 "range not satisfiable for resource of size {resource_size}"
107 )
108 }
109 Self::MultipleRangesNotSupported => write!(f, "too many ranges requested"),
110 }
111 }
112}
113
114impl std::error::Error for RangeError {}
115
116#[derive(Debug, Clone, Copy, PartialEq, Eq)]
118pub enum RangeSpec {
119 FromTo { start: u64, end: u64 },
121 From { start: u64 },
123 Suffix { length: u64 },
125}
126
127impl RangeSpec {
128 pub fn resolve(self, resource_size: u64) -> Result<ByteRange, RangeError> {
136 if resource_size == 0 {
137 return Err(RangeError::NotSatisfiable { resource_size });
138 }
139
140 match self {
141 Self::FromTo { start, end } => {
142 let end = end.min(resource_size - 1);
144
145 if start > end || start >= resource_size {
146 return Err(RangeError::NotSatisfiable { resource_size });
147 }
148
149 Ok(ByteRange::new(start, end))
150 }
151 Self::From { start } => {
152 if start >= resource_size {
153 return Err(RangeError::NotSatisfiable { resource_size });
154 }
155 Ok(ByteRange::new(start, resource_size - 1))
156 }
157 Self::Suffix { length } => {
158 if length == 0 {
159 return Err(RangeError::NotSatisfiable { resource_size });
160 }
161 let start = resource_size.saturating_sub(length);
163 Ok(ByteRange::new(start, resource_size - 1))
164 }
165 }
166 }
167}
168
169pub fn parse_range_header(header: &str, resource_size: u64) -> Result<Vec<ByteRange>, RangeError> {
208 let specs = parse_range_spec(header)?;
209
210 let mut ranges = Vec::with_capacity(specs.len());
211 for spec in specs {
212 match spec.resolve(resource_size) {
213 Ok(r) => ranges.push(r),
214 Err(RangeError::NotSatisfiable { .. }) => {
215 }
218 Err(e) => return Err(e),
219 }
220 }
221
222 if ranges.is_empty() {
223 return Err(RangeError::NotSatisfiable { resource_size });
224 }
225
226 normalize_ranges(&mut ranges);
227 Ok(ranges)
228}
229
230pub fn parse_range_spec(header: &str) -> Result<Vec<RangeSpec>, RangeError> {
238 let header = header.trim();
239
240 let (unit, range_set) = header
242 .split_once('=')
243 .ok_or_else(|| RangeError::InvalidSyntax("missing '=' separator".to_string()))?;
244
245 let unit = unit.trim();
246 let range_set = range_set.trim();
247
248 if !unit.eq_ignore_ascii_case("bytes") {
250 return Err(RangeError::UnsupportedUnit(unit.to_string()));
251 }
252
253 const MAX_RANGES: usize = 16;
254
255 let mut specs = Vec::new();
256 for part in range_set.split(',') {
257 let part = part.trim();
258 if part.is_empty() {
259 return Err(RangeError::InvalidSyntax("empty range".to_string()));
260 }
261 specs.push(parse_single_range(part)?);
262 if specs.len() > MAX_RANGES {
263 return Err(RangeError::MultipleRangesNotSupported);
264 }
265 }
266
267 Ok(specs)
268}
269
270fn parse_single_range(range: &str) -> Result<RangeSpec, RangeError> {
272 let range = range.trim();
273
274 if range.is_empty() {
275 return Err(RangeError::InvalidSyntax("empty range".to_string()));
276 }
277
278 if range.starts_with('-') {
280 let suffix = &range[1..];
281 let length: u64 = suffix
282 .parse()
283 .map_err(|_| RangeError::InvalidSyntax(format!("invalid suffix length: {suffix}")))?;
284 return Ok(RangeSpec::Suffix { length });
285 }
286
287 let (start_str, end_str) = range
289 .split_once('-')
290 .ok_or_else(|| RangeError::InvalidSyntax("missing '-' separator".to_string()))?;
291
292 let start: u64 = start_str
293 .trim()
294 .parse()
295 .map_err(|_| RangeError::InvalidSyntax(format!("invalid start: {start_str}")))?;
296
297 let end_str = end_str.trim();
298
299 if end_str.is_empty() {
300 Ok(RangeSpec::From { start })
302 } else {
303 let end: u64 = end_str
305 .parse()
306 .map_err(|_| RangeError::InvalidSyntax(format!("invalid end: {end_str}")))?;
307 Ok(RangeSpec::FromTo { start, end })
308 }
309}
310
311fn normalize_ranges(ranges: &mut Vec<ByteRange>) {
312 ranges.sort_by_key(|r| r.start);
313
314 let mut out: Vec<ByteRange> = Vec::with_capacity(ranges.len());
315 for r in ranges.drain(..) {
316 match out.last_mut() {
317 None => out.push(r),
318 Some(last) => {
319 if r.start <= last.end.saturating_add(1) {
321 last.end = last.end.max(r.end);
322 } else {
323 out.push(r);
324 }
325 }
326 }
327 }
328 *ranges = out;
329}
330
331#[must_use]
335pub fn supports_ranges(accept_ranges: Option<&str>) -> bool {
336 match accept_ranges {
337 Some(value) => value
338 .split(',')
339 .map(str::trim)
340 .any(|unit| unit.eq_ignore_ascii_case("bytes")),
341 None => false,
342 }
343}
344
345#[must_use]
347pub const fn accept_ranges_bytes() -> &'static str {
348 "bytes"
349}
350
351#[must_use]
355pub fn content_range_unsatisfiable(resource_size: u64) -> String {
356 format!("bytes */{resource_size}")
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Eq)]
361pub enum IfRangeResult {
362 ServePartial,
364 ServeFull,
366}
367
368#[must_use]
411pub fn check_if_range(
412 if_range: &str,
413 etag: Option<&str>,
414 last_modified: Option<&str>,
415) -> IfRangeResult {
416 let if_range = if_range.trim();
417
418 if if_range.is_empty() {
420 return IfRangeResult::ServePartial;
421 }
422
423 if if_range.starts_with('"') || if_range.starts_with("W/") {
425 if let Some(current_etag) = etag {
427 if etag_strong_match(if_range, current_etag) {
429 return IfRangeResult::ServePartial;
430 }
431 }
432 IfRangeResult::ServeFull
433 } else {
434 if let Some(current_last_modified) = last_modified {
436 if if_range == current_last_modified {
438 return IfRangeResult::ServePartial;
439 }
440 }
441 IfRangeResult::ServeFull
442 }
443}
444
445fn etag_strong_match(etag1: &str, etag2: &str) -> bool {
449 if etag1.starts_with("W/") || etag2.starts_with("W/") {
451 return false;
452 }
453
454 etag1 == etag2
456}
457
458#[cfg(test)]
459mod tests {
460 use super::*;
461
462 #[test]
467 fn byte_range_new() {
468 let range = ByteRange::new(0, 499);
469 assert_eq!(range.start, 0);
470 assert_eq!(range.end, 499);
471 }
472
473 #[test]
474 fn byte_range_len() {
475 let range = ByteRange::new(0, 499);
476 assert_eq!(range.len(), 500);
477
478 let range = ByteRange::new(0, 0);
479 assert_eq!(range.len(), 1);
480
481 let range = ByteRange::new(100, 199);
482 assert_eq!(range.len(), 100);
483 }
484
485 #[test]
486 fn byte_range_content_range_header() {
487 let range = ByteRange::new(0, 499);
488 assert_eq!(range.content_range_header(1000), "bytes 0-499/1000");
489
490 let range = ByteRange::new(500, 999);
491 assert_eq!(range.content_range_header(1000), "bytes 500-999/1000");
492 }
493
494 #[test]
495 fn byte_range_display() {
496 let range = ByteRange::new(0, 499);
497 assert_eq!(format!("{range}"), "0-499");
498 }
499
500 #[test]
501 #[should_panic(expected = "start must be <= end")]
502 fn byte_range_invalid() {
503 let _ = ByteRange::new(500, 100);
504 }
505
506 #[test]
511 fn range_spec_from_to_valid() {
512 let spec = RangeSpec::FromTo { start: 0, end: 499 };
513 let range = spec.resolve(1000).unwrap();
514 assert_eq!(range.start, 0);
515 assert_eq!(range.end, 499);
516 }
517
518 #[test]
519 fn range_spec_from_to_clamped() {
520 let spec = RangeSpec::FromTo {
522 start: 0,
523 end: 9999,
524 };
525 let range = spec.resolve(1000).unwrap();
526 assert_eq!(range.start, 0);
527 assert_eq!(range.end, 999);
528 }
529
530 #[test]
531 fn range_spec_from_to_not_satisfiable() {
532 let spec = RangeSpec::FromTo {
533 start: 1000,
534 end: 1500,
535 };
536 let err = spec.resolve(1000).unwrap_err();
537 assert_eq!(
538 err,
539 RangeError::NotSatisfiable {
540 resource_size: 1000
541 }
542 );
543 }
544
545 #[test]
546 fn range_spec_from_valid() {
547 let spec = RangeSpec::From { start: 500 };
548 let range = spec.resolve(1000).unwrap();
549 assert_eq!(range.start, 500);
550 assert_eq!(range.end, 999);
551 }
552
553 #[test]
554 fn range_spec_from_not_satisfiable() {
555 let spec = RangeSpec::From { start: 1000 };
556 let err = spec.resolve(1000).unwrap_err();
557 assert_eq!(
558 err,
559 RangeError::NotSatisfiable {
560 resource_size: 1000
561 }
562 );
563 }
564
565 #[test]
566 fn range_spec_suffix_valid() {
567 let spec = RangeSpec::Suffix { length: 100 };
568 let range = spec.resolve(1000).unwrap();
569 assert_eq!(range.start, 900);
570 assert_eq!(range.end, 999);
571 }
572
573 #[test]
574 fn range_spec_suffix_exceeds_size() {
575 let spec = RangeSpec::Suffix { length: 2000 };
577 let range = spec.resolve(1000).unwrap();
578 assert_eq!(range.start, 0);
579 assert_eq!(range.end, 999);
580 }
581
582 #[test]
583 fn range_spec_suffix_zero() {
584 let spec = RangeSpec::Suffix { length: 0 };
585 let err = spec.resolve(1000).unwrap_err();
586 assert_eq!(
587 err,
588 RangeError::NotSatisfiable {
589 resource_size: 1000
590 }
591 );
592 }
593
594 #[test]
595 fn range_spec_empty_resource() {
596 let spec = RangeSpec::From { start: 0 };
597 let err = spec.resolve(0).unwrap_err();
598 assert_eq!(err, RangeError::NotSatisfiable { resource_size: 0 });
599 }
600
601 #[test]
606 fn parse_range_from_to() {
607 let ranges = parse_range_header("bytes=0-499", 1000).unwrap();
608 assert_eq!(ranges.len(), 1);
609 assert_eq!(ranges[0].start, 0);
610 assert_eq!(ranges[0].end, 499);
611 assert_eq!(ranges[0].len(), 500);
612 }
613
614 #[test]
615 fn parse_range_from() {
616 let ranges = parse_range_header("bytes=500-", 1000).unwrap();
617 assert_eq!(ranges.len(), 1);
618 assert_eq!(ranges[0].start, 500);
619 assert_eq!(ranges[0].end, 999);
620 }
621
622 #[test]
623 fn parse_range_suffix() {
624 let ranges = parse_range_header("bytes=-100", 1000).unwrap();
625 assert_eq!(ranges.len(), 1);
626 assert_eq!(ranges[0].start, 900);
627 assert_eq!(ranges[0].end, 999);
628 }
629
630 #[test]
631 fn parse_range_with_spaces() {
632 let ranges = parse_range_header(" bytes = 0 - 499 ", 1000).unwrap();
633 assert_eq!(ranges.len(), 1);
634 assert_eq!(ranges[0].start, 0);
635 assert_eq!(ranges[0].end, 499);
636 }
637
638 #[test]
639 fn parse_range_invalid_unit() {
640 let err = parse_range_header("items=0-10", 100).unwrap_err();
641 assert!(matches!(err, RangeError::UnsupportedUnit(_)));
642 }
643
644 #[test]
645 fn parse_range_multiple_ranges() {
646 let ranges = parse_range_header("bytes=0-10, 20-30", 100).unwrap();
647 assert_eq!(ranges.len(), 2);
648 assert_eq!(ranges[0], ByteRange::new(0, 10));
649 assert_eq!(ranges[1], ByteRange::new(20, 30));
650 }
651
652 #[test]
653 fn parse_range_too_many_ranges_rejected() {
654 let header = (0..17)
655 .map(|i| format!("{i}-{i}"))
656 .collect::<Vec<_>>()
657 .join(", ");
658 let header = format!("bytes={header}");
659 let err = parse_range_header(&header, 1000).unwrap_err();
660 assert_eq!(err, RangeError::MultipleRangesNotSupported);
661 }
662
663 #[test]
664 fn parse_range_invalid_syntax_no_equals() {
665 let err = parse_range_header("bytes 0-10", 100).unwrap_err();
666 assert!(matches!(err, RangeError::InvalidSyntax(_)));
667 }
668
669 #[test]
670 fn parse_range_invalid_syntax_no_dash() {
671 let err = parse_range_header("bytes=100", 100).unwrap_err();
672 assert!(matches!(err, RangeError::InvalidSyntax(_)));
673 }
674
675 #[test]
676 fn parse_range_invalid_start() {
677 let err = parse_range_header("bytes=abc-100", 1000).unwrap_err();
678 assert!(matches!(err, RangeError::InvalidSyntax(_)));
679 }
680
681 #[test]
682 fn parse_range_invalid_end() {
683 let err = parse_range_header("bytes=0-xyz", 1000).unwrap_err();
684 assert!(matches!(err, RangeError::InvalidSyntax(_)));
685 }
686
687 #[test]
688 fn parse_range_not_satisfiable() {
689 let err = parse_range_header("bytes=1000-2000", 500).unwrap_err();
690 assert_eq!(err, RangeError::NotSatisfiable { resource_size: 500 });
691 }
692
693 #[test]
698 fn test_accept_ranges_bytes() {
699 assert_eq!(accept_ranges_bytes(), "bytes");
700 }
701
702 #[test]
703 fn test_content_range_unsatisfiable() {
704 assert_eq!(content_range_unsatisfiable(1000), "bytes */1000");
705 }
706
707 #[test]
708 fn test_supports_ranges() {
709 assert!(supports_ranges(Some("bytes")));
710 assert!(supports_ranges(Some("Bytes")));
711 assert!(supports_ranges(Some("bytes, other")));
712 assert!(!supports_ranges(Some("none")));
713 assert!(!supports_ranges(Some("None")));
714 assert!(!supports_ranges(Some("items")));
715 assert!(!supports_ranges(Some("")));
716 assert!(!supports_ranges(Some(" ")));
717 assert!(!supports_ranges(None));
718 }
719
720 #[test]
725 fn range_error_display() {
726 let err = RangeError::InvalidSyntax("test".to_string());
727 assert!(format!("{err}").contains("invalid range syntax"));
728
729 let err = RangeError::UnsupportedUnit("items".to_string());
730 assert!(format!("{err}").contains("unsupported range unit: items"));
731
732 let err = RangeError::NotSatisfiable { resource_size: 500 };
733 assert!(format!("{err}").contains("range not satisfiable"));
734
735 let err = RangeError::MultipleRangesNotSupported;
736 assert!(format!("{err}").contains("too many ranges requested"));
737 }
738
739 #[test]
744 fn if_range_etag_match() {
745 let result = check_if_range("\"abc123\"", Some("\"abc123\""), None);
746 assert_eq!(result, IfRangeResult::ServePartial);
747 }
748
749 #[test]
750 fn if_range_etag_mismatch() {
751 let result = check_if_range("\"abc123\"", Some("\"def456\""), None);
752 assert_eq!(result, IfRangeResult::ServeFull);
753 }
754
755 #[test]
756 fn if_range_etag_no_current() {
757 let result = check_if_range("\"abc123\"", None, None);
758 assert_eq!(result, IfRangeResult::ServeFull);
759 }
760
761 #[test]
762 fn if_range_weak_etag_never_matches() {
763 let result = check_if_range("W/\"abc123\"", Some("W/\"abc123\""), None);
765 assert_eq!(result, IfRangeResult::ServeFull);
766
767 let result = check_if_range("\"abc123\"", Some("W/\"abc123\""), None);
769 assert_eq!(result, IfRangeResult::ServeFull);
770 }
771
772 #[test]
773 fn if_range_date_match() {
774 let date = "Wed, 21 Oct 2015 07:28:00 GMT";
775 let result = check_if_range(date, None, Some(date));
776 assert_eq!(result, IfRangeResult::ServePartial);
777 }
778
779 #[test]
780 fn if_range_date_mismatch() {
781 let result = check_if_range(
782 "Wed, 21 Oct 2015 07:28:00 GMT",
783 None,
784 Some("Thu, 22 Oct 2015 07:28:00 GMT"),
785 );
786 assert_eq!(result, IfRangeResult::ServeFull);
787 }
788
789 #[test]
790 fn if_range_date_no_current() {
791 let result = check_if_range("Wed, 21 Oct 2015 07:28:00 GMT", None, None);
792 assert_eq!(result, IfRangeResult::ServeFull);
793 }
794
795 #[test]
796 fn if_range_empty_header() {
797 let result = check_if_range("", None, None);
798 assert_eq!(result, IfRangeResult::ServePartial);
799 }
800
801 #[test]
802 fn if_range_whitespace_trimmed() {
803 let result = check_if_range(" \"abc123\" ", Some("\"abc123\""), None);
804 assert_eq!(result, IfRangeResult::ServePartial);
805 }
806}