http/uri/
path.rs

1use std::convert::TryFrom;
2use std::str::FromStr;
3use std::{cmp, fmt, str};
4
5use bytes::Bytes;
6
7use super::{ErrorKind, InvalidUri};
8use crate::byte_str::ByteStr;
9
10/// Represents the path component of a URI
11#[derive(Clone)]
12pub struct PathAndQuery {
13    pub(super) data: ByteStr,
14    pub(super) query: u16,
15}
16
17const NONE: u16 = ::std::u16::MAX;
18
19impl PathAndQuery {
20    // Not public while `bytes` is unstable.
21    pub(super) fn from_shared(mut src: Bytes) -> Result<Self, InvalidUri> {
22        let mut query = NONE;
23        let mut fragment = None;
24
25        // block for iterator borrow
26        {
27            let mut iter = src.as_ref().iter().enumerate();
28
29            // path ...
30            for (i, &b) in &mut iter {
31                // See https://url.spec.whatwg.org/#path-state
32                match b {
33                    b'?' => {
34                        debug_assert_eq!(query, NONE);
35                        query = i as u16;
36                        break;
37                    }
38                    b'#' => {
39                        fragment = Some(i);
40                        break;
41                    }
42
43                    // This is the range of bytes that don't need to be
44                    // percent-encoded in the path. If it should have been
45                    // percent-encoded, then error.
46                    0x21 |
47                    0x24..=0x3B |
48                    0x3D |
49                    0x40..=0x5F |
50                    0x61..=0x7A |
51                    0x7C |
52                    0x7E => {},
53
54                    // These are code points that are supposed to be
55                    // percent-encoded in the path but there are clients
56                    // out there sending them as is and httparse accepts
57                    // to parse those requests, so they are allowed here
58                    // for parity.
59                    //
60                    // For reference, those are code points that are used
61                    // to send requests with JSON directly embedded in
62                    // the URI path. Yes, those things happen for real.
63                    b'"' |
64                    b'{' | b'}' => {},
65
66                    _ => return Err(ErrorKind::InvalidUriChar.into()),
67                }
68            }
69
70            // query ...
71            if query != NONE {
72                for (i, &b) in iter {
73                    match b {
74                        // While queries *should* be percent-encoded, most
75                        // bytes are actually allowed...
76                        // See https://url.spec.whatwg.org/#query-state
77                        //
78                        // Allowed: 0x21 / 0x24 - 0x3B / 0x3D / 0x3F - 0x7E
79                        0x21 |
80                        0x24..=0x3B |
81                        0x3D |
82                        0x3F..=0x7E => {},
83
84                        b'#' => {
85                            fragment = Some(i);
86                            break;
87                        }
88
89                        _ => return Err(ErrorKind::InvalidUriChar.into()),
90                    }
91                }
92            }
93        }
94
95        if let Some(i) = fragment {
96            src.truncate(i);
97        }
98
99        Ok(PathAndQuery {
100            data: unsafe { ByteStr::from_utf8_unchecked(src) },
101            query: query,
102        })
103    }
104
105    /// Convert a `PathAndQuery` from a static string.
106    ///
107    /// This function will not perform any copying, however the string is
108    /// checked to ensure that it is valid.
109    ///
110    /// # Panics
111    ///
112    /// This function panics if the argument is an invalid path and query.
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// # use http::uri::*;
118    /// let v = PathAndQuery::from_static("/hello?world");
119    ///
120    /// assert_eq!(v.path(), "/hello");
121    /// assert_eq!(v.query(), Some("world"));
122    /// ```
123    #[inline]
124    pub fn from_static(src: &'static str) -> Self {
125        let src = Bytes::from_static(src.as_bytes());
126
127        PathAndQuery::from_shared(src).unwrap()
128    }
129
130    /// Attempt to convert a `Bytes` buffer to a `PathAndQuery`.
131    ///
132    /// This will try to prevent a copy if the type passed is the type used
133    /// internally, and will copy the data if it is not.
134    pub fn from_maybe_shared<T>(src: T) -> Result<Self, InvalidUri>
135    where
136        T: AsRef<[u8]> + 'static,
137    {
138        if_downcast_into!(T, Bytes, src, {
139            return PathAndQuery::from_shared(src);
140        });
141
142        PathAndQuery::try_from(src.as_ref())
143    }
144
145    pub(super) fn empty() -> Self {
146        PathAndQuery {
147            data: ByteStr::new(),
148            query: NONE,
149        }
150    }
151
152    pub(super) fn slash() -> Self {
153        PathAndQuery {
154            data: ByteStr::from_static("/"),
155            query: NONE,
156        }
157    }
158
159    pub(super) fn star() -> Self {
160        PathAndQuery {
161            data: ByteStr::from_static("*"),
162            query: NONE,
163        }
164    }
165
166    /// Returns the path component
167    ///
168    /// The path component is **case sensitive**.
169    ///
170    /// ```notrust
171    /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
172    ///                                        |--------|
173    ///                                             |
174    ///                                           path
175    /// ```
176    ///
177    /// If the URI is `*` then the path component is equal to `*`.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// # use http::uri::*;
183    ///
184    /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
185    ///
186    /// assert_eq!(path_and_query.path(), "/hello/world");
187    /// ```
188    #[inline]
189    pub fn path(&self) -> &str {
190        let ret = if self.query == NONE {
191            &self.data[..]
192        } else {
193            &self.data[..self.query as usize]
194        };
195
196        if ret.is_empty() {
197            return "/";
198        }
199
200        ret
201    }
202
203    /// Returns the query string component
204    ///
205    /// The query component contains non-hierarchical data that, along with data
206    /// in the path component, serves to identify a resource within the scope of
207    /// the URI's scheme and naming authority (if any). The query component is
208    /// indicated by the first question mark ("?") character and terminated by a
209    /// number sign ("#") character or by the end of the URI.
210    ///
211    /// ```notrust
212    /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1
213    ///                                                   |-------------------|
214    ///                                                             |
215    ///                                                           query
216    /// ```
217    ///
218    /// # Examples
219    ///
220    /// With a query string component
221    ///
222    /// ```
223    /// # use http::uri::*;
224    /// let path_and_query: PathAndQuery = "/hello/world?key=value&foo=bar".parse().unwrap();
225    ///
226    /// assert_eq!(path_and_query.query(), Some("key=value&foo=bar"));
227    /// ```
228    ///
229    /// Without a query string component
230    ///
231    /// ```
232    /// # use http::uri::*;
233    /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
234    ///
235    /// assert!(path_and_query.query().is_none());
236    /// ```
237    #[inline]
238    pub fn query(&self) -> Option<&str> {
239        if self.query == NONE {
240            None
241        } else {
242            let i = self.query + 1;
243            Some(&self.data[i as usize..])
244        }
245    }
246
247    /// Returns the path and query as a string component.
248    ///
249    /// # Examples
250    ///
251    /// With a query string component
252    ///
253    /// ```
254    /// # use http::uri::*;
255    /// let path_and_query: PathAndQuery = "/hello/world?key=value&foo=bar".parse().unwrap();
256    ///
257    /// assert_eq!(path_and_query.as_str(), "/hello/world?key=value&foo=bar");
258    /// ```
259    ///
260    /// Without a query string component
261    ///
262    /// ```
263    /// # use http::uri::*;
264    /// let path_and_query: PathAndQuery = "/hello/world".parse().unwrap();
265    ///
266    /// assert_eq!(path_and_query.as_str(), "/hello/world");
267    /// ```
268    #[inline]
269    pub fn as_str(&self) -> &str {
270        let ret = &self.data[..];
271        if ret.is_empty() {
272            return "/";
273        }
274        ret
275    }
276}
277
278impl<'a> TryFrom<&'a [u8]> for PathAndQuery {
279    type Error = InvalidUri;
280    #[inline]
281    fn try_from(s: &'a [u8]) -> Result<Self, Self::Error> {
282        PathAndQuery::from_shared(Bytes::copy_from_slice(s))
283    }
284}
285
286impl<'a> TryFrom<&'a str> for PathAndQuery {
287    type Error = InvalidUri;
288    #[inline]
289    fn try_from(s: &'a str) -> Result<Self, Self::Error> {
290        TryFrom::try_from(s.as_bytes())
291    }
292}
293
294impl TryFrom<String> for PathAndQuery {
295    type Error = InvalidUri;
296    #[inline]
297    fn try_from(s: String) -> Result<Self, Self::Error> {
298        TryFrom::try_from(s.as_bytes())
299    }
300}
301
302impl TryFrom<&String> for PathAndQuery {
303    type Error = InvalidUri;
304    #[inline]
305    fn try_from(s: &String) -> Result<Self, Self::Error> {
306        TryFrom::try_from(s.as_bytes())
307    }
308}
309
310impl FromStr for PathAndQuery {
311    type Err = InvalidUri;
312    #[inline]
313    fn from_str(s: &str) -> Result<Self, InvalidUri> {
314        TryFrom::try_from(s)
315    }
316}
317
318impl fmt::Debug for PathAndQuery {
319    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
320        fmt::Display::fmt(self, f)
321    }
322}
323
324impl fmt::Display for PathAndQuery {
325    fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
326        if !self.data.is_empty() {
327            match self.data.as_bytes()[0] {
328                b'/' | b'*' => write!(fmt, "{}", &self.data[..]),
329                _ => write!(fmt, "/{}", &self.data[..]),
330            }
331        } else {
332            write!(fmt, "/")
333        }
334    }
335}
336
337// ===== PartialEq / PartialOrd =====
338
339impl PartialEq for PathAndQuery {
340    #[inline]
341    fn eq(&self, other: &PathAndQuery) -> bool {
342        self.data == other.data
343    }
344}
345
346impl Eq for PathAndQuery {}
347
348impl PartialEq<str> for PathAndQuery {
349    #[inline]
350    fn eq(&self, other: &str) -> bool {
351        self.as_str() == other
352    }
353}
354
355impl<'a> PartialEq<PathAndQuery> for &'a str {
356    #[inline]
357    fn eq(&self, other: &PathAndQuery) -> bool {
358        self == &other.as_str()
359    }
360}
361
362impl<'a> PartialEq<&'a str> for PathAndQuery {
363    #[inline]
364    fn eq(&self, other: &&'a str) -> bool {
365        self.as_str() == *other
366    }
367}
368
369impl PartialEq<PathAndQuery> for str {
370    #[inline]
371    fn eq(&self, other: &PathAndQuery) -> bool {
372        self == other.as_str()
373    }
374}
375
376impl PartialEq<String> for PathAndQuery {
377    #[inline]
378    fn eq(&self, other: &String) -> bool {
379        self.as_str() == other.as_str()
380    }
381}
382
383impl PartialEq<PathAndQuery> for String {
384    #[inline]
385    fn eq(&self, other: &PathAndQuery) -> bool {
386        self.as_str() == other.as_str()
387    }
388}
389
390impl PartialOrd for PathAndQuery {
391    #[inline]
392    fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
393        self.as_str().partial_cmp(other.as_str())
394    }
395}
396
397impl PartialOrd<str> for PathAndQuery {
398    #[inline]
399    fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
400        self.as_str().partial_cmp(other)
401    }
402}
403
404impl PartialOrd<PathAndQuery> for str {
405    #[inline]
406    fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
407        self.partial_cmp(other.as_str())
408    }
409}
410
411impl<'a> PartialOrd<&'a str> for PathAndQuery {
412    #[inline]
413    fn partial_cmp(&self, other: &&'a str) -> Option<cmp::Ordering> {
414        self.as_str().partial_cmp(*other)
415    }
416}
417
418impl<'a> PartialOrd<PathAndQuery> for &'a str {
419    #[inline]
420    fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
421        self.partial_cmp(&other.as_str())
422    }
423}
424
425impl PartialOrd<String> for PathAndQuery {
426    #[inline]
427    fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
428        self.as_str().partial_cmp(other.as_str())
429    }
430}
431
432impl PartialOrd<PathAndQuery> for String {
433    #[inline]
434    fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
435        self.as_str().partial_cmp(other.as_str())
436    }
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442
443    #[test]
444    fn equal_to_self_of_same_path() {
445        let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
446        let p2: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
447        assert_eq!(p1, p2);
448        assert_eq!(p2, p1);
449    }
450
451    #[test]
452    fn not_equal_to_self_of_different_path() {
453        let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
454        let p2: PathAndQuery = "/world&foo=bar".parse().unwrap();
455        assert_ne!(p1, p2);
456        assert_ne!(p2, p1);
457    }
458
459    #[test]
460    fn equates_with_a_str() {
461        let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
462        assert_eq!(&path_and_query, "/hello/world&foo=bar");
463        assert_eq!("/hello/world&foo=bar", &path_and_query);
464        assert_eq!(path_and_query, "/hello/world&foo=bar");
465        assert_eq!("/hello/world&foo=bar", path_and_query);
466    }
467
468    #[test]
469    fn not_equal_with_a_str_of_a_different_path() {
470        let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
471        // as a reference
472        assert_ne!(&path_and_query, "/hello&foo=bar");
473        assert_ne!("/hello&foo=bar", &path_and_query);
474        // without reference
475        assert_ne!(path_and_query, "/hello&foo=bar");
476        assert_ne!("/hello&foo=bar", path_and_query);
477    }
478
479    #[test]
480    fn equates_with_a_string() {
481        let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
482        assert_eq!(path_and_query, "/hello/world&foo=bar".to_string());
483        assert_eq!("/hello/world&foo=bar".to_string(), path_and_query);
484    }
485
486    #[test]
487    fn not_equal_with_a_string_of_a_different_path() {
488        let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
489        assert_ne!(path_and_query, "/hello&foo=bar".to_string());
490        assert_ne!("/hello&foo=bar".to_string(), path_and_query);
491    }
492
493    #[test]
494    fn compares_to_self() {
495        let p1: PathAndQuery = "/a/world&foo=bar".parse().unwrap();
496        let p2: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
497        assert!(p1 < p2);
498        assert!(p2 > p1);
499    }
500
501    #[test]
502    fn compares_with_a_str() {
503        let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
504        // by ref
505        assert!(&path_and_query < "/c/world&foo=bar");
506        assert!("/c/world&foo=bar" > &path_and_query);
507        assert!(&path_and_query > "/a/world&foo=bar");
508        assert!("/a/world&foo=bar" < &path_and_query);
509
510        // by val
511        assert!(path_and_query < "/c/world&foo=bar");
512        assert!("/c/world&foo=bar" > path_and_query);
513        assert!(path_and_query > "/a/world&foo=bar");
514        assert!("/a/world&foo=bar" < path_and_query);
515    }
516
517    #[test]
518    fn compares_with_a_string() {
519        let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
520        assert!(path_and_query < "/c/world&foo=bar".to_string());
521        assert!("/c/world&foo=bar".to_string() > path_and_query);
522        assert!(path_and_query > "/a/world&foo=bar".to_string());
523        assert!("/a/world&foo=bar".to_string() < path_and_query);
524    }
525
526    #[test]
527    fn ignores_valid_percent_encodings() {
528        assert_eq!("/a%20b", pq("/a%20b?r=1").path());
529        assert_eq!("qr=%31", pq("/a/b?qr=%31").query().unwrap());
530    }
531
532    #[test]
533    fn ignores_invalid_percent_encodings() {
534        assert_eq!("/a%%b", pq("/a%%b?r=1").path());
535        assert_eq!("/aaa%", pq("/aaa%").path());
536        assert_eq!("/aaa%", pq("/aaa%?r=1").path());
537        assert_eq!("/aa%2", pq("/aa%2").path());
538        assert_eq!("/aa%2", pq("/aa%2?r=1").path());
539        assert_eq!("qr=%3", pq("/a/b?qr=%3").query().unwrap());
540    }
541
542    #[test]
543    fn json_is_fine() {
544        assert_eq!(r#"/{"bread":"baguette"}"#, pq(r#"/{"bread":"baguette"}"#).path());
545    }
546
547    fn pq(s: &str) -> PathAndQuery {
548        s.parse().expect(&format!("parsing {}", s))
549    }
550}