http/uri/
path.rs

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