use crate::{
class,
date::HttpDate,
error::{ErrorKind, Result, WriteError, err},
etag::EntityTag,
limits::Limits,
num::parse_u64,
text::{skip_ows, trim_ows},
write::{CountSink, Sink, reserve, write_decimal_u64},
};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RangeSpec {
From(u64),
FromTo(u64, u64),
Suffix(u64),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ByteRange {
pub start: u64,
pub end: u64,
}
impl ByteRange {
#[must_use]
pub const fn len(&self) -> u64 {
if self.end < self.start {
0
} else {
(self.end - self.start).saturating_add(1)
}
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.end < self.start
}
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum RangeOutcome {
Ranges(usize),
Unsatisfiable,
Ignore,
NotBytes,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct RangeHeader<'a> {
unit: &'a [u8],
set: &'a [u8],
count: usize,
}
impl<'a> RangeHeader<'a> {
#[must_use]
pub fn is_bytes(&self) -> bool {
self.unit.eq_ignore_ascii_case(b"bytes")
}
#[must_use]
pub const fn unit(&self) -> &'a [u8] {
self.unit
}
pub fn specs(&self) -> impl Iterator<Item = RangeSpec> + use<'a> {
let set = if self.is_bytes() { self.set } else { &[] };
Elements { s: set }.filter_map(|e| spec(e).ok())
}
#[must_use]
pub const fn len(&self) -> usize {
self.count
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.count == 0
}
}
struct Elements<'a> {
s: &'a [u8],
}
impl<'a> Iterator for Elements<'a> {
type Item = &'a [u8];
fn next(&mut self) -> Option<&'a [u8]> {
loop {
if self.s.is_empty() {
return None;
}
let end = crate::scan::find_byte(b',', self.s).unwrap_or(self.s.len());
let e = trim_ows(self.s.get(..end).unwrap_or(&[]));
self.s = self.s.get(end + 1..).unwrap_or(&[]);
if !e.is_empty() {
return Some(e);
}
}
}
}
fn refused(v: &[u8], at: usize, kind: ErrorKind) -> crate::ParseError {
match v.get(at) {
Some(&b) if crate::scan::is_control(b) => {
crate::ParseError::new(crate::scan::control_kind(b), at)
}
_ => crate::ParseError::new(kind, at),
}
}
fn position(digits: &[u8]) -> Result<u64> {
let mut n = 0u64;
for (i, &b) in digits.iter().enumerate() {
if !b.is_ascii_digit() {
return Err(refused(digits, i, ErrorKind::InvalidNumber));
}
n = n.saturating_mul(10).saturating_add(u64::from(b - b'0'));
}
Ok(n)
}
fn numeral_above(a: &[u8], b: &[u8]) -> bool {
let strip = |s: &'_ [u8]| -> usize { s.iter().take_while(|&&d| d == b'0').count() };
let a = a.get(strip(a)..).unwrap_or(&[]);
let b = b.get(strip(b)..).unwrap_or(&[]);
a.len() > b.len() || (a.len() == b.len() && a > b)
}
fn spec(e: &[u8]) -> Result<RangeSpec> {
let Some(dash) = e.iter().position(|&b| b == b'-') else {
let at = e.iter().position(|&b| crate::scan::is_control(b));
return Err(refused(e, at.unwrap_or(e.len()), ErrorKind::InvalidRange));
};
let (first, last) = (
e.get(..dash).unwrap_or(&[]),
e.get(dash + 1..).unwrap_or(&[]),
);
match (first.is_empty(), last.is_empty()) {
(true, true) => err(ErrorKind::InvalidRange, 0),
(true, false) => Ok(RangeSpec::Suffix(
position(last).map_err(|x| x.shifted(dash + 1))?,
)),
(false, true) => Ok(RangeSpec::From(position(first)?)),
(false, false) => {
let f = position(first)?;
let l = position(last).map_err(|x| x.shifted(dash + 1))?;
if numeral_above(first, last) {
return err(ErrorKind::InvalidRange, 0);
}
Ok(RangeSpec::FromTo(f, l))
}
}
}
pub fn parse<'a>(value: &'a [u8], limits: &Limits) -> Result<RangeHeader<'a>> {
let lead = skip_ows(value);
let v = trim_ows(value);
let unit_len = v.iter().take_while(|&&b| class::is_tchar(b)).count();
if unit_len == 0 {
return Err(refused(v, 0, ErrorKind::NotToken).shifted(lead));
}
if v.get(unit_len) != Some(&b'=') {
return Err(refused(v, unit_len, ErrorKind::InvalidRange).shifted(lead));
}
let unit = v.get(..unit_len).unwrap_or(&[]);
let set_at = unit_len + 1;
let set_text = v.get(set_at..).unwrap_or(&[]);
if set_text.first().is_some_and(|&b| crate::text::is_ows(b)) {
return err(ErrorKind::InvalidRange, lead + set_at);
}
let bytes = unit.eq_ignore_ascii_case(b"bytes");
if set_text.is_empty() {
return err(ErrorKind::InvalidRange, lead + set_at);
}
let (mut i, mut count, mut empty) = (0usize, 0usize, 0usize);
while i <= set_text.len() {
let rest = set_text.get(i..).unwrap_or(&[]);
let end = crate::scan::find_byte(b',', rest).unwrap_or(rest.len());
let raw = rest.get(..end).unwrap_or(&[]);
let e = trim_ows(raw);
let at = lead + set_at + i + skip_ows(raw);
if e.is_empty() {
empty += 1;
if empty > usize::from(limits.empty_elements) {
return err(ErrorKind::TooManyEmptyElements, at);
}
} else {
count += 1;
if count > usize::from(limits.list_members) {
return err(ErrorKind::TooManyMembers, at);
}
if bytes {
spec(e).map(|_| ()).map_err(|x| x.shifted(at))?;
} else if let Some(j) = e.iter().position(|&b| !class::is_vchar(b)) {
return Err(refused(e, j, ErrorKind::InvalidRange).shifted(at));
}
}
i += end + 1;
}
if count == 0 {
return err(ErrorKind::InvalidRange, lead + set_at);
}
Ok(RangeHeader {
unit,
set: set_text,
count,
})
}
#[must_use]
pub fn resolve(
h: &RangeHeader<'_>,
length: u64,
coalesce: bool,
limits: &Limits,
out: &mut [ByteRange],
) -> RangeOutcome {
if !h.is_bytes() {
return RangeOutcome::NotBytes;
}
if h.len() > usize::from(limits.ranges) {
return RangeOutcome::Ignore;
}
let mut n = 0usize;
let mut descents = 0usize;
let mut previous_start: Option<u64> = None;
for s in h.specs() {
let Some(r) = absolute(s, length) else {
continue;
};
if previous_start.is_some_and(|p| r.start < p) {
descents += 1;
}
previous_start = Some(r.start);
match out.get_mut(n) {
Some(slot) => *slot = r,
None => return RangeOutcome::Ignore,
}
n += 1;
}
if n == 0 {
let empty_suffix = |s| matches!(s, RangeSpec::Suffix(1..));
if length == 0 && h.specs().any(empty_suffix) {
return RangeOutcome::Ignore;
}
return RangeOutcome::Unsatisfiable;
}
let resolved = out.get_mut(..n).unwrap_or(&mut []);
let overlapping = resolved
.iter()
.enumerate()
.filter(|&(i, a)| {
resolved
.iter()
.enumerate()
.any(|(j, b)| i != j && a.start <= b.end && b.start <= a.end)
})
.count();
if overlapping > 2 || descents > 2 {
return RangeOutcome::Ignore;
}
if coalesce {
for i in 1..resolved.len() {
let mut j = i;
while j > 0 {
let (Some(&a), Some(&b)) = (resolved.get(j - 1), resolved.get(j)) else {
break;
};
if a.start <= b.start {
break;
}
resolved.swap(j - 1, j);
j -= 1;
}
}
let mut merged = 0usize;
for i in 0..resolved.len() {
let Some(&r) = resolved.get(i) else { break };
match merged.checked_sub(1).and_then(|m| resolved.get_mut(m)) {
Some(last) if r.start <= last.end.saturating_add(1) => {
last.end = last.end.max(r.end);
}
_ => {
if let Some(slot) = resolved.get_mut(merged) {
*slot = r;
}
merged += 1;
}
}
}
n = merged;
}
RangeOutcome::Ranges(n)
}
fn absolute(s: RangeSpec, length: u64) -> Option<ByteRange> {
let last = length.checked_sub(1)?;
match s {
RangeSpec::From(f) if f <= last => Some(ByteRange {
start: f,
end: last,
}),
RangeSpec::FromTo(f, l) if f <= last => Some(ByteRange {
start: f,
end: l.min(last),
}),
RangeSpec::Suffix(n) if n > 0 => Some(ByteRange {
start: length - n.min(length),
end: last,
}),
_ => None,
}
}
fn digits(s: &[u8]) -> (Option<u64>, &[u8]) {
let (mut n, mut rest, mut len) = (0u64, s, 0usize);
while let [d @ b'0'..=b'9', tail @ ..] = rest {
if len == 19 {
break;
}
n = n * 10 + u64::from(*d - b'0');
rest = tail;
len += 1;
}
((len > 0).then_some(n), rest)
}
fn one_spec(value: &[u8]) -> Option<RangeSpec> {
let (first, rest) = digits(value.strip_prefix(b"bytes=")?);
let [b'-', rest @ ..] = rest else {
return None;
};
let (last, rest) = digits(rest);
if !rest.is_empty() {
return None;
}
match (first, last) {
(Some(f), Some(l)) if f <= l => Some(RangeSpec::FromTo(f, l)),
(Some(f), None) => Some(RangeSpec::From(f)),
(None, Some(l)) => Some(RangeSpec::Suffix(l)),
_ => None,
}
}
pub fn byte_ranges(value: &[u8], length: u64, out: &mut [ByteRange]) -> Result<RangeOutcome> {
if let Some(s) = one_spec(value) {
return Ok(match (absolute(s, length), out.first_mut()) {
(None, _) if length == 0 && matches!(s, RangeSpec::Suffix(1..)) => RangeOutcome::Ignore,
(None, _) => RangeOutcome::Unsatisfiable,
(Some(r), Some(slot)) => {
*slot = r;
RangeOutcome::Ranges(1)
}
(Some(_), None) => RangeOutcome::Ignore,
});
}
let h = parse(value, &Limits::DEFAULT)?;
Ok(resolve(&h, length, false, &Limits::DEFAULT, out))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ContentRange {
Satisfied {
range: ByteRange,
complete: Option<u64>,
},
Unsatisfied {
complete: u64,
},
}
pub fn parse_content_range(value: &[u8]) -> Result<ContentRange> {
let lead = skip_ows(value);
let v = trim_ows(value);
inner_content_range(v).map_err(|e| e.shifted(lead))
}
fn content_number(s: &[u8]) -> Result<u64> {
parse_u64(s).map_err(|e| match e.kind() {
ErrorKind::Empty => crate::ParseError::new(ErrorKind::InvalidRange, 0),
ErrorKind::InvalidNumber => refused(s, e.offset(), ErrorKind::InvalidNumber),
_ => e,
})
}
fn inner_content_range(v: &[u8]) -> Result<ContentRange> {
let unit = v.get(..5).unwrap_or(&[]);
if !unit.eq_ignore_ascii_case(b"bytes") || v.get(5) != Some(&b' ') {
let at = v.iter().take(6).position(|&b| crate::scan::is_control(b));
return Err(refused(v, at.unwrap_or(0), ErrorKind::InvalidRange));
}
let rest = v.get(6..).unwrap_or(&[]);
let Some(slash) = rest.iter().position(|&b| b == b'/') else {
return err(ErrorKind::InvalidRange, v.len());
};
let (range_text, complete_text) = (
rest.get(..slash).unwrap_or(&[]),
rest.get(slash + 1..).unwrap_or(&[]),
);
let complete_at = 6 + slash + 1;
if range_text == b"*" {
let complete = content_number(complete_text).map_err(|e| e.shifted(complete_at))?;
return Ok(ContentRange::Unsatisfied { complete });
}
let complete = if complete_text == b"*" {
None
} else {
Some(content_number(complete_text).map_err(|e| e.shifted(complete_at))?)
};
let Some(dash) = range_text.iter().position(|&b| b == b'-') else {
return err(ErrorKind::InvalidRange, 6);
};
let first = content_number(range_text.get(..dash).unwrap_or(&[])).map_err(|e| e.shifted(6))?;
let last = content_number(range_text.get(dash + 1..).unwrap_or(&[]))
.map_err(|e| e.shifted(6 + dash + 1))?;
if last < first || complete.is_some_and(|c| last >= c) {
return err(ErrorKind::InvalidRange, 6);
}
Ok(ContentRange::Satisfied {
range: ByteRange {
start: first,
end: last,
},
complete,
})
}
fn put_content_range(
r: ByteRange,
complete: Option<u64>,
out: &mut impl Sink,
) -> core::result::Result<(), WriteError> {
out.put(b"bytes ")?;
write_decimal_u64(r.start, out)?;
out.put(b"-")?;
write_decimal_u64(r.end, out)?;
out.put(b"/")?;
match complete {
Some(c) => write_decimal_u64(c, out)?,
None => out.put(b"*")?,
}
Ok(())
}
pub fn write_content_range(
r: ByteRange,
complete: Option<u64>,
out: &mut impl Sink,
) -> core::result::Result<(), WriteError> {
if r.end < r.start || complete.is_some_and(|c| r.end >= c) {
return Err(WriteError::OutOfRange);
}
let mut count = CountSink::new();
put_content_range(r, complete, &mut count)?;
reserve(out, count.count())?;
put_content_range(r, complete, out)
}
pub fn write_unsatisfied(
complete: u64,
out: &mut impl Sink,
) -> core::result::Result<(), WriteError> {
let mut count = CountSink::new();
count.put(b"bytes */")?;
write_decimal_u64(complete, &mut count)?;
reserve(out, count.count())?;
out.put(b"bytes */")?;
write_decimal_u64(complete, out)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum IfRange<'a> {
Date(HttpDate),
Tag(EntityTag<'a>),
}
impl IfRange<'_> {
#[must_use]
pub fn matches(
&self,
current_tag: Option<&EntityTag<'_>>,
last_modified: Option<HttpDate>,
) -> bool {
match self {
IfRange::Tag(t) => current_tag.is_some_and(|c| t.strong_eq(c)),
IfRange::Date(d) => last_modified == Some(*d),
}
}
}
pub fn parse_if_range_at(value: &[u8], now: i64) -> Result<IfRange<'_>> {
let v = trim_ows(value);
if v.starts_with(b"W/") || v.starts_with(b"\"") {
EntityTag::parse(value).map(IfRange::Tag)
} else {
HttpDate::parse_at(value, now).map(IfRange::Date)
}
}
pub fn parse_if_range(value: &[u8]) -> Result<IfRange<'_>> {
let v = trim_ows(value);
if v.starts_with(b"W/") || v.starts_with(b"\"") {
EntityTag::parse(value).map(IfRange::Tag)
} else {
match HttpDate::parse_detailed(value, 0)? {
(_, crate::DateFormat::Rfc850) => err(ErrorKind::InvalidDate, skip_ows(value)),
(d, _) => Ok(IfRange::Date(d)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn resolve_str(
v: &[u8],
length: u64,
coalesce: bool,
) -> (RangeOutcome, std::vec::Vec<ByteRange>) {
let mut out = [ByteRange { start: 0, end: 0 }; 32];
let h = parse(v, &Limits::DEFAULT).unwrap();
let o = resolve(&h, length, coalesce, &Limits::DEFAULT, &mut out);
let n = if let RangeOutcome::Ranges(n) = o {
n
} else {
0
};
(o, out[..n].to_vec())
}
#[test]
fn test_range_parse_edge_cases() {
let l = Limits::DEFAULT;
let k = |v: &[u8]| parse(v, &l).unwrap_err().kind();
assert!(parse(b"bytes=0-0", &l).is_ok());
assert!(parse(b"BYTES=0-1", &l).unwrap().is_bytes());
assert_eq!(k(b"bytes=5-4"), ErrorKind::InvalidRange);
assert_eq!(k(b"bytes = 0-1"), ErrorKind::InvalidRange);
assert_eq!(k(b"bytes= 0-1"), ErrorKind::InvalidRange);
assert_eq!(k(b"bytes=0 -1"), ErrorKind::InvalidNumber);
assert_eq!(k(b"bytes="), ErrorKind::InvalidRange);
assert_eq!(k(b"bytes=-"), ErrorKind::InvalidRange);
assert_eq!(
parse(b"bytes=0-18446744073709551616", &l)
.unwrap()
.specs()
.next(),
Some(RangeSpec::FromTo(0, u64::MAX))
);
assert_eq!(
k(b"bytes=18446744073709551617-18446744073709551616"),
ErrorKind::InvalidRange
);
assert!(parse(b"bytes=0-18446744073709551615", &l).is_ok());
assert!(parse(b"bytes=0-1,", &l).is_ok());
assert_eq!(k(b"=0-1"), ErrorKind::NotToken);
assert_eq!(k(b"bytes"), ErrorKind::InvalidRange);
assert!(parse(b"items=0-5", &l).is_ok());
assert_eq!(k(b"items=a b"), ErrorKind::InvalidRange);
assert_eq!(parse(b"items=0-5", &l).unwrap().specs().count(), 0);
}
#[test]
fn test_range_resolution() {
assert_eq!(
resolve_str(b"bytes=0-", 10, false).1,
[ByteRange { start: 0, end: 9 }]
);
assert_eq!(
resolve_str(b"bytes=-0", 10, false).0,
RangeOutcome::Unsatisfiable
);
assert_eq!(resolve_str(b"bytes=-1", 0, false).0, RangeOutcome::Ignore);
assert_eq!(
resolve_str(b"bytes=-20", 10, false).1,
[ByteRange { start: 0, end: 9 }]
);
assert_eq!(
resolve_str(b"bytes=5-100", 10, false).1,
[ByteRange { start: 5, end: 9 }]
);
assert_eq!(
resolve_str(b"bytes=10-", 10, false).0,
RangeOutcome::Unsatisfiable
);
assert_eq!(
resolve_str(b"bytes=10-, 0-0", 10, false).1,
[ByteRange { start: 0, end: 0 }]
);
assert_eq!(
resolve_str(b"bytes=0-18446744073709551615", u64::MAX, false).1[0].len(),
u64::MAX
);
}
fn many(n: u64) -> std::vec::Vec<u8> {
use std::fmt::Write as _;
let mut text = std::string::String::from("bytes=");
for i in 0..n {
let _ = write!(text, "{}-{},", i * 10, i * 10 + 1);
}
text.into_bytes()
}
#[test]
fn test_range_17_ranges_ignored() {
assert_eq!(
resolve_str(&many(16), 1000, false).0,
RangeOutcome::Ranges(16)
);
assert_eq!(resolve_str(&many(17), 1000, false).0, RangeOutcome::Ignore);
}
#[test]
fn test_range_three_overlapping_ignored() {
assert_eq!(
resolve_str(b"bytes=0-10, 5-15", 100, false).0,
RangeOutcome::Ranges(2)
);
assert_eq!(
resolve_str(b"bytes=0-10, 5-15, 8-20", 100, false).0,
RangeOutcome::Ignore
);
assert_eq!(
resolve_str(b"bytes=50-60, 40-45, 30-35, 20-25", 100, false).0,
RangeOutcome::Ignore
);
assert_eq!(
resolve_str(b"bytes=50-60, 40-45, 30-35", 100, false).0,
RangeOutcome::Ranges(3)
);
}
#[test]
fn test_range_coalesce() {
let (o, r) = resolve_str(b"bytes=40-49, 0-10, 11-20, 5-6", 100, true);
assert_eq!(o, RangeOutcome::Ranges(2));
assert_eq!(
r,
[
ByteRange { start: 0, end: 20 },
ByteRange { start: 40, end: 49 }
]
);
}
#[test]
fn test_content_range_cases() {
assert!(parse_content_range(b"bytes 0-0/1").is_ok());
assert_eq!(
parse_content_range(b"bytes 0-0/*").unwrap(),
ContentRange::Satisfied {
range: ByteRange { start: 0, end: 0 },
complete: None
}
);
assert_eq!(
parse_content_range(b"bytes */0").unwrap(),
ContentRange::Unsatisfied { complete: 0 }
);
for bad in [
&b"bytes 1-0/5"[..],
b"bytes 0-5/5",
b"bytes0-1/2",
b"items 0-1/2",
b"bytes 0-1",
b"bytes -1/2",
b"bytes */*",
] {
assert!(
parse_content_range(bad).is_err(),
"{}",
std::string::String::from_utf8_lossy(bad)
);
}
}
#[test]
fn test_content_range_writers() {
let mut buf = [0u8; 64];
let mut out = crate::SliceSink::new(&mut buf);
assert_eq!(
write_content_range(ByteRange { start: 5, end: 4 }, None, &mut out),
Err(WriteError::OutOfRange)
);
assert_eq!(
write_content_range(ByteRange { start: 0, end: 5 }, Some(5), &mut out),
Err(WriteError::OutOfRange)
);
write_content_range(ByteRange { start: 0, end: 4 }, None, &mut out).unwrap();
assert_eq!(out.written(), b"bytes 0-4/*");
assert_eq!(
parse_content_range(out.written()).unwrap(),
ContentRange::Satisfied {
range: ByteRange { start: 0, end: 4 },
complete: None
}
);
let mut small = [0u8; 8];
let mut out = crate::SliceSink::new(&mut small);
assert_eq!(write_unsatisfied(1, &mut out), Err(WriteError::Full));
assert!(out.is_empty());
}
#[test]
fn test_if_range() {
let d = HttpDate::from_unix(784_111_777).unwrap();
let date = parse_if_range(b"Sun, 06 Nov 1994 08:49:37 GMT").unwrap();
assert!(date.matches(None, Some(d)));
assert!(!date.matches(None, None));
assert!(parse_if_range(b"Sunday, 06-Nov-94 08:49:37 GMT").is_err());
let t = EntityTag::strong(b"a").unwrap();
assert!(!parse_if_range(b"\"b\"").unwrap().matches(Some(&t), Some(d)));
}
}