Skip to main content

rama_http_headers/common/
range.rs

1use std::fmt;
2use std::ops::{Bound, RangeBounds};
3
4use rama_core::telemetry::tracing;
5use rama_http_types::{HeaderName, HeaderValue};
6
7use crate::{Error, HeaderDecode, HeaderEncode, TypedHeader, util};
8
9/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1)
10///
11/// The "Range" header field on a GET request modifies the method
12/// semantics to request transfer of only one or more subranges of the
13/// selected representation data, rather than the entire selected
14/// representation data.
15///
16/// # ABNF
17///
18/// ```text
19/// Range = byte-ranges-specifier / other-ranges-specifier
20/// other-ranges-specifier = other-range-unit "=" other-range-set
21/// other-range-set = 1*VCHAR
22///
23/// bytes-unit = "bytes"
24///
25/// byte-ranges-specifier = bytes-unit "=" byte-range-set
26/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec)
27/// byte-range-spec = first-byte-pos "-" [last-byte-pos]
28/// first-byte-pos = 1*DIGIT
29/// last-byte-pos = 1*DIGIT
30/// ```
31///
32/// # Example values
33///
34/// * `bytes=1000-`
35/// * `bytes=-2000`
36/// * `bytes=0-1,30-40`
37/// * `bytes=0-10,20-90,-100`
38///
39/// # Examples
40///
41/// ```
42/// use rama_http_headers::Range;
43///
44/// // A client asking for the last 500 bytes of a representation.
45/// let range = Range::suffix(500);
46///
47/// // Resolve against a known content length into the inclusive
48/// // `(first, last)` byte range to serve. `None` would mean `416`.
49/// assert_eq!(range.first_satisfiable_range(2000), Some((1500, 1999)));
50/// ```
51//NOTE: only the `bytes` range unit is supported; other units are rejected on decode.
52#[derive(Clone, Debug, PartialEq, Eq, Hash)]
53pub struct Range(HeaderValue);
54
55rama_utils::macros::error::static_str_error! {
56    #[doc = "range is not valid"]
57    pub struct InvalidRange;
58}
59
60impl Range {
61    /// Creates a `Range` header from bounds (e.g. `0..100`, `0..=99`, `100..`).
62    ///
63    /// A range open at the start does not map to a `bytes=` spec; use
64    /// [`Range::suffix`] for the suffix (`bytes=-N`) form instead.
65    pub fn bytes(bounds: impl RangeBounds<u64>) -> Result<Self, InvalidRange> {
66        let v = match (bounds.start_bound(), bounds.end_bound()) {
67            (Bound::Included(start), Bound::Included(end)) => format!("bytes={start}-{end}"),
68            (Bound::Included(start), Bound::Excluded(&end)) => {
69                // `start..end` excludes `end`; an empty range (e.g. `0..0`) has no last byte.
70                let Some(last) = end.checked_sub(1) else {
71                    return Err(InvalidRange);
72                };
73                format!("bytes={start}-{last}")
74            }
75            (Bound::Included(start), Bound::Unbounded) => format!("bytes={start}-"),
76            // Anything open at the start is a suffix range, see `Range::suffix`.
77            _ => return Err(InvalidRange),
78        };
79
80        match HeaderValue::try_from(v) {
81            Ok(v) => Ok(Self(v)),
82            Err(err) => {
83                tracing::debug!("failed to create Range header from bytes: {err}");
84                Err(InvalidRange)
85            }
86        }
87    }
88
89    /// Creates a suffix `Range` header (`bytes=-n`) requesting the final
90    /// `n` bytes of the selected representation.
91    #[must_use]
92    pub fn suffix(n: u64) -> Self {
93        Self(util::fmt(format_args!("bytes=-{n}")))
94    }
95
96    /// Iterate over the [`ByteRangeSpec`]s in this header.
97    ///
98    /// Syntactically invalid specs (and the empty entries that a stray comma
99    /// produces) are silently skipped, matching how servers tolerate them.
100    pub fn iter(&self) -> impl Iterator<Item = ByteRangeSpec> + '_ {
101        self.range_set().split(',').filter_map(parse_spec)
102    }
103
104    /// Resolve every satisfiable range against a known `content_length`.
105    ///
106    /// Each yielded `(first, last)` is inclusive and clamped to the
107    /// representation; unsatisfiable specs are dropped. See
108    /// [`ByteRangeSpec::to_satisfiable_range`].
109    pub fn satisfiable_ranges(&self, content_length: u64) -> impl Iterator<Item = (u64, u64)> + '_ {
110        self.iter()
111            .filter_map(move |spec| spec.to_satisfiable_range(content_length))
112    }
113
114    /// Resolve the first satisfiable range against a known `content_length`.
115    ///
116    /// This is the common case for single-range byte serving: it returns the
117    /// inclusive `(first, last)` to serve, or `None` (⇒ `416 Range Not
118    /// Satisfiable`) when no spec in the header can be satisfied.
119    #[must_use]
120    pub fn first_satisfiable_range(&self, content_length: u64) -> Option<(u64, u64)> {
121        self.iter()
122            .find_map(|spec| spec.to_satisfiable_range(content_length))
123    }
124
125    /// The `byte-range-set`, i.e. everything after the `bytes=` unit prefix.
126    fn range_set(&self) -> &str {
127        #[expect(
128            clippy::expect_used,
129            reason = "value is validated as a UTF-8 `bytes=…` string in HeaderDecode::decode"
130        )]
131        let s = self
132            .0
133            .to_str()
134            .expect("valid string checked in HeaderDecode::decode()");
135        s.strip_prefix("bytes=").unwrap_or("")
136    }
137}
138
139/// A single entry of a [`Range`] header's `byte-range-set`, as defined in
140/// [RFC7233 §2.1](https://tools.ietf.org/html/rfc7233#section-2.1).
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
142pub enum ByteRangeSpec {
143    /// `first-last`: an inclusive range from `first` to `last`.
144    FromTo(u64, u64),
145    /// `first-`: from `first` to the end of the representation.
146    AllFrom(u64),
147    /// `-suffix`: the final `suffix` bytes of the representation.
148    Last(u64),
149}
150
151impl ByteRangeSpec {
152    /// Resolve this spec against a known `content_length` into an inclusive
153    /// `(first, last)` byte range, following [RFC7233 §2.1][rfc].
154    ///
155    /// The end is clamped to `content_length - 1`, a suffix (`-n`) range is
156    /// anchored to the end of the representation (a suffix at least as long as
157    /// the representation yields the whole of it), and `None` is returned when
158    /// the range is unsatisfiable — the cue for a `416 Range Not Satisfiable`
159    /// response. A returned range always satisfies `0 <= first <= last < content_length`.
160    ///
161    /// [rfc]: https://tools.ietf.org/html/rfc7233#section-2.1
162    #[must_use]
163    pub fn to_satisfiable_range(&self, content_length: u64) -> Option<(u64, u64)> {
164        if content_length == 0 {
165            // An empty representation has no satisfiable range.
166            return None;
167        }
168        let last = content_length - 1;
169        match *self {
170            Self::FromTo(first, to) if first <= to && first < content_length => {
171                Some((first, to.min(last)))
172            }
173            Self::AllFrom(first) if first < content_length => Some((first, last)),
174            Self::Last(suffix) if suffix > 0 => Some((content_length.saturating_sub(suffix), last)),
175            _ => None,
176        }
177    }
178}
179
180impl fmt::Display for ByteRangeSpec {
181    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182        match *self {
183            Self::FromTo(first, last) => write!(f, "{first}-{last}"),
184            Self::AllFrom(first) => write!(f, "{first}-"),
185            Self::Last(suffix) => write!(f, "-{suffix}"),
186        }
187    }
188}
189
190fn parse_spec(spec: &str) -> Option<ByteRangeSpec> {
191    let (first, last) = spec.trim().split_once('-')?;
192    match (first, last) {
193        // A bare `-` carries no suffix length and is meaningless.
194        ("", "") => None,
195        ("", suffix) => Some(ByteRangeSpec::Last(suffix.parse().ok()?)),
196        (first, "") => Some(ByteRangeSpec::AllFrom(first.parse().ok()?)),
197        (first, last) => {
198            let first = first.parse().ok()?;
199            let last = last.parse().ok()?;
200            (first <= last).then_some(ByteRangeSpec::FromTo(first, last))
201        }
202    }
203}
204
205impl TypedHeader for Range {
206    fn name() -> &'static HeaderName {
207        &::rama_http_types::header::RANGE
208    }
209}
210
211impl HeaderDecode for Range {
212    fn decode<'i, I: Iterator<Item = &'i HeaderValue>>(values: &mut I) -> Result<Self, Error> {
213        values
214            .next()
215            .and_then(|val| {
216                if val.to_str().ok()?.starts_with("bytes=") {
217                    Some(Self(val.clone()))
218                } else {
219                    None
220                }
221            })
222            .ok_or_else(Error::invalid)
223    }
224}
225
226impl HeaderEncode for Range {
227    fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
228        values.extend(::std::iter::once(self.0.clone()));
229    }
230}
231
232#[cfg(test)]
233mod tests {
234    use super::super::{test_decode, test_encode};
235    use super::*;
236
237    fn range(s: &str) -> Range {
238        test_decode(&[s]).unwrap()
239    }
240
241    fn specs(s: &str) -> Vec<ByteRangeSpec> {
242        range(s).iter().collect()
243    }
244
245    #[test]
246    fn decode_rejects_non_bytes_unit() {
247        assert!(test_decode::<Range>(&["seconds=1-2"]).is_none());
248        assert!(test_decode::<Range>(&["1-2"]).is_none());
249        assert!(test_decode::<Range>(&["bytes"]).is_none());
250    }
251
252    #[test]
253    fn iter_parses_specs() {
254        assert_eq!(specs("bytes=1-100"), [ByteRangeSpec::FromTo(1, 100)]);
255        assert_eq!(
256            specs("bytes=1-100,200-"),
257            [ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)],
258        );
259        assert_eq!(
260            specs("bytes=0-10,20-90,-100"),
261            [
262                ByteRangeSpec::FromTo(0, 10),
263                ByteRangeSpec::FromTo(20, 90),
264                ByteRangeSpec::Last(100),
265            ],
266        );
267    }
268
269    #[test]
270    fn iter_trims_and_skips_invalid() {
271        // surrounding whitespace is tolerated; empty/malformed specs are skipped.
272        assert_eq!(
273            specs("bytes= 1-100 , 101-xxx ,  200- , ,, -100 , 5-2"),
274            [
275                ByteRangeSpec::FromTo(1, 100),
276                ByteRangeSpec::AllFrom(200),
277                ByteRangeSpec::Last(100),
278            ],
279        );
280        assert!(specs("bytes=1-2-3").is_empty());
281        assert!(specs("bytes=").is_empty());
282        assert!(specs("bytes=-").is_empty());
283    }
284
285    #[test]
286    fn spec_from_to_resolution() {
287        use ByteRangeSpec::FromTo;
288        assert_eq!(FromTo(0, 0).to_satisfiable_range(3), Some((0, 0)));
289        assert_eq!(FromTo(1, 2).to_satisfiable_range(3), Some((1, 2)));
290        assert_eq!(FromTo(1, 5).to_satisfiable_range(3), Some((1, 2))); // clamped
291        assert_eq!(FromTo(3, 3).to_satisfiable_range(3), None); // first == len
292        assert_eq!(FromTo(2, 1).to_satisfiable_range(3), None); // first > last
293        assert_eq!(FromTo(0, 0).to_satisfiable_range(0), None); // empty repr
294    }
295
296    #[test]
297    fn spec_all_from_resolution() {
298        use ByteRangeSpec::AllFrom;
299        assert_eq!(AllFrom(0).to_satisfiable_range(3), Some((0, 2)));
300        assert_eq!(AllFrom(2).to_satisfiable_range(3), Some((2, 2)));
301        assert_eq!(AllFrom(3).to_satisfiable_range(3), None);
302        assert_eq!(AllFrom(5).to_satisfiable_range(3), None);
303        assert_eq!(AllFrom(0).to_satisfiable_range(0), None);
304    }
305
306    #[test]
307    fn spec_last_resolution() {
308        use ByteRangeSpec::Last;
309        assert_eq!(Last(2).to_satisfiable_range(3), Some((1, 2)));
310        assert_eq!(Last(1).to_satisfiable_range(3), Some((2, 2)));
311        // a suffix at least as long as the representation yields the whole of it.
312        assert_eq!(Last(3).to_satisfiable_range(3), Some((0, 2)));
313        assert_eq!(Last(5).to_satisfiable_range(3), Some((0, 2)));
314        assert_eq!(Last(0).to_satisfiable_range(3), None);
315        assert_eq!(Last(2).to_satisfiable_range(0), None);
316    }
317
318    #[test]
319    fn first_satisfiable_range_suffix() {
320        assert_eq!(
321            range("bytes=-100").first_satisfiable_range(350),
322            Some((250, 349)),
323        );
324        // suffix longer than the representation → the whole representation (RFC 7233 §2.1).
325        assert_eq!(
326            range("bytes=-350").first_satisfiable_range(100),
327            Some((0, 99)),
328        );
329    }
330
331    #[test]
332    fn first_satisfiable_range_skips_unsatisfiable() {
333        // the first spec is out of range, so the next satisfiable one is used.
334        assert_eq!(
335            range("bytes=500-600,0-1").first_satisfiable_range(100),
336            Some((0, 1)),
337        );
338        // every spec is out of range → 416.
339        assert_eq!(range("bytes=500-,600-").first_satisfiable_range(100), None);
340    }
341
342    #[test]
343    fn satisfiable_ranges_resolves_and_clamps() {
344        let resolved: Vec<_> = range("bytes=0-1,30-40,500-600,-5")
345            .satisfiable_ranges(100)
346            .collect();
347        // 500-600 dropped (unsatisfiable); -5 → last 5 bytes; others kept.
348        assert_eq!(resolved, [(0, 1), (30, 40), (95, 99)]);
349    }
350
351    #[test]
352    fn bytes_constructor_encodes() {
353        assert_eq!(
354            test_encode(Range::bytes(0..1234).unwrap())["range"],
355            "bytes=0-1233"
356        );
357        assert_eq!(
358            test_encode(Range::bytes(0..=99).unwrap())["range"],
359            "bytes=0-99"
360        );
361        assert_eq!(
362            test_encode(Range::bytes(100..).unwrap())["range"],
363            "bytes=100-"
364        );
365    }
366
367    #[test]
368    fn bytes_constructor_rejects_unrepresentable() {
369        Range::bytes(..).unwrap_err(); // open start
370        Range::bytes(..100).unwrap_err(); // open start
371        Range::bytes(0..0).unwrap_err(); // empty (would underflow last)
372    }
373
374    #[test]
375    fn suffix_constructor() {
376        assert_eq!(test_encode(Range::suffix(500))["range"], "bytes=-500");
377        assert_eq!(
378            Range::suffix(500).first_satisfiable_range(2000),
379            Some((1500, 1999))
380        );
381    }
382
383    #[test]
384    fn spec_display_roundtrips_through_iter() {
385        let rendered: Vec<String> = range("bytes=0-10,20-,-100")
386            .iter()
387            .map(|s| s.to_string())
388            .collect();
389        assert_eq!(rendered, ["0-10", "20-", "-100"]);
390    }
391}