Skip to main content

opendal_core/raw/http_util/
bytes_range.rs

1// Licensed to the Apache Software Foundation (ASF) under one
2// or more contributor license agreements.  See the NOTICE file
3// distributed with this work for additional information
4// regarding copyright ownership.  The ASF licenses this file
5// to you under the Apache License, Version 2.0 (the
6// "License"); you may not use this file except in compliance
7// with the License.  You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing,
12// software distributed under the License is distributed on an
13// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14// KIND, either express or implied.  See the License for the
15// specific language governing permissions and limitations
16// under the License.
17
18use std::fmt::Debug;
19use std::fmt::Display;
20use std::fmt::Formatter;
21use std::ops::Bound;
22use std::ops::Range;
23use std::ops::RangeBounds;
24use std::str::FromStr;
25
26use crate::*;
27
28/// BytesRange(offset, size) carries a range of content.
29///
30/// BytesRange implements `ToString` which can be used as `Range` HTTP header directly.
31///
32/// `<unit>` should always be `bytes`.
33///
34/// ```text
35/// Range: bytes=<range-start>-
36/// Range: bytes=<range-start>-<range-end>
37/// ```
38///
39/// # Notes
40///
41/// We don't support tailing read like `Range: bytes=-<range-end>`
42#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)]
43pub struct BytesRange(
44    /// Offset of the range.
45    u64,
46    /// Size of the range.
47    Option<u64>,
48);
49
50impl BytesRange {
51    /// Create a new `BytesRange`
52    ///
53    /// It better to use `BytesRange::from(1024..2048)` to construct.
54    ///
55    /// # Note
56    ///
57    /// The behavior for `None` and `Some` of `size` is different.
58    ///
59    /// - size=None => `bytes=<offset>-`, read from `<offset>` until the end
60    /// - size=Some(1024) => `bytes=<offset>-<offset + 1024>`, read 1024 bytes starting from the `<offset>`
61    pub fn new(offset: u64, size: Option<u64>) -> Self {
62        BytesRange(offset, size)
63    }
64
65    /// Get offset of BytesRange.
66    pub fn offset(&self) -> u64 {
67        self.0
68    }
69
70    /// Get size of BytesRange.
71    pub fn size(&self) -> Option<u64> {
72        self.1
73    }
74
75    /// Advance the range by `n` bytes.
76    ///
77    /// # Panics
78    ///
79    /// Panic if advancing the offset would overflow or if input `n` is larger
80    /// than the size of the range.
81    pub fn advance(&mut self, n: u64) {
82        self.0 = self
83            .0
84            .checked_add(n)
85            .expect("BytesRange::advance overflow: offset + n exceeds u64::MAX");
86        self.1 = self.1.map(|size| {
87            size.checked_sub(n)
88                .expect("BytesRange::advance underflow: n exceeds range size")
89        });
90    }
91
92    /// Check if this range is full of this content.
93    ///
94    /// If this range is full, we don't need to specify it in http request.
95    pub fn is_full(&self) -> bool {
96        self.0 == 0 && self.1.is_none()
97    }
98
99    /// Convert bytes range into Range header.
100    pub fn to_header(&self) -> String {
101        format!("bytes={self}")
102    }
103
104    /// Convert bytes range into rust range.
105    pub fn to_range(&self) -> impl RangeBounds<u64> {
106        (
107            Bound::Included(self.0),
108            match self.1 {
109                Some(size) => Bound::Excluded(
110                    self.0
111                        .checked_add(size)
112                        .expect("BytesRange::to_range overflow: offset + size exceeds u64::MAX"),
113                ),
114                None => Bound::Unbounded,
115            },
116        )
117    }
118
119    /// Convert bytes range into rust range with usize.
120    pub fn to_range_as_usize(self) -> impl RangeBounds<usize> {
121        let offset: usize = self
122            .0
123            .try_into()
124            .expect("BytesRange::to_range_as_usize: offset exceeds usize::MAX");
125        (
126            Bound::Included(offset),
127            match self.1 {
128                Some(size) => {
129                    let end: usize = self
130                        .0
131                        .checked_add(size)
132                        .expect(
133                            "BytesRange::to_range_as_usize overflow: offset + size exceeds u64::MAX",
134                        )
135                        .try_into()
136                        .expect(
137                            "BytesRange::to_range_as_usize: offset + size exceeds usize::MAX",
138                        );
139                    Bound::Excluded(end)
140                }
141                None => Bound::Unbounded,
142            },
143        )
144    }
145
146    /// Convert bytes range into a checked content slice range.
147    pub fn to_content_range(self, content_length: usize) -> Result<Range<usize>> {
148        if self.1 == Some(0) {
149            return Ok(0..0);
150        }
151
152        if self.0 >= content_length as u64 && self.1.is_none() {
153            return Ok(content_length..content_length);
154        }
155
156        let offset: usize = self.0.try_into().map_err(|_| {
157            Error::new(ErrorKind::RangeNotSatisfied, "range exceeds content length")
158                .with_operation("BytesRange::to_content_range")
159                .with_context("offset", self.0)
160                .with_context("content_length", content_length)
161        })?;
162
163        match self.1 {
164            Some(size) => {
165                let end = self
166                    .0
167                    .checked_add(size)
168                    .ok_or_else(|| {
169                        Error::new(ErrorKind::RangeNotSatisfied, "range exceeds content length")
170                            .with_operation("BytesRange::to_content_range")
171                            .with_context("offset", self.0)
172                            .with_context("size", size)
173                            .with_context("content_length", content_length)
174                    })?
175                    .try_into()
176                    .map_err(|_| {
177                        Error::new(ErrorKind::RangeNotSatisfied, "range exceeds content length")
178                            .with_operation("BytesRange::to_content_range")
179                            .with_context("offset", self.0)
180                            .with_context("size", size)
181                            .with_context("content_length", content_length)
182                    })?;
183
184                if end > content_length {
185                    return Err(Error::new(
186                        ErrorKind::RangeNotSatisfied,
187                        "range exceeds content length",
188                    )
189                    .with_operation("BytesRange::to_content_range")
190                    .with_context("offset", self.0)
191                    .with_context("size", size)
192                    .with_context("content_length", content_length));
193                }
194
195                Ok(offset..end)
196            }
197            None => Ok(offset..content_length),
198        }
199    }
200}
201
202impl Display for BytesRange {
203    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
204        match self.1 {
205            None => write!(f, "{}-", self.0),
206            // A zero-size range can't be represented as a valid HTTP Range.
207            Some(0) => Err(std::fmt::Error),
208            Some(size) => write!(
209                f,
210                "{}-{}",
211                self.0,
212                self.0.checked_add(size - 1).ok_or(std::fmt::Error)?
213            ),
214        }
215    }
216}
217
218impl FromStr for BytesRange {
219    type Err = Error;
220
221    fn from_str(value: &str) -> Result<Self> {
222        let s = value.strip_prefix("bytes=").ok_or_else(|| {
223            Error::new(ErrorKind::Unexpected, "header range is invalid")
224                .with_operation("BytesRange::from_str")
225                .with_context("value", value)
226        })?;
227
228        if s.contains(',') {
229            return Err(Error::new(ErrorKind::Unexpected, "header range is invalid")
230                .with_operation("BytesRange::from_str")
231                .with_context("value", value));
232        }
233
234        let v = s.split('-').collect::<Vec<_>>();
235        if v.len() != 2 {
236            return Err(Error::new(ErrorKind::Unexpected, "header range is invalid")
237                .with_operation("BytesRange::from_str")
238                .with_context("value", value));
239        }
240
241        let parse_int_error = |e: std::num::ParseIntError| {
242            Error::new(ErrorKind::Unexpected, "header range is invalid")
243                .with_operation("BytesRange::from_str")
244                .with_context("value", value)
245                .set_source(e)
246        };
247
248        if v[1].is_empty() {
249            // <range-start>-
250            Ok(BytesRange::new(
251                v[0].parse().map_err(parse_int_error)?,
252                None,
253            ))
254        } else if v[0].is_empty() {
255            // -<suffix-length>
256            Err(Error::new(
257                ErrorKind::Unexpected,
258                "header range with tailing is not supported",
259            )
260            .with_operation("BytesRange::from_str")
261            .with_context("value", value))
262        } else {
263            // <range-start>-<range-end>
264            let start: u64 = v[0].parse().map_err(parse_int_error)?;
265            let end: u64 = v[1].parse().map_err(parse_int_error)?;
266            if end < start {
267                return Err(Error::new(
268                    ErrorKind::Unexpected,
269                    "header range is invalid: end is less than start",
270                )
271                .with_operation("BytesRange::from_str")
272                .with_context("value", value));
273            }
274            Ok(BytesRange::new(start, Some(end - start + 1)))
275        }
276    }
277}
278
279impl<T> From<T> for BytesRange
280where
281    T: RangeBounds<u64>,
282{
283    fn from(range: T) -> Self {
284        let offset = match range.start_bound().cloned() {
285            Bound::Included(n) => n,
286            Bound::Excluded(n) => n.saturating_add(1),
287            Bound::Unbounded => 0,
288        };
289        let size = match range.end_bound().cloned() {
290            Bound::Included(n) => Some(n.saturating_add(1).saturating_sub(offset)),
291            Bound::Excluded(n) => Some(n.saturating_sub(offset)),
292            Bound::Unbounded => None,
293        };
294
295        BytesRange(offset, size)
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn test_bytes_range_display_zero_size() {
305        // Zero-size at offset 0
306        let range = BytesRange::new(0, Some(0));
307        assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
308
309        // Zero-size at nonzero offset
310        let range = BytesRange::new(5, Some(0));
311        assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
312    }
313
314    #[test]
315    fn test_bytes_range_to_string() {
316        let h = BytesRange::new(0, Some(1024));
317        assert_eq!(h.to_string(), "0-1023");
318
319        let h = BytesRange::new(1024, None);
320        assert_eq!(h.to_string(), "1024-");
321
322        let h = BytesRange::new(1024, Some(1024));
323        assert_eq!(h.to_string(), "1024-2047");
324    }
325
326    #[test]
327    fn test_bytes_range_to_header() {
328        let h = BytesRange::new(0, Some(1024));
329        assert_eq!(h.to_header(), "bytes=0-1023");
330
331        let h = BytesRange::new(1024, None);
332        assert_eq!(h.to_header(), "bytes=1024-");
333
334        let h = BytesRange::new(1024, Some(1024));
335        assert_eq!(h.to_header(), "bytes=1024-2047");
336    }
337
338    #[test]
339    fn test_bytes_range_from_range_bounds() {
340        assert_eq!(BytesRange::new(0, None), BytesRange::from(..));
341        assert_eq!(BytesRange::new(10, None), BytesRange::from(10..));
342        assert_eq!(BytesRange::new(0, Some(11)), BytesRange::from(..=10));
343        assert_eq!(BytesRange::new(0, Some(10)), BytesRange::from(..10));
344        assert_eq!(BytesRange::new(10, Some(10)), BytesRange::from(10..20));
345        assert_eq!(BytesRange::new(10, Some(11)), BytesRange::from(10..=20));
346    }
347
348    #[test]
349    fn test_bytes_range_from_str() -> Result<()> {
350        let cases = vec![
351            ("range-start", "bytes=123-", BytesRange::new(123, None)),
352            ("range", "bytes=123-124", BytesRange::new(123, Some(2))),
353            ("one byte", "bytes=0-0", BytesRange::new(0, Some(1))),
354            (
355                "lower case header",
356                "bytes=0-0",
357                BytesRange::new(0, Some(1)),
358            ),
359        ];
360
361        for (name, input, expected) in cases {
362            let actual = input.parse()?;
363
364            assert_eq!(expected, actual, "{name}")
365        }
366
367        Ok(())
368    }
369
370    #[test]
371    fn test_bytes_range_from_str_invalid_end_less_than_start() {
372        let cases = vec!["bytes=100-50", "bytes=10-9", "bytes=1-0"];
373
374        for input in cases {
375            let result: Result<BytesRange> = input.parse();
376            assert!(
377                result.is_err(),
378                "expected error for invalid range {input}, got {result:?}"
379            );
380        }
381    }
382
383    #[allow(clippy::reversed_empty_ranges)]
384    #[test]
385    fn test_bytes_range_from_range_bounds_underflow() {
386        // Invalid ranges where end < start should produce zero-size ranges
387        // rather than underflowing.
388        assert_eq!(BytesRange::new(100, Some(0)), BytesRange::from(100..50));
389        assert_eq!(BytesRange::new(10, Some(0)), BytesRange::from(10..=5));
390        assert_eq!(BytesRange::new(5, Some(0)), BytesRange::from(5..0));
391        assert_eq!(BytesRange::new(5, Some(0)), BytesRange::from(5..=0));
392    }
393
394    #[test]
395    fn test_bytes_range_from_range_bounds_u64_max() {
396        // Boundary cases near u64::MAX must not overflow.
397        assert_eq!(
398            BytesRange::new(0, Some(u64::MAX)),
399            BytesRange::from(..=u64::MAX)
400        );
401        assert_eq!(
402            BytesRange::new(0, Some(u64::MAX)),
403            BytesRange::from(..u64::MAX)
404        );
405        assert_eq!(
406            BytesRange::new(1, Some(u64::MAX.saturating_sub(1))),
407            BytesRange::from(1..=u64::MAX)
408        );
409        // Excluded start at u64::MAX must not overflow.
410        assert_eq!(
411            BytesRange::new(u64::MAX, None),
412            BytesRange::from((u64::MAX)..)
413        );
414    }
415
416    #[test]
417    fn test_bytes_range_display_overflow() {
418        // offset=u64::MAX, size=2 would overflow in Display (u64::MAX + 1)
419        let range = BytesRange::new(u64::MAX, Some(2));
420        assert!(std::fmt::write(&mut String::new(), format_args!("{}", range)).is_err());
421    }
422
423    #[test]
424    #[should_panic(expected = "BytesRange::to_range overflow")]
425    fn test_bytes_range_to_range_overflow() {
426        let range = BytesRange::new(u64::MAX, Some(1));
427        let _ = range.to_range();
428    }
429
430    #[test]
431    #[should_panic(expected = "BytesRange::to_range_as_usize")]
432    fn test_bytes_range_to_range_as_usize_overflow() {
433        let range = BytesRange::new(u64::MAX, Some(1));
434        let _ = range.to_range_as_usize();
435    }
436
437    #[test]
438    fn test_bytes_range_to_content_range() -> Result<()> {
439        assert_eq!(BytesRange::new(2, Some(4)).to_content_range(10)?, 2..6);
440        assert_eq!(BytesRange::new(2, None).to_content_range(10)?, 2..10);
441        assert_eq!(BytesRange::new(10, None).to_content_range(10)?, 10..10);
442        assert_eq!(BytesRange::new(20, None).to_content_range(10)?, 10..10);
443        assert_eq!(
444            BytesRange::new(u64::MAX, None).to_content_range(10)?,
445            10..10
446        );
447        assert_eq!(
448            BytesRange::new(u64::MAX, Some(0)).to_content_range(10)?,
449            0..0
450        );
451
452        let err = BytesRange::new(8, Some(4))
453            .to_content_range(10)
454            .unwrap_err();
455        assert_eq!(err.kind(), ErrorKind::RangeNotSatisfied);
456
457        let err = BytesRange::new(u64::MAX, Some(1))
458            .to_content_range(10)
459            .unwrap_err();
460        assert_eq!(err.kind(), ErrorKind::RangeNotSatisfied);
461
462        Ok(())
463    }
464
465    #[test]
466    #[should_panic(expected = "BytesRange::advance overflow")]
467    fn test_bytes_range_advance_offset_overflow() {
468        let mut range = BytesRange::new(u64::MAX, None);
469        range.advance(1);
470    }
471
472    #[test]
473    #[should_panic(expected = "BytesRange::advance underflow")]
474    fn test_bytes_range_advance_size_underflow() {
475        let mut range = BytesRange::new(0, Some(1));
476        range.advance(2);
477    }
478}