use crate::S3Error;
use crate::S3ErrorCode;
use crate::http;
use std::ops;
use atoi::FromRadix10Checked;
use stdx::str::StrExt;
#[allow(clippy::exhaustive_enums)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Range {
Int {
first: u64,
last: Option<u64>,
},
Suffix {
length: u64,
},
}
#[derive(Debug, thiserror::Error)]
#[error("ParseRangeError")]
pub struct ParseRangeError {
_priv: (),
}
#[derive(Debug, thiserror::Error)]
#[error("RangeNotSatisfiable")]
pub struct RangeNotSatisfiable {
_priv: (),
}
impl Range {
pub fn parse(header: &str) -> Result<Self, ParseRangeError> {
let err = || ParseRangeError { _priv: () };
let s = header.strip_prefix("bytes=").ok_or_else(err)?.as_bytes();
if let [b'-', s @ ..] = s {
let length = parse_u64_full(s).ok_or_else(err)?;
return Ok(Range::Suffix { length });
}
let (first, s) = parse_u64_once(s).ok_or_else(err)?;
if first > (i64::MAX as u64) {
return Err(err());
}
let [b'-', s @ ..] = s else { return Err(err()) };
if s.is_empty() {
return Ok(Range::Int { first, last: None });
}
let last = parse_u64_full(s).ok_or_else(err)?;
if last > (i64::MAX as u64) {
return Err(err());
}
if first > last {
return Err(err());
}
Ok(Range::Int { first, last: Some(last) })
}
#[must_use]
pub fn to_header_string(&self) -> String {
match self {
Range::Int { first, last } => match last {
Some(last) => format!("bytes={first}-{last}"),
None => format!("bytes={first}-"),
},
Range::Suffix { length } => format!("bytes=-{length}"),
}
}
#[allow(clippy::range_plus_one)] pub fn check(&self, full_length: u64) -> Result<ops::Range<u64>, RangeNotSatisfiable> {
let err = || RangeNotSatisfiable { _priv: () };
match *self {
Range::Int { first, last } => {
if first >= full_length {
return Err(err());
}
match last {
Some(last) => {
let last = last.min(full_length - 1);
if first > last {
return Err(err());
}
Ok(first..last + 1)
}
None => Ok(first..full_length),
}
}
Range::Suffix { length } => {
if length == 0 {
return Err(err());
}
let length = length.min(full_length);
Ok((full_length - length)..full_length)
}
}
}
}
impl From<RangeNotSatisfiable> for S3Error {
#[inline]
fn from(_: RangeNotSatisfiable) -> Self {
S3ErrorCode::InvalidRange.into()
}
}
impl http::TryFromHeaderValue for Range {
type Error = ParseRangeError;
fn try_from_header_value(val: &http::HeaderValue) -> Result<Self, Self::Error> {
let header = str::from_ascii_simd(val.as_bytes()).map_err(|_| ParseRangeError { _priv: () })?;
Self::parse(header)
}
}
fn parse_u64_full(s: &[u8]) -> Option<u64> {
match u64::from_radix_10_checked(s) {
(Some(x), pos) if pos == s.len() => Some(x),
_ => None,
}
}
fn parse_u64_once(s: &[u8]) -> Option<(u64, &[u8])> {
match u64::from_radix_10_checked(s) {
(Some(x), pos) if pos > 0 => Some((x, &s[pos..])),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn range_int_inclusive(first: u64, last: u64) -> Range {
Range::Int { first, last: Some(last) }
}
fn range_int_from(first: u64) -> Range {
Range::Int { first, last: None }
}
fn range_suffix(length: u64) -> Range {
Range::Suffix { length }
}
#[test]
fn byte_range() {
let cases = [
("bytes=0-499", Ok(range_int_inclusive(0, 499))),
("bytes=0-499;", Err(())),
("bytes=9500-", Ok(range_int_from(9500))),
("bytes=9500-0-", Err(())),
("bytes=9500", Err(())),
("bytes=0-0", Ok(range_int_inclusive(0, 0))),
("bytes=-500", Ok(range_suffix(500))),
("bytes=-500 ", Err(())),
("bytes=-+500", Err(())),
("bytes=-1000000000000000000000000", Err(())),
("bytes=-0", Ok(range_suffix(0))),
("bytes=-000", Ok(range_suffix(0))),
];
for (input, expected) in &cases {
let output = Range::parse(input);
match expected {
Ok(expected) => assert_eq!(output.unwrap(), *expected),
Err(()) => assert!(output.is_err()),
}
}
}
#[test]
fn satisfiable() {
let cases = [
(10000, range_int_from(9500), Ok(9500..10000)),
(10000, range_int_from(10000), Err(())),
(10000, range_int_inclusive(0, 499), Ok(0..500)),
(10000, range_int_inclusive(0, 0), Ok(0..1)),
(10000, range_int_inclusive(9500, 50000), Ok(9500..10000)),
(10000, range_int_inclusive(10000, 10000), Err(())),
(10000, range_suffix(500), Ok(9500..10000)),
(10000, range_suffix(10000), Ok(0..10000)),
(10000, range_suffix(0), Err(())),
(0, range_int_from(0), Err(())),
(0, range_suffix(1), Ok(0..0)),
(0, range_suffix(0), Err(())),
];
for &(full_length, ref range, ref expected) in &cases {
let output = range.check(full_length);
match expected {
Ok(expected) => assert_eq!(output.unwrap(), *expected),
Err(()) => assert!(output.is_err(), "{full_length:?}, {range:?}"),
}
}
}
}