Loading crates/s3s-aws/src/conv/builtin.rs +1 −1 Original line number Diff line number Diff line Loading @@ -105,7 +105,7 @@ impl AwsConversion for s3s::dto::Range { } fn try_into_aws(x: Self) -> S3Result<Self::Target> { Ok(x.format_to_string()) Ok(x.to_header_string()) } } Loading crates/s3s-fs/src/s3.rs +5 −5 Original line number Diff line number Diff line Loading @@ -167,7 +167,7 @@ impl S3 for FileSystem { let file_len = file_metadata.len(); let content_len = match input.range { None => file_len, Some(Range::Normal { first, last }) => { Some(Range::Int { first, last }) => { if first >= file_len { return Err(s3_error!(InvalidRange)); } Loading @@ -184,15 +184,15 @@ impl S3 for FileSystem { None => file_len - first, } } Some(Range::Suffix { last }) => { if last > file_len || last > u64::MAX / 2 { Some(Range::Suffix { length }) => { if length > file_len || length > u64::MAX / 2 { return Err(s3_error!(InvalidRange)); } let neg_offset = last.numeric_cast::<i64>().neg(); let neg_offset = length.numeric_cast::<i64>().neg(); try_!(file.seek(io::SeekFrom::End(neg_offset)).await); last length } }; try_!(usize::try_from(content_len)) Loading crates/s3s/src/dto/range.rs +84 −86 Original line number Diff line number Diff line Loading @@ -3,23 +3,31 @@ use crate::http; use crate::utils::from_ascii; use atoi::FromRadix10Checked; /// HTTP Range header /// /// Amazon S3 doesn't support retrieving multiple ranges of data per GET request. /// /// See <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2> #[allow(clippy::exhaustive_enums, missing_copy_implementations)] #[derive(Debug, Clone)] #[allow(clippy::exhaustive_enums)] #[derive(Debug, Clone, PartialEq, Eq)] pub enum Range { /// Normal byte range Normal { /// first /// Int range in bytes. This range is **inclusive**. /// /// See <https://www.rfc-editor.org/rfc/rfc9110.html#rule.int-range> Int { /// first position first: u64, /// last /// last position last: Option<u64>, }, /// Suffix byte range /// Suffix range in bytes. /// /// See <https://www.rfc-editor.org/rfc/rfc9110.html#rule.suffix-range> Suffix { /// last last: u64, /// suffix length length: u64, }, } Loading @@ -36,53 +44,38 @@ impl Range { /// # Errors /// Returns an error if the header is invalid pub fn parse(header: &str) -> Result<Self, ParseRangeError> { /// nom parser fn nom_parse(input: &str) -> nom::IResult<&str, Range> { use nom::{ branch::alt, bytes::complete::tag, character::complete::digit1, combinator::{all_consuming, map, map_res, opt}, sequence::tuple, }; let normal_parser = map_res( tuple((map_res(digit1, str::parse::<u64>), tag("-"), opt(map_res(digit1, str::parse::<u64>)))), |ss: (u64, &str, Option<u64>)| { if let (first, Some(last)) = (ss.0, ss.2) { if first > last { return Err(ParseRangeError { _priv: () }); } } Ok(Range::Normal { first: ss.0, last: ss.2 }) }, ); let err = || ParseRangeError { _priv: () }; let s = header.strip_prefix("bytes=").ok_or_else(err)?.as_bytes(); let suffix_parser = map(tuple((tag("-"), map_res(digit1, str::parse::<u64>))), |ss: (&str, u64)| Range::Suffix { last: ss.1, }); if let [b'-', s @ ..] = s { // suffix range let length = parse_u64_full(s).ok_or_else(err)?; return Ok(Range::Suffix { length }); } let mut parser = all_consuming(tuple((tag("bytes="), alt((normal_parser, suffix_parser))))); // int range let (first, s) = parse_u64_once(s).ok_or_else(err)?; let (input, (_, ans)) = parser(input)?; let [b'-', s @ ..] = s else { return Err(err()) }; Ok((input, ans)) if s.is_empty() { // int range from return Ok(Range::Int { first, last: None }); } match nom_parse(header) { Err(_) => Err(ParseRangeError { _priv: () }), Ok((_, ans)) => Ok(ans), } // int range inclusive let last = parse_u64_full(s).ok_or_else(err)?; Ok(Range::Int { first, last: Some(last) }) } #[must_use] pub fn format_to_string(&self) -> String { pub fn to_header_string(&self) -> String { match self { Range::Normal { first, last } => match last { Range::Int { first, last } => match last { Some(last) => format!("bytes={first}-{last}"), None => format!("bytes={first}-"), }, Range::Suffix { last } => format!("bytes=-{last}"), Range::Suffix { length } => format!("bytes=-{length}"), } } } Loading @@ -96,52 +89,57 @@ impl http::TryFromHeaderValue for Range { } } 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 src = "bytes=0-499"; let result = Range::parse(src); assert!(matches!( result.unwrap(), Range::Normal { first: 0, last: Some(499) } )); } { let src = "bytes=0-499;"; let result = Range::parse(src); let _ = result.unwrap_err(); } { let src = "bytes=9500-"; let result = Range::parse(src); assert!(matches!(result.unwrap(), Range::Normal { first: 9500, last: None })); } { let src = "bytes=9500-0-"; let result = Range::parse(src); let _ = result.unwrap_err(); } { let src = "bytes=-500"; let result = Range::parse(src); assert!(matches!(result.unwrap(), Range::Suffix { last: 500 })); } { let src = "bytes=-500 "; let result = Range::parse(src); let _ = result.unwrap_err(); } { let src = "bytes=-1000000000000000000000000"; let result = Range::parse(src); let _ = result.unwrap_err(); 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(())), ]; for (input, expected) in cases.iter() { let output = Range::parse(input); match expected { Ok(expected) => assert_eq!(output.unwrap(), *expected), Err(_) => assert!(output.is_err()), } } } } Loading
crates/s3s-aws/src/conv/builtin.rs +1 −1 Original line number Diff line number Diff line Loading @@ -105,7 +105,7 @@ impl AwsConversion for s3s::dto::Range { } fn try_into_aws(x: Self) -> S3Result<Self::Target> { Ok(x.format_to_string()) Ok(x.to_header_string()) } } Loading
crates/s3s-fs/src/s3.rs +5 −5 Original line number Diff line number Diff line Loading @@ -167,7 +167,7 @@ impl S3 for FileSystem { let file_len = file_metadata.len(); let content_len = match input.range { None => file_len, Some(Range::Normal { first, last }) => { Some(Range::Int { first, last }) => { if first >= file_len { return Err(s3_error!(InvalidRange)); } Loading @@ -184,15 +184,15 @@ impl S3 for FileSystem { None => file_len - first, } } Some(Range::Suffix { last }) => { if last > file_len || last > u64::MAX / 2 { Some(Range::Suffix { length }) => { if length > file_len || length > u64::MAX / 2 { return Err(s3_error!(InvalidRange)); } let neg_offset = last.numeric_cast::<i64>().neg(); let neg_offset = length.numeric_cast::<i64>().neg(); try_!(file.seek(io::SeekFrom::End(neg_offset)).await); last length } }; try_!(usize::try_from(content_len)) Loading
crates/s3s/src/dto/range.rs +84 −86 Original line number Diff line number Diff line Loading @@ -3,23 +3,31 @@ use crate::http; use crate::utils::from_ascii; use atoi::FromRadix10Checked; /// HTTP Range header /// /// Amazon S3 doesn't support retrieving multiple ranges of data per GET request. /// /// See <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2> #[allow(clippy::exhaustive_enums, missing_copy_implementations)] #[derive(Debug, Clone)] #[allow(clippy::exhaustive_enums)] #[derive(Debug, Clone, PartialEq, Eq)] pub enum Range { /// Normal byte range Normal { /// first /// Int range in bytes. This range is **inclusive**. /// /// See <https://www.rfc-editor.org/rfc/rfc9110.html#rule.int-range> Int { /// first position first: u64, /// last /// last position last: Option<u64>, }, /// Suffix byte range /// Suffix range in bytes. /// /// See <https://www.rfc-editor.org/rfc/rfc9110.html#rule.suffix-range> Suffix { /// last last: u64, /// suffix length length: u64, }, } Loading @@ -36,53 +44,38 @@ impl Range { /// # Errors /// Returns an error if the header is invalid pub fn parse(header: &str) -> Result<Self, ParseRangeError> { /// nom parser fn nom_parse(input: &str) -> nom::IResult<&str, Range> { use nom::{ branch::alt, bytes::complete::tag, character::complete::digit1, combinator::{all_consuming, map, map_res, opt}, sequence::tuple, }; let normal_parser = map_res( tuple((map_res(digit1, str::parse::<u64>), tag("-"), opt(map_res(digit1, str::parse::<u64>)))), |ss: (u64, &str, Option<u64>)| { if let (first, Some(last)) = (ss.0, ss.2) { if first > last { return Err(ParseRangeError { _priv: () }); } } Ok(Range::Normal { first: ss.0, last: ss.2 }) }, ); let err = || ParseRangeError { _priv: () }; let s = header.strip_prefix("bytes=").ok_or_else(err)?.as_bytes(); let suffix_parser = map(tuple((tag("-"), map_res(digit1, str::parse::<u64>))), |ss: (&str, u64)| Range::Suffix { last: ss.1, }); if let [b'-', s @ ..] = s { // suffix range let length = parse_u64_full(s).ok_or_else(err)?; return Ok(Range::Suffix { length }); } let mut parser = all_consuming(tuple((tag("bytes="), alt((normal_parser, suffix_parser))))); // int range let (first, s) = parse_u64_once(s).ok_or_else(err)?; let (input, (_, ans)) = parser(input)?; let [b'-', s @ ..] = s else { return Err(err()) }; Ok((input, ans)) if s.is_empty() { // int range from return Ok(Range::Int { first, last: None }); } match nom_parse(header) { Err(_) => Err(ParseRangeError { _priv: () }), Ok((_, ans)) => Ok(ans), } // int range inclusive let last = parse_u64_full(s).ok_or_else(err)?; Ok(Range::Int { first, last: Some(last) }) } #[must_use] pub fn format_to_string(&self) -> String { pub fn to_header_string(&self) -> String { match self { Range::Normal { first, last } => match last { Range::Int { first, last } => match last { Some(last) => format!("bytes={first}-{last}"), None => format!("bytes={first}-"), }, Range::Suffix { last } => format!("bytes=-{last}"), Range::Suffix { length } => format!("bytes=-{length}"), } } } Loading @@ -96,52 +89,57 @@ impl http::TryFromHeaderValue for Range { } } 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 src = "bytes=0-499"; let result = Range::parse(src); assert!(matches!( result.unwrap(), Range::Normal { first: 0, last: Some(499) } )); } { let src = "bytes=0-499;"; let result = Range::parse(src); let _ = result.unwrap_err(); } { let src = "bytes=9500-"; let result = Range::parse(src); assert!(matches!(result.unwrap(), Range::Normal { first: 9500, last: None })); } { let src = "bytes=9500-0-"; let result = Range::parse(src); let _ = result.unwrap_err(); } { let src = "bytes=-500"; let result = Range::parse(src); assert!(matches!(result.unwrap(), Range::Suffix { last: 500 })); } { let src = "bytes=-500 "; let result = Range::parse(src); let _ = result.unwrap_err(); } { let src = "bytes=-1000000000000000000000000"; let result = Range::parse(src); let _ = result.unwrap_err(); 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(())), ]; for (input, expected) in cases.iter() { let output = Range::parse(input); match expected { Ok(expected) => assert_eq!(output.unwrap(), *expected), Err(_) => assert!(output.is_err()), } } } }