Commit b0b6878d authored by Nugine's avatar Nugine
Browse files

feat(s3s::dto::range): Range::check

parent 798339d9
Loading
Loading
Loading
Loading
+22 −35
Original line number Diff line number Diff line
@@ -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?;

@@ -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}\"")),
+58 −1
Original line number Diff line number Diff line
@@ -2,6 +2,10 @@

use crate::http;
use crate::utils::from_ascii;
use crate::S3Error;
use crate::S3ErrorCode;

use std::ops;

use atoi::FromRadix10Checked;

@@ -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**.
    ///
@@ -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
@@ -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()) };

@@ -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) })
    }

@@ -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 {