Loading crates/s3s-fs/src/s3.rs +22 −35 Original line number Diff line number Diff line Loading @@ -162,43 +162,30 @@ impl S3 for FileSystem { let file_metadata = try_!(file.metadata().await); let last_modified = Timestamp::from(try_!(file_metadata.modified())); let content_length = { let file_len = file_metadata.len(); let content_len = match input.range { let content_length = match input.range { None => file_len, Some(Range::Int { first, last }) => { if first >= file_len { return Err(s3_error!(InvalidRange)); } if let Some(last) = last { if last > file_len { return Err(s3_error!(InvalidRange)); } Some(range) => { let file_range = range.check(file_len)?; file_range.end - file_range.start } }; let content_length_usize = try_!(usize::try_from(content_length)); let content_length_i64 = try_!(i64::try_from(content_length)); match input.range { Some(Range::Int { first, .. }) => { try_!(file.seek(io::SeekFrom::Start(first)).await); match last.and_then(|x| x.checked_add(1)) { Some(end) => end - first, None => file_len - first, } } Some(Range::Suffix { length }) => { if length > file_len || length > u64::MAX / 2 { return Err(s3_error!(InvalidRange)); } let neg_offset = length.numeric_cast::<i64>().neg(); try_!(file.seek(io::SeekFrom::End(neg_offset)).await); length } }; try_!(usize::try_from(content_len)) }; None => {} } let body = bytes_stream(ReaderStream::with_capacity(file, 4096), content_length); let body = bytes_stream(ReaderStream::with_capacity(file, 4096), content_length_usize); let object_metadata = self.load_metadata(&input.bucket, &input.key).await?; Loading @@ -206,7 +193,7 @@ impl S3 for FileSystem { let output = GetObjectOutput { body: Some(StreamingBlob::wrap(body)), content_length: try_!(i64::try_from(content_length)), content_length: content_length_i64, last_modified: Some(last_modified), metadata: object_metadata, e_tag: Some(format!("\"{md5_sum}\"")), Loading crates/s3s/src/dto/range.rs +58 −1 Original line number Diff line number Diff line Loading @@ -2,6 +2,10 @@ use crate::http; use crate::utils::from_ascii; use crate::S3Error; use crate::S3ErrorCode; use std::ops; use atoi::FromRadix10Checked; Loading @@ -11,7 +15,7 @@ use atoi::FromRadix10Checked; /// /// See <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2> #[allow(clippy::exhaustive_enums)] #[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Range { /// Int range in bytes. This range is **inclusive**. /// Loading Loading @@ -39,6 +43,14 @@ pub struct ParseRangeError { _priv: (), } /// [`Range`] #[derive(Debug, thiserror::Error)] #[error("RangeNotSatisfiable")] pub struct RangeNotSatisfiable { /// private place holder _priv: (), } impl Range { /// Parses `Range` from header /// # Errors Loading @@ -55,6 +67,9 @@ impl Range { // int range 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()) }; Loading @@ -65,6 +80,14 @@ impl Range { // int range inclusive 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) }) } Loading @@ -78,6 +101,40 @@ impl Range { Range::Suffix { length } => format!("bytes=-{length}"), } } pub fn check(&self, full_length: u64) -> Result<ops::Range<u64>, RangeNotSatisfiable> { let err = || RangeNotSatisfiable { _priv: () }; match *self { Range::Int { first, last } => match last { Some(last) => { if first > last || last >= full_length { return Err(err()); } // first <= last < full_length Ok(first..last + 1) } None => { if first > full_length { return Err(err()); } Ok(first..full_length) } }, Range::Suffix { length } => { if length > full_length { return Err(err()); } Ok((full_length - length)..full_length) } } } } impl From<RangeNotSatisfiable> for S3Error { #[inline] fn from(_: RangeNotSatisfiable) -> Self { S3ErrorCode::InvalidRange.into() } } impl http::TryFromHeaderValue for Range { Loading Loading
crates/s3s-fs/src/s3.rs +22 −35 Original line number Diff line number Diff line Loading @@ -162,43 +162,30 @@ impl S3 for FileSystem { let file_metadata = try_!(file.metadata().await); let last_modified = Timestamp::from(try_!(file_metadata.modified())); let content_length = { let file_len = file_metadata.len(); let content_len = match input.range { let content_length = match input.range { None => file_len, Some(Range::Int { first, last }) => { if first >= file_len { return Err(s3_error!(InvalidRange)); } if let Some(last) = last { if last > file_len { return Err(s3_error!(InvalidRange)); } Some(range) => { let file_range = range.check(file_len)?; file_range.end - file_range.start } }; let content_length_usize = try_!(usize::try_from(content_length)); let content_length_i64 = try_!(i64::try_from(content_length)); match input.range { Some(Range::Int { first, .. }) => { try_!(file.seek(io::SeekFrom::Start(first)).await); match last.and_then(|x| x.checked_add(1)) { Some(end) => end - first, None => file_len - first, } } Some(Range::Suffix { length }) => { if length > file_len || length > u64::MAX / 2 { return Err(s3_error!(InvalidRange)); } let neg_offset = length.numeric_cast::<i64>().neg(); try_!(file.seek(io::SeekFrom::End(neg_offset)).await); length } }; try_!(usize::try_from(content_len)) }; None => {} } let body = bytes_stream(ReaderStream::with_capacity(file, 4096), content_length); let body = bytes_stream(ReaderStream::with_capacity(file, 4096), content_length_usize); let object_metadata = self.load_metadata(&input.bucket, &input.key).await?; Loading @@ -206,7 +193,7 @@ impl S3 for FileSystem { let output = GetObjectOutput { body: Some(StreamingBlob::wrap(body)), content_length: try_!(i64::try_from(content_length)), content_length: content_length_i64, last_modified: Some(last_modified), metadata: object_metadata, e_tag: Some(format!("\"{md5_sum}\"")), Loading
crates/s3s/src/dto/range.rs +58 −1 Original line number Diff line number Diff line Loading @@ -2,6 +2,10 @@ use crate::http; use crate::utils::from_ascii; use crate::S3Error; use crate::S3ErrorCode; use std::ops; use atoi::FromRadix10Checked; Loading @@ -11,7 +15,7 @@ use atoi::FromRadix10Checked; /// /// See <https://www.rfc-editor.org/rfc/rfc9110.html#section-14.1.2> #[allow(clippy::exhaustive_enums)] #[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Range { /// Int range in bytes. This range is **inclusive**. /// Loading Loading @@ -39,6 +43,14 @@ pub struct ParseRangeError { _priv: (), } /// [`Range`] #[derive(Debug, thiserror::Error)] #[error("RangeNotSatisfiable")] pub struct RangeNotSatisfiable { /// private place holder _priv: (), } impl Range { /// Parses `Range` from header /// # Errors Loading @@ -55,6 +67,9 @@ impl Range { // int range 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()) }; Loading @@ -65,6 +80,14 @@ impl Range { // int range inclusive 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) }) } Loading @@ -78,6 +101,40 @@ impl Range { Range::Suffix { length } => format!("bytes=-{length}"), } } pub fn check(&self, full_length: u64) -> Result<ops::Range<u64>, RangeNotSatisfiable> { let err = || RangeNotSatisfiable { _priv: () }; match *self { Range::Int { first, last } => match last { Some(last) => { if first > last || last >= full_length { return Err(err()); } // first <= last < full_length Ok(first..last + 1) } None => { if first > full_length { return Err(err()); } Ok(first..full_length) } }, Range::Suffix { length } => { if length > full_length { return Err(err()); } Ok((full_length - length)..full_length) } } } } impl From<RangeNotSatisfiable> for S3Error { #[inline] fn from(_: RangeNotSatisfiable) -> Self { S3ErrorCode::InvalidRange.into() } } impl http::TryFromHeaderValue for Range { Loading