http_range/
lib.rs

1//! # http-range
2//!
3//! HTTP Range header parser.
4//! Inspired by Go's net/http library.
5
6static PREFIX: &'static [u8] = b"bytes=";
7const PREFIX_LEN: usize = 6;
8
9/// Range parsing error
10#[derive(Debug)]
11pub enum HttpRangeParseError {
12    /// Returned if range is invalid.
13    InvalidRange,
14    /// Returned if first-byte-pos of all of the byte-range-spec
15    /// values is greater than the content size.
16    /// See https://github.com/golang/go/commit/aa9b3d7
17    NoOverlap,
18}
19
20/// HTTP Range header representation.
21#[derive(Debug, Clone, Copy)]
22pub struct HttpRange {
23    pub start: u64,
24    pub length: u64,
25}
26
27impl HttpRange {
28    /// Parses Range HTTP header string as per RFC 2616.
29    ///
30    /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`).
31    /// `size` is full size of response (file).
32    pub fn parse(header: &str, size: u64) -> Result<Vec<HttpRange>, HttpRangeParseError> {
33        Self::parse_bytes(header.as_bytes(), size)
34    }
35
36    pub fn parse_bytes(header: &[u8], size: u64) -> Result<Vec<HttpRange>, HttpRangeParseError> {
37        if header.is_empty() {
38            return Ok(Vec::new());
39        }
40
41        if !header.starts_with(PREFIX) {
42            return Err(HttpRangeParseError::InvalidRange);
43        }
44
45        let mut no_overlap = false;
46
47        let ranges: Vec<HttpRange> = header[PREFIX_LEN..]
48            .split(|b| *b == b',')
49            .filter_map(|ra| {
50                let ra = ra.trim();
51                if ra.is_empty() {
52                    return None;
53                }
54                match Self::parse_single_range(ra, size) {
55                    Ok(Some(range)) => Some(Ok(range)),
56                    Ok(None) => {
57                        no_overlap = true;
58                        None
59                    }
60                    Err(e) => Some(Err(e)),
61                }
62            })
63            .collect::<Result<_, _>>()?;
64
65        if no_overlap && ranges.is_empty() {
66            return Err(HttpRangeParseError::NoOverlap);
67        }
68
69        Ok(ranges)
70    }
71
72    fn parse_single_range(
73        bytes: &[u8],
74        size: u64,
75    ) -> Result<Option<HttpRange>, HttpRangeParseError> {
76        let mut start_end_iter = bytes.splitn(2, |b| *b == b'-');
77
78        let start_str = start_end_iter
79            .next()
80            .ok_or(HttpRangeParseError::InvalidRange)?
81            .trim();
82        let end_str = start_end_iter
83            .next()
84            .ok_or(HttpRangeParseError::InvalidRange)?
85            .trim();
86
87        if start_str.is_empty() {
88            // If no start is specified, end specifies the
89            // range start relative to the end of the file,
90            // and we are dealing with <suffix-length>
91            // which has to be a non-negative integer as per
92            // RFC 7233 Section 2.1 "Byte-Ranges".
93            if end_str.is_empty() || end_str[0] == b'-' {
94                return Err(HttpRangeParseError::InvalidRange);
95            }
96
97            let mut length: u64 = end_str
98                .parse_u64()
99                .map_err(|_| HttpRangeParseError::InvalidRange)?;
100
101            if length == 0 {
102                return Ok(None);
103            }
104
105            if length > size {
106                length = size;
107            }
108
109            Ok(Some(HttpRange {
110                start: (size - length),
111                length,
112            }))
113        } else {
114            let start: u64 = start_str
115                .parse_u64()
116                .map_err(|_| HttpRangeParseError::InvalidRange)?;
117
118            if start >= size {
119                return Ok(None);
120            }
121
122            let length = if end_str.is_empty() {
123                // If no end is specified, range extends to end of the file.
124                size - start
125            } else {
126                let mut end: u64 = end_str
127                    .parse_u64()
128                    .map_err(|_| HttpRangeParseError::InvalidRange)?;
129
130                if start > end {
131                    return Err(HttpRangeParseError::InvalidRange);
132                }
133
134                if end >= size {
135                    end = size - 1;
136                }
137
138                end - start + 1
139            };
140
141            Ok(Some(HttpRange { start, length }))
142        }
143    }
144}
145
146trait SliceExt {
147    fn trim(&self) -> &Self;
148    fn parse_u64(&self) -> Result<u64, ()>;
149}
150
151impl SliceExt for [u8] {
152    fn trim(&self) -> &[u8] {
153        fn is_whitespace(c: &u8) -> bool {
154            *c == b'\t' || *c == b' '
155        }
156
157        fn is_not_whitespace(c: &u8) -> bool {
158            !is_whitespace(c)
159        }
160
161        if let Some(first) = self.iter().position(is_not_whitespace) {
162            if let Some(last) = self.iter().rposition(is_not_whitespace) {
163                &self[first..last + 1]
164            } else {
165                unreachable!();
166            }
167        } else {
168            &[]
169        }
170    }
171
172    fn parse_u64(&self) -> Result<u64, ()> {
173        if self.is_empty() {
174            return Err(());
175        }
176        let mut res = 0u64;
177        for b in self {
178            if *b >= b'0' && *b <= b'9' {
179                res = res
180                    .checked_mul(10)
181                    .ok_or(())?
182                    .checked_add((b - b'0') as u64)
183                    .ok_or(())?;
184            } else {
185                return Err(());
186            }
187        }
188
189        Ok(res)
190    }
191}
192
193#[cfg(test)]
194mod tests {
195    use super::*;
196
197    struct T(&'static str, u64, Vec<HttpRange>);
198
199    #[test]
200    fn test_parse() {
201        let tests = vec![
202            T("", 0, vec![]),
203            T("", 1000, vec![]),
204            T("foo", 0, vec![]),
205            T("bytes=", 0, vec![]),
206            T("bytes=", 200, vec![]),
207            T("bytes=7", 10, vec![]),
208            T("bytes= 7 ", 10, vec![]),
209            T("bytes=1-", 0, vec![]),
210            T("bytes=5-4", 10, vec![]),
211            T("bytes=--6", 200, vec![]),
212            T("bytes=--0", 200, vec![]),
213            T("bytes=---0", 200, vec![]),
214            T(
215                "bytes=-6",
216                200,
217                vec![HttpRange {
218                    start: 194,
219                    length: 6,
220                }],
221            ),
222            T(
223                "bytes=6-",
224                200,
225                vec![HttpRange {
226                    start: 6,
227                    length: 194,
228                }],
229            ),
230            T("bytes=-6-", 0, vec![]),
231            T("bytes=-0", 200, vec![]),
232            T("bytes=0-2,5-4", 10, vec![]),
233            T("bytes=2-5,4-3", 10, vec![]),
234            T("bytes=--5,4--3", 10, vec![]),
235            T("bytes=A-", 10, vec![]),
236            T("bytes=A- ", 10, vec![]),
237            T("bytes=A-Z", 10, vec![]),
238            T("bytes= -Z", 10, vec![]),
239            T("bytes=5-Z", 10, vec![]),
240            T("bytes=Ran-dom, garbage", 10, vec![]),
241            T("bytes=0x01-0x02", 10, vec![]),
242            T("bytes=         ", 10, vec![]),
243            T("bytes= , , ,   ", 10, vec![]),
244            T(
245                "bytes=0-9",
246                10,
247                vec![HttpRange {
248                    start: 0,
249                    length: 10,
250                }],
251            ),
252            T(
253                "bytes=0-",
254                10,
255                vec![HttpRange {
256                    start: 0,
257                    length: 10,
258                }],
259            ),
260            T(
261                "bytes=5-",
262                10,
263                vec![HttpRange {
264                    start: 5,
265                    length: 5,
266                }],
267            ),
268            T(
269                "bytes=0-20",
270                10,
271                vec![HttpRange {
272                    start: 0,
273                    length: 10,
274                }],
275            ),
276            T(
277                "bytes=15-,0-5",
278                10,
279                vec![HttpRange {
280                    start: 0,
281                    length: 6,
282                }],
283            ),
284            T(
285                "bytes=1-2,5-",
286                10,
287                vec![
288                    HttpRange {
289                        start: 1,
290                        length: 2,
291                    },
292                    HttpRange {
293                        start: 5,
294                        length: 5,
295                    },
296                ],
297            ),
298            T(
299                "bytes=-2 , 7-",
300                11,
301                vec![
302                    HttpRange {
303                        start: 9,
304                        length: 2,
305                    },
306                    HttpRange {
307                        start: 7,
308                        length: 4,
309                    },
310                ],
311            ),
312            T(
313                "bytes=0-0 ,2-2, 7-",
314                11,
315                vec![
316                    HttpRange {
317                        start: 0,
318                        length: 1,
319                    },
320                    HttpRange {
321                        start: 2,
322                        length: 1,
323                    },
324                    HttpRange {
325                        start: 7,
326                        length: 4,
327                    },
328                ],
329            ),
330            T(
331                "bytes=-5",
332                10,
333                vec![HttpRange {
334                    start: 5,
335                    length: 5,
336                }],
337            ),
338            T(
339                "bytes=-15",
340                10,
341                vec![HttpRange {
342                    start: 0,
343                    length: 10,
344                }],
345            ),
346            T(
347                "bytes=0-499",
348                10000,
349                vec![HttpRange {
350                    start: 0,
351                    length: 500,
352                }],
353            ),
354            T(
355                "bytes=500-999",
356                10000,
357                vec![HttpRange {
358                    start: 500,
359                    length: 500,
360                }],
361            ),
362            T(
363                "bytes=-500",
364                10000,
365                vec![HttpRange {
366                    start: 9500,
367                    length: 500,
368                }],
369            ),
370            T(
371                "bytes=9500-",
372                10000,
373                vec![HttpRange {
374                    start: 9500,
375                    length: 500,
376                }],
377            ),
378            T(
379                "bytes=0-0,-1",
380                10000,
381                vec![
382                    HttpRange {
383                        start: 0,
384                        length: 1,
385                    },
386                    HttpRange {
387                        start: 9999,
388                        length: 1,
389                    },
390                ],
391            ),
392            T(
393                "bytes=500-600,601-999",
394                10000,
395                vec![
396                    HttpRange {
397                        start: 500,
398                        length: 101,
399                    },
400                    HttpRange {
401                        start: 601,
402                        length: 399,
403                    },
404                ],
405            ),
406            T(
407                "bytes=500-700,601-999",
408                10000,
409                vec![
410                    HttpRange {
411                        start: 500,
412                        length: 201,
413                    },
414                    HttpRange {
415                        start: 601,
416                        length: 399,
417                    },
418                ],
419            ),
420            // Match Apache laxity:
421            T(
422                "bytes=   1 -2   ,  4- 5, 7 - 8 , ,,",
423                11,
424                vec![
425                    HttpRange {
426                        start: 1,
427                        length: 2,
428                    },
429                    HttpRange {
430                        start: 4,
431                        length: 2,
432                    },
433                    HttpRange {
434                        start: 7,
435                        length: 2,
436                    },
437                ],
438            ),
439            T(
440                "bytes=50-60,2-3",
441                10,
442                vec![HttpRange {
443                    start: 2,
444                    length: 2,
445                }],
446            ),
447            T(
448                "bytes=50-60,-5",
449                10,
450                vec![HttpRange {
451                    start: 5,
452                    length: 5,
453                }],
454            ),
455            T(
456                "bytes=50-60,7-",
457                10,
458                vec![HttpRange {
459                    start: 7,
460                    length: 3,
461                }],
462            ),
463            T("bytes=50-60,20-", 10, vec![]),
464            T(
465                "bytes=50-60,20-,3-4",
466                10,
467                vec![HttpRange {
468                    start: 3,
469                    length: 2,
470                }],
471            ),
472            T(
473                "bytes=9-20,-5",
474                10,
475                vec![
476                    HttpRange {
477                        start: 9,
478                        length: 1,
479                    },
480                    HttpRange {
481                        start: 5,
482                        length: 5,
483                    },
484                ],
485            ),
486            T("bytes=15-20,-0", 10, vec![]),
487            T(
488                "bytes=15-20,-0",
489                20,
490                vec![HttpRange {
491                    start: 15,
492                    length: 5,
493                }],
494            ),
495            T("bytes=1-2,bytes=3-4", 10, vec![]),
496            T("bytes=1-2,blergh=3-4", 10, vec![]),
497            T("blergh=1-2,bytes=3-4", 10, vec![]),
498            T("bytes=-0", 0, vec![]),
499            T("bytes=-0", 5, vec![]),
500            T(
501                "bytes=-1",
502                0,
503                vec![HttpRange {
504                    start: 0,
505                    length: 0,
506                }],
507            ),
508            T(
509                "bytes=0-99999999999999999999999999999999999999999999",
510                10,
511                vec![],
512            ),
513        ];
514
515        for t in tests {
516            let header = t.0;
517            let size = t.1;
518            let expected = t.2;
519
520            let res = HttpRange::parse(header, size);
521
522            if res.is_err() {
523                if expected.is_empty() {
524                    continue;
525                } else {
526                    assert!(
527                        false,
528                        "parse({}, {}) returned error {:?}",
529                        header,
530                        size,
531                        res.unwrap_err()
532                    );
533                }
534            }
535
536            let got = res.unwrap();
537
538            if got.len() != expected.len() {
539                assert!(
540                    false,
541                    "len(parseRange({}, {})) = {}, want {}",
542                    header,
543                    size,
544                    got.len(),
545                    expected.len()
546                );
547                continue;
548            }
549
550            for i in 0..expected.len() {
551                if got[i].start != expected[i].start {
552                    assert!(
553                        false,
554                        "parseRange({}, {})[{}].start = {}, want {}",
555                        header, size, i, got[i].start, expected[i].start
556                    )
557                }
558                if got[i].length != expected[i].length {
559                    assert!(
560                        false,
561                        "parseRange({}, {})[{}].length = {}, want {}",
562                        header, size, i, got[i].length, expected[i].length
563                    )
564                }
565            }
566        }
567    }
568}