no_std_http/uri/
path.rs

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