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, "multiple ranges not supported"),
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<ByteRange, RangeError> {
207 let spec = parse_range_spec(header)?;
208 spec.resolve(resource_size)
209}
210
211pub fn parse_range_spec(header: &str) -> Result<RangeSpec, RangeError> {
219 let header = header.trim();
220
221 let (unit, range_set) = header
223 .split_once('=')
224 .ok_or_else(|| RangeError::InvalidSyntax("missing '=' separator".to_string()))?;
225
226 let unit = unit.trim();
227 let range_set = range_set.trim();
228
229 if !unit.eq_ignore_ascii_case("bytes") {
231 return Err(RangeError::UnsupportedUnit(unit.to_string()));
232 }
233
234 if range_set.contains(',') {
236 return Err(RangeError::MultipleRangesNotSupported);
237 }
238
239 parse_single_range(range_set)
241}
242
243fn parse_single_range(range: &str) -> Result<RangeSpec, RangeError> {
245 let range = range.trim();
246
247 if range.is_empty() {
248 return Err(RangeError::InvalidSyntax("empty range".to_string()));
249 }
250
251 if range.starts_with('-') {
253 let suffix = &range[1..];
254 let length: u64 = suffix
255 .parse()
256 .map_err(|_| RangeError::InvalidSyntax(format!("invalid suffix length: {suffix}")))?;
257 return Ok(RangeSpec::Suffix { length });
258 }
259
260 let (start_str, end_str) = range
262 .split_once('-')
263 .ok_or_else(|| RangeError::InvalidSyntax("missing '-' separator".to_string()))?;
264
265 let start: u64 = start_str
266 .trim()
267 .parse()
268 .map_err(|_| RangeError::InvalidSyntax(format!("invalid start: {start_str}")))?;
269
270 let end_str = end_str.trim();
271
272 if end_str.is_empty() {
273 Ok(RangeSpec::From { start })
275 } else {
276 let end: u64 = end_str
278 .parse()
279 .map_err(|_| RangeError::InvalidSyntax(format!("invalid end: {end_str}")))?;
280 Ok(RangeSpec::FromTo { start, end })
281 }
282}
283
284#[must_use]
288pub fn supports_ranges(accept_ranges: Option<&str>) -> bool {
289 match accept_ranges {
290 Some(value) => !value.eq_ignore_ascii_case("none"),
291 None => false,
292 }
293}
294
295#[must_use]
297pub const fn accept_ranges_bytes() -> &'static str {
298 "bytes"
299}
300
301#[must_use]
305pub fn content_range_unsatisfiable(resource_size: u64) -> String {
306 format!("bytes */{resource_size}")
307}
308
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub enum IfRangeResult {
312 ServePartial,
314 ServeFull,
316}
317
318#[must_use]
361pub fn check_if_range(
362 if_range: &str,
363 etag: Option<&str>,
364 last_modified: Option<&str>,
365) -> IfRangeResult {
366 let if_range = if_range.trim();
367
368 if if_range.is_empty() {
370 return IfRangeResult::ServePartial;
371 }
372
373 if if_range.starts_with('"') || if_range.starts_with("W/") {
375 if let Some(current_etag) = etag {
377 if etag_strong_match(if_range, current_etag) {
379 return IfRangeResult::ServePartial;
380 }
381 }
382 IfRangeResult::ServeFull
383 } else {
384 if let Some(current_last_modified) = last_modified {
386 if if_range == current_last_modified {
388 return IfRangeResult::ServePartial;
389 }
390 }
391 IfRangeResult::ServeFull
392 }
393}
394
395fn etag_strong_match(etag1: &str, etag2: &str) -> bool {
399 if etag1.starts_with("W/") || etag2.starts_with("W/") {
401 return false;
402 }
403
404 etag1 == etag2
406}
407
408#[cfg(test)]
409mod tests {
410 use super::*;
411
412 #[test]
417 fn byte_range_new() {
418 let range = ByteRange::new(0, 499);
419 assert_eq!(range.start, 0);
420 assert_eq!(range.end, 499);
421 }
422
423 #[test]
424 fn byte_range_len() {
425 let range = ByteRange::new(0, 499);
426 assert_eq!(range.len(), 500);
427
428 let range = ByteRange::new(0, 0);
429 assert_eq!(range.len(), 1);
430
431 let range = ByteRange::new(100, 199);
432 assert_eq!(range.len(), 100);
433 }
434
435 #[test]
436 fn byte_range_content_range_header() {
437 let range = ByteRange::new(0, 499);
438 assert_eq!(range.content_range_header(1000), "bytes 0-499/1000");
439
440 let range = ByteRange::new(500, 999);
441 assert_eq!(range.content_range_header(1000), "bytes 500-999/1000");
442 }
443
444 #[test]
445 fn byte_range_display() {
446 let range = ByteRange::new(0, 499);
447 assert_eq!(format!("{range}"), "0-499");
448 }
449
450 #[test]
451 #[should_panic(expected = "start must be <= end")]
452 fn byte_range_invalid() {
453 let _ = ByteRange::new(500, 100);
454 }
455
456 #[test]
461 fn range_spec_from_to_valid() {
462 let spec = RangeSpec::FromTo { start: 0, end: 499 };
463 let range = spec.resolve(1000).unwrap();
464 assert_eq!(range.start, 0);
465 assert_eq!(range.end, 499);
466 }
467
468 #[test]
469 fn range_spec_from_to_clamped() {
470 let spec = RangeSpec::FromTo {
472 start: 0,
473 end: 9999,
474 };
475 let range = spec.resolve(1000).unwrap();
476 assert_eq!(range.start, 0);
477 assert_eq!(range.end, 999);
478 }
479
480 #[test]
481 fn range_spec_from_to_not_satisfiable() {
482 let spec = RangeSpec::FromTo {
483 start: 1000,
484 end: 1500,
485 };
486 let err = spec.resolve(1000).unwrap_err();
487 assert_eq!(
488 err,
489 RangeError::NotSatisfiable {
490 resource_size: 1000
491 }
492 );
493 }
494
495 #[test]
496 fn range_spec_from_valid() {
497 let spec = RangeSpec::From { start: 500 };
498 let range = spec.resolve(1000).unwrap();
499 assert_eq!(range.start, 500);
500 assert_eq!(range.end, 999);
501 }
502
503 #[test]
504 fn range_spec_from_not_satisfiable() {
505 let spec = RangeSpec::From { start: 1000 };
506 let err = spec.resolve(1000).unwrap_err();
507 assert_eq!(
508 err,
509 RangeError::NotSatisfiable {
510 resource_size: 1000
511 }
512 );
513 }
514
515 #[test]
516 fn range_spec_suffix_valid() {
517 let spec = RangeSpec::Suffix { length: 100 };
518 let range = spec.resolve(1000).unwrap();
519 assert_eq!(range.start, 900);
520 assert_eq!(range.end, 999);
521 }
522
523 #[test]
524 fn range_spec_suffix_exceeds_size() {
525 let spec = RangeSpec::Suffix { length: 2000 };
527 let range = spec.resolve(1000).unwrap();
528 assert_eq!(range.start, 0);
529 assert_eq!(range.end, 999);
530 }
531
532 #[test]
533 fn range_spec_suffix_zero() {
534 let spec = RangeSpec::Suffix { length: 0 };
535 let err = spec.resolve(1000).unwrap_err();
536 assert_eq!(
537 err,
538 RangeError::NotSatisfiable {
539 resource_size: 1000
540 }
541 );
542 }
543
544 #[test]
545 fn range_spec_empty_resource() {
546 let spec = RangeSpec::From { start: 0 };
547 let err = spec.resolve(0).unwrap_err();
548 assert_eq!(err, RangeError::NotSatisfiable { resource_size: 0 });
549 }
550
551 #[test]
556 fn parse_range_from_to() {
557 let range = parse_range_header("bytes=0-499", 1000).unwrap();
558 assert_eq!(range.start, 0);
559 assert_eq!(range.end, 499);
560 assert_eq!(range.len(), 500);
561 }
562
563 #[test]
564 fn parse_range_from() {
565 let range = parse_range_header("bytes=500-", 1000).unwrap();
566 assert_eq!(range.start, 500);
567 assert_eq!(range.end, 999);
568 }
569
570 #[test]
571 fn parse_range_suffix() {
572 let range = parse_range_header("bytes=-100", 1000).unwrap();
573 assert_eq!(range.start, 900);
574 assert_eq!(range.end, 999);
575 }
576
577 #[test]
578 fn parse_range_with_spaces() {
579 let range = parse_range_header(" bytes = 0 - 499 ", 1000).unwrap();
580 assert_eq!(range.start, 0);
581 assert_eq!(range.end, 499);
582 }
583
584 #[test]
585 fn parse_range_invalid_unit() {
586 let err = parse_range_header("items=0-10", 100).unwrap_err();
587 assert!(matches!(err, RangeError::UnsupportedUnit(_)));
588 }
589
590 #[test]
591 fn parse_range_multiple_not_supported() {
592 let err = parse_range_header("bytes=0-10, 20-30", 100).unwrap_err();
593 assert_eq!(err, RangeError::MultipleRangesNotSupported);
594 }
595
596 #[test]
597 fn parse_range_invalid_syntax_no_equals() {
598 let err = parse_range_header("bytes 0-10", 100).unwrap_err();
599 assert!(matches!(err, RangeError::InvalidSyntax(_)));
600 }
601
602 #[test]
603 fn parse_range_invalid_syntax_no_dash() {
604 let err = parse_range_header("bytes=100", 100).unwrap_err();
605 assert!(matches!(err, RangeError::InvalidSyntax(_)));
606 }
607
608 #[test]
609 fn parse_range_invalid_start() {
610 let err = parse_range_header("bytes=abc-100", 1000).unwrap_err();
611 assert!(matches!(err, RangeError::InvalidSyntax(_)));
612 }
613
614 #[test]
615 fn parse_range_invalid_end() {
616 let err = parse_range_header("bytes=0-xyz", 1000).unwrap_err();
617 assert!(matches!(err, RangeError::InvalidSyntax(_)));
618 }
619
620 #[test]
621 fn parse_range_not_satisfiable() {
622 let err = parse_range_header("bytes=1000-2000", 500).unwrap_err();
623 assert_eq!(err, RangeError::NotSatisfiable { resource_size: 500 });
624 }
625
626 #[test]
631 fn test_accept_ranges_bytes() {
632 assert_eq!(accept_ranges_bytes(), "bytes");
633 }
634
635 #[test]
636 fn test_content_range_unsatisfiable() {
637 assert_eq!(content_range_unsatisfiable(1000), "bytes */1000");
638 }
639
640 #[test]
641 fn test_supports_ranges() {
642 assert!(supports_ranges(Some("bytes")));
643 assert!(supports_ranges(Some("Bytes")));
644 assert!(!supports_ranges(Some("none")));
645 assert!(!supports_ranges(Some("None")));
646 assert!(!supports_ranges(None));
647 }
648
649 #[test]
654 fn range_error_display() {
655 let err = RangeError::InvalidSyntax("test".to_string());
656 assert!(format!("{err}").contains("invalid range syntax"));
657
658 let err = RangeError::UnsupportedUnit("items".to_string());
659 assert!(format!("{err}").contains("unsupported range unit: items"));
660
661 let err = RangeError::NotSatisfiable { resource_size: 500 };
662 assert!(format!("{err}").contains("range not satisfiable"));
663
664 let err = RangeError::MultipleRangesNotSupported;
665 assert!(format!("{err}").contains("multiple ranges not supported"));
666 }
667
668 #[test]
673 fn if_range_etag_match() {
674 let result = check_if_range("\"abc123\"", Some("\"abc123\""), None);
675 assert_eq!(result, IfRangeResult::ServePartial);
676 }
677
678 #[test]
679 fn if_range_etag_mismatch() {
680 let result = check_if_range("\"abc123\"", Some("\"def456\""), None);
681 assert_eq!(result, IfRangeResult::ServeFull);
682 }
683
684 #[test]
685 fn if_range_etag_no_current() {
686 let result = check_if_range("\"abc123\"", None, None);
687 assert_eq!(result, IfRangeResult::ServeFull);
688 }
689
690 #[test]
691 fn if_range_weak_etag_never_matches() {
692 let result = check_if_range("W/\"abc123\"", Some("W/\"abc123\""), None);
694 assert_eq!(result, IfRangeResult::ServeFull);
695
696 let result = check_if_range("\"abc123\"", Some("W/\"abc123\""), None);
698 assert_eq!(result, IfRangeResult::ServeFull);
699 }
700
701 #[test]
702 fn if_range_date_match() {
703 let date = "Wed, 21 Oct 2015 07:28:00 GMT";
704 let result = check_if_range(date, None, Some(date));
705 assert_eq!(result, IfRangeResult::ServePartial);
706 }
707
708 #[test]
709 fn if_range_date_mismatch() {
710 let result = check_if_range(
711 "Wed, 21 Oct 2015 07:28:00 GMT",
712 None,
713 Some("Thu, 22 Oct 2015 07:28:00 GMT"),
714 );
715 assert_eq!(result, IfRangeResult::ServeFull);
716 }
717
718 #[test]
719 fn if_range_date_no_current() {
720 let result = check_if_range("Wed, 21 Oct 2015 07:28:00 GMT", None, None);
721 assert_eq!(result, IfRangeResult::ServeFull);
722 }
723
724 #[test]
725 fn if_range_empty_header() {
726 let result = check_if_range("", None, None);
727 assert_eq!(result, IfRangeResult::ServePartial);
728 }
729
730 #[test]
731 fn if_range_whitespace_trimmed() {
732 let result = check_if_range(" \"abc123\" ", Some("\"abc123\""), None);
733 assert_eq!(result, IfRangeResult::ServePartial);
734 }
735}