Skip to content

Commit d4fde98

Browse files
fix!: use Range<u64> instead of RangeInclusive<u64> and support zero-sized files
Change ContentRange::range() from RangeInclusive<u64> to Range<u64> (exclusive end) and file_range() from NonZero<u64> to plain u64. RangeInclusive cannot represent an empty range, which forced the API to require NonZero<u64> and reject zero-sized files entirely. With Range<u64>, a zero-sized file naturally produces 0..0 when no Range header is present. Any Range header against a zero-sized file correctly returns 416. The HTTP header types (OrderedRange, HttpContentRange) keep their inclusive semantics since they model the wire format.
1 parent e4f012a commit d4fde98

2 files changed

Lines changed: 87 additions & 66 deletions

File tree

src/headers/tests.rs

Lines changed: 73 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -400,8 +400,6 @@ mod content_range_parsing_errors {
400400

401401
#[cfg(test)]
402402
mod file_range {
403-
use std::num::NonZeroU64;
404-
405403
use crate::{
406404
file_range,
407405
headers::{
@@ -411,147 +409,176 @@ mod file_range {
411409
},
412410
};
413411

414-
fn size(n: u64) -> NonZeroU64 {
415-
NonZeroU64::new(n).unwrap()
416-
}
417-
418412
#[test]
419413
fn no_range_returns_full_file() {
420-
let result = file_range(size(10), None).unwrap();
414+
let result = file_range(10, None).unwrap();
421415
assert!(result.header().is_none());
422-
assert_eq!(result.range(), &(0..=9));
416+
assert_eq!(result.range(), &(0..10));
423417
}
424418

425419
#[test]
426420
fn starting_point_zero() {
427-
let result = file_range(size(10), Some(HttpRange::StartingPoint(0))).unwrap();
421+
let result = file_range(10, Some(HttpRange::StartingPoint(0))).unwrap();
428422
assert!(result.header().is_some());
429-
assert_eq!(result.range(), &(0..=9));
423+
assert_eq!(result.range(), &(0..10));
430424
}
431425

432426
#[test]
433427
fn starting_point_middle() {
434-
let result = file_range(size(10), Some(HttpRange::StartingPoint(5))).unwrap();
435-
assert_eq!(result.range(), &(5..=9));
428+
let result = file_range(10, Some(HttpRange::StartingPoint(5))).unwrap();
429+
assert_eq!(result.range(), &(5..10));
436430
}
437431

438432
#[test]
439433
fn starting_point_last_byte() {
440-
let result = file_range(size(10), Some(HttpRange::StartingPoint(9))).unwrap();
441-
assert_eq!(result.range(), &(9..=9));
434+
let result = file_range(10, Some(HttpRange::StartingPoint(9))).unwrap();
435+
assert_eq!(result.range(), &(9..10));
442436
}
443437

444438
#[test]
445439
fn starting_point_at_size() {
446-
let result = file_range(size(10), Some(HttpRange::StartingPoint(10)));
440+
let result = file_range(10, Some(HttpRange::StartingPoint(10)));
447441
assert!(result.is_err());
448442
}
449443

450444
#[test]
451445
fn starting_point_beyond_size() {
452-
let result = file_range(size(10), Some(HttpRange::StartingPoint(20)));
446+
let result = file_range(10, Some(HttpRange::StartingPoint(20)));
453447
assert!(result.is_err());
454448
}
455449

456450
#[test]
457451
fn range_single_byte() {
458452
let range = HttpRange::Range(OrderedRange::new(0..=0).unwrap());
459-
let result = file_range(size(10), Some(range)).unwrap();
460-
assert_eq!(result.range(), &(0..=0));
453+
let result = file_range(10, Some(range)).unwrap();
454+
assert_eq!(result.range(), &(0..1));
461455
}
462456

463457
#[test]
464458
fn range_full_file() {
465459
let range = HttpRange::Range(OrderedRange::new(0..=9).unwrap());
466-
let result = file_range(size(10), Some(range)).unwrap();
467-
assert_eq!(result.range(), &(0..=9));
460+
let result = file_range(10, Some(range)).unwrap();
461+
assert_eq!(result.range(), &(0..10));
468462
}
469463

470464
#[test]
471465
fn range_end_at_size_is_clamped() {
472466
let range = HttpRange::Range(OrderedRange::new(0..=10).unwrap());
473-
let result = file_range(size(10), Some(range)).unwrap();
474-
assert_eq!(result.range(), &(0..=9));
467+
let result = file_range(10, Some(range)).unwrap();
468+
assert_eq!(result.range(), &(0..10));
475469
}
476470

477471
#[test]
478472
fn range_beyond_size_is_clamped() {
479473
let range = HttpRange::Range(OrderedRange::new(0..=50).unwrap());
480-
let result = file_range(size(10), Some(range)).unwrap();
481-
assert_eq!(result.range(), &(0..=9));
474+
let result = file_range(10, Some(range)).unwrap();
475+
assert_eq!(result.range(), &(0..10));
476+
}
477+
478+
#[test]
479+
fn range_end_at_u64_max_is_clamped() {
480+
let range = HttpRange::Range(OrderedRange::new(0..=u64::MAX).unwrap());
481+
let result = file_range(10, Some(range)).unwrap();
482+
assert_eq!(result.range(), &(0..10));
482483
}
483484

484485
#[test]
485486
fn range_start_at_size_is_unsatisfiable() {
486487
let range = HttpRange::Range(OrderedRange::new(10..=20).unwrap());
487-
let result = file_range(size(10), Some(range));
488+
let result = file_range(10, Some(range));
488489
assert!(result.is_err());
489490
}
490491

491492
#[test]
492493
fn range_start_beyond_size_is_unsatisfiable() {
493494
let range = HttpRange::Range(OrderedRange::new(50..=100).unwrap());
494-
let result = file_range(size(10), Some(range));
495+
let result = file_range(10, Some(range));
495496
assert!(result.is_err());
496497
}
497498

498499
#[test]
499500
fn suffix_entire_file() {
500-
let result = file_range(size(10), Some(HttpRange::Suffix(10))).unwrap();
501-
assert_eq!(result.range(), &(0..=9));
501+
let result = file_range(10, Some(HttpRange::Suffix(10))).unwrap();
502+
assert_eq!(result.range(), &(0..10));
502503
}
503504

504505
#[test]
505506
fn suffix_last_byte() {
506-
let result = file_range(size(10), Some(HttpRange::Suffix(1))).unwrap();
507-
assert_eq!(result.range(), &(9..=9));
507+
let result = file_range(10, Some(HttpRange::Suffix(1))).unwrap();
508+
assert_eq!(result.range(), &(9..10));
508509
}
509510

510511
#[test]
511512
fn suffix_middle() {
512-
let result = file_range(size(10), Some(HttpRange::Suffix(5))).unwrap();
513-
assert_eq!(result.range(), &(5..=9));
513+
let result = file_range(10, Some(HttpRange::Suffix(5))).unwrap();
514+
assert_eq!(result.range(), &(5..10));
514515
}
515516

516517
#[test]
517518
fn suffix_zero_is_unsatisfiable() {
518-
let result = file_range(size(10), Some(HttpRange::Suffix(0)));
519+
let result = file_range(10, Some(HttpRange::Suffix(0)));
519520
assert!(result.is_err());
520521
}
521522

522523
#[test]
523524
fn suffix_exceeds_size_is_clamped() {
524-
let result = file_range(size(10), Some(HttpRange::Suffix(11))).unwrap();
525-
assert_eq!(result.range(), &(0..=9));
525+
let result = file_range(10, Some(HttpRange::Suffix(11))).unwrap();
526+
assert_eq!(result.range(), &(0..10));
526527
}
527528

528529
#[test]
529530
fn size_one_no_range() {
530-
let result = file_range(size(1), None).unwrap();
531-
assert_eq!(result.range(), &(0..=0));
531+
let result = file_range(1, None).unwrap();
532+
assert_eq!(result.range(), &(0..1));
532533
}
533534

534535
#[test]
535536
fn size_one_starting_point_zero() {
536-
let result = file_range(size(1), Some(HttpRange::StartingPoint(0))).unwrap();
537-
assert_eq!(result.range(), &(0..=0));
537+
let result = file_range(1, Some(HttpRange::StartingPoint(0))).unwrap();
538+
assert_eq!(result.range(), &(0..1));
538539
}
539540

540541
#[test]
541542
fn size_one_suffix_one() {
542-
let result = file_range(size(1), Some(HttpRange::Suffix(1))).unwrap();
543-
assert_eq!(result.range(), &(0..=0));
543+
let result = file_range(1, Some(HttpRange::Suffix(1))).unwrap();
544+
assert_eq!(result.range(), &(0..1));
544545
}
545546

546547
#[test]
547548
fn content_range_header_present_for_range_request() {
548-
let result = file_range(size(10), Some(HttpRange::StartingPoint(0))).unwrap();
549+
let result = file_range(10, Some(HttpRange::StartingPoint(0))).unwrap();
549550
let header = result.header().unwrap();
550551
assert_eq!(
551552
header,
552553
HttpContentRange::Bound(Bound::new(0..=9, Some(10)).unwrap())
553554
);
554555
}
556+
557+
#[test]
558+
fn size_zero_no_range() {
559+
let result = file_range(0, None).unwrap();
560+
assert!(result.header().is_none());
561+
assert_eq!(result.range(), &(0..0));
562+
}
563+
564+
#[test]
565+
fn size_zero_starting_point_is_unsatisfiable() {
566+
let result = file_range(0, Some(HttpRange::StartingPoint(0)));
567+
assert!(result.is_err());
568+
}
569+
570+
#[test]
571+
fn size_zero_range_is_unsatisfiable() {
572+
let range = HttpRange::Range(OrderedRange::new(0..=0).unwrap());
573+
let result = file_range(0, Some(range));
574+
assert!(result.is_err());
575+
}
576+
577+
#[test]
578+
fn size_zero_suffix_is_unsatisfiable() {
579+
let result = file_range(0, Some(HttpRange::Suffix(1)));
580+
assert!(result.is_err());
581+
}
555582
}
556583

557584
#[cfg(test)]
@@ -586,10 +613,11 @@ mod serve_file {
586613
}
587614

588615
#[test]
589-
fn empty_body_is_unsatisfiable() {
616+
fn empty_body_returns_empty_response() {
590617
let body = Bytes::new();
591-
let result = serve_file_with_http_range(body, None);
592-
assert!(result.is_err());
618+
let result = serve_file_with_http_range(body, None).unwrap();
619+
assert!(result.body().is_empty());
620+
assert!(result.header().is_none());
593621
}
594622

595623
#[test]

src/lib.rs

Lines changed: 14 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
#![cfg_attr(docsrs, feature(doc_cfg))]
22

3-
use std::{
4-
num::{NonZero, NonZeroU64},
5-
ops::RangeInclusive,
6-
};
3+
use std::ops::Range;
74

85
use bytes::Bytes;
96

@@ -22,17 +19,14 @@ pub fn serve_file_with_http_range(
2219
http_range: Option<HttpRange>,
2320
) -> Result<BodyRange<Bytes>, UnsatisfiableRange> {
2421
let size = u64::try_from(body.len()).expect("we do not support 128bit usize");
25-
let size = NonZeroU64::try_from(size).map_err(|_| {
26-
UnsatisfiableRange(HttpContentRange::Unsatisfiable(Unsatisfiable::new(size)))
27-
})?;
2822

2923
let content_range = file_range(size, http_range)?;
3024

31-
let start = usize::try_from(*content_range.range.start()).expect("u64 doesn't fit usize");
32-
let end = usize::try_from(*content_range.range.end()).expect("u64 doesn't fit usize");
25+
let start = usize::try_from(content_range.range.start).expect("u64 doesn't fit usize");
26+
let end = usize::try_from(content_range.range.end).expect("u64 doesn't fit usize");
3327

3428
Ok(BodyRange {
35-
body: body.slice(start..=end),
29+
body: body.slice(start..end),
3630
header: content_range.header,
3731
})
3832
}
@@ -41,31 +35,30 @@ pub fn serve_file_with_http_range(
4135
///
4236
/// [`HttpRange`]: crate::headers::range::HttpRange
4337
pub fn file_range(
44-
size: NonZero<u64>,
38+
size: u64,
4539
http_range: Option<HttpRange>,
4640
) -> Result<ContentRange, UnsatisfiableRange> {
47-
let size = size.get();
48-
4941
let Some(http_range) = http_range else {
5042
return Ok(ContentRange {
5143
header: None,
52-
range: 0..=size - 1,
44+
range: 0..size,
5345
});
5446
};
5547

5648
let range = match http_range {
57-
HttpRange::StartingPoint(start) if start < size => start..=size - 1,
49+
HttpRange::StartingPoint(start) if start < size => start..size,
5850
HttpRange::Range(range) if range.start() < size => {
59-
range.start()..=range.end().min(size - 1)
51+
range.start()..range.end().saturating_add(1).min(size)
6052
}
61-
HttpRange::Suffix(suffix) if suffix > 0 => size.saturating_sub(suffix)..=size - 1,
53+
HttpRange::Suffix(suffix) if suffix > 0 && size > 0 => size.saturating_sub(suffix)..size,
6254
_ => {
6355
let content_range = HttpContentRange::Unsatisfiable(Unsatisfiable::new(size));
6456
return Err(UnsatisfiableRange(content_range));
6557
}
6658
};
6759

68-
let content_range = HttpContentRange::Bound(Bound::new(range.clone(), Some(size)).unwrap());
60+
let content_range =
61+
HttpContentRange::Bound(Bound::new(range.start..=range.end - 1, Some(size)).unwrap());
6962

7063
Ok(ContentRange {
7164
header: Some(content_range),
@@ -107,7 +100,7 @@ impl<T> BodyRange<T> {
107100
#[derive(Debug, Clone, PartialEq, Eq)]
108101
pub struct ContentRange {
109102
header: Option<HttpContentRange>,
110-
range: RangeInclusive<u64>,
103+
range: Range<u64>,
111104
}
112105

113106
impl ContentRange {
@@ -117,8 +110,8 @@ impl ContentRange {
117110
self.header
118111
}
119112

120-
/// Returns a [`RangeInclusive`] of `u64` useful to manually slice the response body.
121-
pub fn range(&self) -> &RangeInclusive<u64> {
113+
/// Returns a [`Range`] of `u64` useful to manually slice the response body.
114+
pub fn range(&self) -> &Range<u64> {
122115
&self.range
123116
}
124117
}

0 commit comments

Comments
 (0)