1use std::convert::TryFrom;
2use std::str::FromStr;
3use std::{cmp, fmt, hash, str};
4
5use bytes::Bytes;
6
7use super::{ErrorKind, InvalidUri, MAX_LEN};
8use crate::byte_str::ByteStr;
9
10#[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 pub(super) fn from_shared(mut src: Bytes) -> Result<Self, InvalidUri> {
22 let Scanned {
23 query,
24 fragment,
25 is_maybe_not_utf8,
26 } = scan_path_and_query(&src)?;
27
28 if let Some(i) = fragment {
29 src.truncate(i as usize);
30 }
31
32 let data = if is_maybe_not_utf8 {
33 ByteStr::from_utf8(src).map_err(|_| ErrorKind::InvalidUriChar)?
34 } else {
35 unsafe { ByteStr::from_utf8_unchecked(src) }
36 };
37
38 Ok(PathAndQuery { data, query })
39 }
40
41 #[inline]
60 pub const fn from_static(src: &'static str) -> Self {
61 match scan_path_and_query(src.as_bytes()) {
62 Ok(Scanned {
63 query,
64 fragment: None,
65 is_maybe_not_utf8: false,
66 }) => PathAndQuery {
67 data: ByteStr::from_static(src),
68 query,
69 },
70 _ => panic!("static str is not valid path"),
72 }
73 }
74
75 pub fn from_maybe_shared<T>(src: T) -> Result<Self, InvalidUri>
80 where
81 T: AsRef<[u8]> + 'static,
82 {
83 if_downcast_into!(T, Bytes, src, {
84 return PathAndQuery::from_shared(src);
85 });
86
87 PathAndQuery::try_from(src.as_ref())
88 }
89
90 pub(super) fn empty() -> Self {
91 PathAndQuery {
92 data: ByteStr::new(),
93 query: NONE,
94 }
95 }
96
97 pub(super) fn slash() -> Self {
98 PathAndQuery {
99 data: ByteStr::from_static("/"),
100 query: NONE,
101 }
102 }
103
104 pub(super) fn star() -> Self {
105 PathAndQuery {
106 data: ByteStr::from_static("*"),
107 query: NONE,
108 }
109 }
110
111 #[inline]
134 pub fn path(&self) -> &str {
135 let ret = if self.query == NONE {
136 &self.data[..]
137 } else {
138 &self.data[..self.query as usize]
139 };
140
141 if ret.is_empty() {
142 return "/";
143 }
144
145 ret
146 }
147
148 #[inline]
183 pub fn query(&self) -> Option<&str> {
184 if self.query == NONE {
185 None
186 } else {
187 let i = self.query + 1;
188 Some(&self.data[i as usize..])
189 }
190 }
191
192 #[inline]
214 pub fn as_str(&self) -> &str {
215 let ret = &self.data[..];
216 if ret.is_empty() {
217 return "/";
218 }
219 ret
220 }
221}
222
223impl TryFrom<&[u8]> for PathAndQuery {
224 type Error = InvalidUri;
225 #[inline]
226 fn try_from(s: &[u8]) -> Result<Self, Self::Error> {
227 PathAndQuery::from_shared(Bytes::copy_from_slice(s))
228 }
229}
230
231impl TryFrom<&str> for PathAndQuery {
232 type Error = InvalidUri;
233 #[inline]
234 fn try_from(s: &str) -> Result<Self, Self::Error> {
235 TryFrom::try_from(s.as_bytes())
236 }
237}
238
239impl TryFrom<Vec<u8>> for PathAndQuery {
240 type Error = InvalidUri;
241 #[inline]
242 fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> {
243 PathAndQuery::from_shared(vec.into())
244 }
245}
246
247impl TryFrom<String> for PathAndQuery {
248 type Error = InvalidUri;
249 #[inline]
250 fn try_from(s: String) -> Result<Self, Self::Error> {
251 PathAndQuery::from_shared(s.into())
252 }
253}
254
255impl TryFrom<&String> for PathAndQuery {
256 type Error = InvalidUri;
257 #[inline]
258 fn try_from(s: &String) -> Result<Self, Self::Error> {
259 TryFrom::try_from(s.as_bytes())
260 }
261}
262
263impl FromStr for PathAndQuery {
264 type Err = InvalidUri;
265 #[inline]
266 fn from_str(s: &str) -> Result<Self, InvalidUri> {
267 TryFrom::try_from(s)
268 }
269}
270
271impl fmt::Debug for PathAndQuery {
272 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273 fmt::Display::fmt(self, f)
274 }
275}
276
277impl fmt::Display for PathAndQuery {
278 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
279 if !self.data.is_empty() {
280 match self.data.as_bytes()[0] {
281 b'/' | b'*' => fmt.write_str(&self.data),
282 _ => {
283 fmt.write_str("/")?;
284 fmt.write_str(&self.data)
285 }
286 }
287 } else {
288 fmt.write_str("/")
289 }
290 }
291}
292
293impl hash::Hash for PathAndQuery {
294 fn hash<H: hash::Hasher>(&self, state: &mut H) {
295 self.data.hash(state);
296 }
297}
298
299impl PartialEq for PathAndQuery {
302 #[inline]
303 fn eq(&self, other: &PathAndQuery) -> bool {
304 self.data == other.data
305 }
306}
307
308impl Eq for PathAndQuery {}
309
310impl PartialEq<str> for PathAndQuery {
311 #[inline]
312 fn eq(&self, other: &str) -> bool {
313 self.as_str() == other
314 }
315}
316
317impl PartialEq<PathAndQuery> for &str {
318 #[inline]
319 fn eq(&self, other: &PathAndQuery) -> bool {
320 self == &other.as_str()
321 }
322}
323
324impl PartialEq<&str> for PathAndQuery {
325 #[inline]
326 fn eq(&self, other: &&str) -> bool {
327 self.as_str() == *other
328 }
329}
330
331impl PartialEq<PathAndQuery> for str {
332 #[inline]
333 fn eq(&self, other: &PathAndQuery) -> bool {
334 self == other.as_str()
335 }
336}
337
338impl PartialEq<String> for PathAndQuery {
339 #[inline]
340 fn eq(&self, other: &String) -> bool {
341 self.as_str() == other.as_str()
342 }
343}
344
345impl PartialEq<PathAndQuery> for String {
346 #[inline]
347 fn eq(&self, other: &PathAndQuery) -> bool {
348 self.as_str() == other.as_str()
349 }
350}
351
352impl PartialOrd for PathAndQuery {
353 #[inline]
354 fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
355 self.as_str().partial_cmp(other.as_str())
356 }
357}
358
359impl PartialOrd<str> for PathAndQuery {
360 #[inline]
361 fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> {
362 self.as_str().partial_cmp(other)
363 }
364}
365
366impl PartialOrd<PathAndQuery> for str {
367 #[inline]
368 fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
369 self.partial_cmp(other.as_str())
370 }
371}
372
373impl PartialOrd<&str> for PathAndQuery {
374 #[inline]
375 fn partial_cmp(&self, other: &&str) -> Option<cmp::Ordering> {
376 self.as_str().partial_cmp(*other)
377 }
378}
379
380impl PartialOrd<PathAndQuery> for &str {
381 #[inline]
382 fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
383 self.partial_cmp(&other.as_str())
384 }
385}
386
387impl PartialOrd<String> for PathAndQuery {
388 #[inline]
389 fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> {
390 self.as_str().partial_cmp(other.as_str())
391 }
392}
393
394impl PartialOrd<PathAndQuery> for String {
395 #[inline]
396 fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> {
397 self.as_str().partial_cmp(other.as_str())
398 }
399}
400
401struct Scanned {
406 query: u16,
407 fragment: Option<u16>,
408 is_maybe_not_utf8: bool,
409}
410
411const CLASS_VALID: u8 = 0;
413const CLASS_QUERY: u8 = 1;
414const CLASS_FRAGMENT: u8 = 2;
415const CLASS_HIGH: u8 = 3;
416const CLASS_INVALID: u8 = 4;
417
418const fn build_path_map() -> [u8; 256] {
419 let mut t = [CLASS_INVALID; 256];
420 let mut i = 0;
421 while i < 256 {
422 t[i] = match i as u8 {
424 b'?' => CLASS_QUERY,
425 b'#' => CLASS_FRAGMENT,
426
427 0x21 | 0x24..=0x3B | 0x3D | 0x40..=0x5F | 0x61..=0x7A | 0x7C | 0x7E => CLASS_VALID,
429
430 0x80..=0xFF => CLASS_HIGH,
432
433 b'"' | b'{' | b'}' => CLASS_VALID,
436
437 _ => CLASS_INVALID,
438 };
439 i += 1;
440 }
441 t
442}
443
444const fn build_query_map() -> [u8; 256] {
445 let mut t = [CLASS_INVALID; 256];
446 let mut i = 0;
447 while i < 256 {
448 t[i] = match i as u8 {
450 b'#' => CLASS_FRAGMENT,
451
452 0x21 | 0x24..=0x3B | 0x3D | 0x3F..=0x7E => CLASS_VALID,
454
455 0x80..=0xFF => CLASS_HIGH,
456
457 _ => CLASS_INVALID,
458 };
459 i += 1;
460 }
461 t
462}
463
464const PATH_MAP: [u8; 256] = build_path_map();
465const QUERY_MAP: [u8; 256] = build_query_map();
466
467const fn scan_path_and_query(bytes: &[u8]) -> Result<Scanned, ErrorKind> {
468 let mut i = 0;
469 let mut query = NONE;
470 let mut fragment = None;
471
472 let mut is_maybe_not_utf8 = false;
473
474 if bytes.is_empty() {
475 return Err(ErrorKind::Empty);
476 }
477
478 if bytes.len() > MAX_LEN {
479 return Err(ErrorKind::TooLong);
480 }
481
482 if bytes.len() == 1 && bytes[0] == b'*' {
483 return Ok(Scanned {
484 query,
485 fragment,
486 is_maybe_not_utf8: false,
487 });
488 }
489
490 if !matches!(bytes[0], b'/' | b'?' | b'#') {
491 return Err(ErrorKind::PathDoesNotStartWithSlash);
492 }
493
494 while i < bytes.len() {
495 match PATH_MAP[bytes[i] as usize] {
496 CLASS_VALID => {}
497 CLASS_QUERY => {
498 debug_assert!(query == NONE);
499 query = i as u16;
500 i += 1;
501 break;
502 }
503 CLASS_FRAGMENT => {
504 fragment = Some(i as u16);
505 break;
506 }
507 CLASS_HIGH => {
508 is_maybe_not_utf8 = true;
509 }
510 _ => return Err(ErrorKind::InvalidUriChar),
511 }
512 i += 1;
513 }
514
515 if query != NONE {
517 while i < bytes.len() {
518 match QUERY_MAP[bytes[i] as usize] {
519 CLASS_VALID => {}
520 CLASS_HIGH => {
521 is_maybe_not_utf8 = true;
522 }
523 CLASS_FRAGMENT => {
524 fragment = Some(i as u16);
525 break;
526 }
527 _ => return Err(ErrorKind::InvalidUriChar),
528 }
529 i += 1;
530 }
531 }
532
533 Ok(Scanned {
534 query,
535 fragment,
536 is_maybe_not_utf8,
537 })
538}
539
540#[cfg(test)]
541mod tests {
542 use super::*;
543
544 #[test]
545 fn equal_to_self_of_same_path() {
546 let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
547 let p2: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
548 assert_eq!(p1, p2);
549 assert_eq!(p2, p1);
550 }
551
552 #[test]
553 fn not_equal_to_self_of_different_path() {
554 let p1: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
555 let p2: PathAndQuery = "/world&foo=bar".parse().unwrap();
556 assert_ne!(p1, p2);
557 assert_ne!(p2, p1);
558 }
559
560 #[test]
561 fn equates_with_a_str() {
562 let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
563 assert_eq!(&path_and_query, "/hello/world&foo=bar");
564 assert_eq!("/hello/world&foo=bar", &path_and_query);
565 assert_eq!(path_and_query, "/hello/world&foo=bar");
566 assert_eq!("/hello/world&foo=bar", path_and_query);
567 }
568
569 #[test]
570 fn not_equal_with_a_str_of_a_different_path() {
571 let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
572 assert_ne!(&path_and_query, "/hello&foo=bar");
574 assert_ne!("/hello&foo=bar", &path_and_query);
575 assert_ne!(path_and_query, "/hello&foo=bar");
577 assert_ne!("/hello&foo=bar", path_and_query);
578 }
579
580 #[test]
581 fn equates_with_a_string() {
582 let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
583 assert_eq!(path_and_query, "/hello/world&foo=bar".to_string());
584 assert_eq!("/hello/world&foo=bar".to_string(), path_and_query);
585 }
586
587 #[test]
588 fn not_equal_with_a_string_of_a_different_path() {
589 let path_and_query: PathAndQuery = "/hello/world&foo=bar".parse().unwrap();
590 assert_ne!(path_and_query, "/hello&foo=bar".to_string());
591 assert_ne!("/hello&foo=bar".to_string(), path_and_query);
592 }
593
594 #[test]
595 fn compares_to_self() {
596 let p1: PathAndQuery = "/a/world&foo=bar".parse().unwrap();
597 let p2: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
598 assert!(p1 < p2);
599 assert!(p2 > p1);
600 }
601
602 #[test]
603 fn compares_with_a_str() {
604 let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
605 assert!(&path_and_query < "/c/world&foo=bar");
607 assert!("/c/world&foo=bar" > &path_and_query);
608 assert!(&path_and_query > "/a/world&foo=bar");
609 assert!("/a/world&foo=bar" < &path_and_query);
610
611 assert!(path_and_query < "/c/world&foo=bar");
613 assert!("/c/world&foo=bar" > path_and_query);
614 assert!(path_and_query > "/a/world&foo=bar");
615 assert!("/a/world&foo=bar" < path_and_query);
616 }
617
618 #[test]
619 fn compares_with_a_string() {
620 let path_and_query: PathAndQuery = "/b/world&foo=bar".parse().unwrap();
621 assert!(path_and_query < "/c/world&foo=bar".to_string());
622 assert!("/c/world&foo=bar".to_string() > path_and_query);
623 assert!(path_and_query > "/a/world&foo=bar".to_string());
624 assert!("/a/world&foo=bar".to_string() < path_and_query);
625 }
626
627 #[test]
628 fn ignores_valid_percent_encodings() {
629 assert_eq!("/a%20b", pq("/a%20b?r=1").path());
630 assert_eq!("qr=%31", pq("/a/b?qr=%31").query().unwrap());
631 }
632
633 #[test]
634 fn ignores_invalid_percent_encodings() {
635 assert_eq!("/a%%b", pq("/a%%b?r=1").path());
636 assert_eq!("/aaa%", pq("/aaa%").path());
637 assert_eq!("/aaa%", pq("/aaa%?r=1").path());
638 assert_eq!("/aa%2", pq("/aa%2").path());
639 assert_eq!("/aa%2", pq("/aa%2?r=1").path());
640 assert_eq!("qr=%3", pq("/a/b?qr=%3").query().unwrap());
641 }
642
643 #[test]
644 fn allow_utf8_in_path() {
645 assert_eq!("/🍕", pq("/🍕").path());
646 }
647
648 #[test]
649 fn allow_utf8_in_query() {
650 assert_eq!(Some("pizza=🍕"), pq("/test?pizza=🍕").query());
651 }
652
653 #[test]
654 fn rejects_invalid_utf8_in_path() {
655 PathAndQuery::try_from(&[b'/', 0xFF][..]).expect_err("reject invalid utf8");
656 }
657
658 #[test]
659 fn rejects_invalid_utf8_in_query() {
660 PathAndQuery::try_from(&[b'/', b'a', b'?', 0xFF][..]).expect_err("reject invalid utf8");
661 }
662
663 #[test]
664 fn rejects_empty_string() {
665 PathAndQuery::try_from("").expect_err("reject empty str");
666 }
667
668 #[test]
669 fn requires_starting_with_slash() {
670 PathAndQuery::try_from("sneaky").expect_err("reject missing slash");
671 }
672
673 #[test]
674 fn rejects_del_in_path() {
675 PathAndQuery::try_from(&[b'/', 0x7F][..]).expect_err("reject DEL");
676 }
677
678 #[test]
679 fn rejects_del_in_query() {
680 PathAndQuery::try_from(&[b'/', b'a', b'?', 0x7F][..]).expect_err("reject DEL");
681 }
682
683 #[test]
684 fn rejects_too_long_path_and_query() {
685 let path = format!("/{}?query", "a".repeat(MAX_LEN));
686 let err = PathAndQuery::try_from(path).expect_err("reject overly long path and query");
687 assert_eq!(err.0, ErrorKind::TooLong);
688 }
689
690 #[test]
691 fn accepts_max_length_path_and_query() {
692 let path = format!("/{}?", "a".repeat(MAX_LEN - 2));
693 let path_and_query = PathAndQuery::try_from(path).expect("accept maximum length");
694 assert_eq!(path_and_query.as_str().len(), MAX_LEN);
695 assert_eq!(path_and_query.query(), Some(""));
696 }
697
698 #[test]
699 fn json_is_fine() {
700 assert_eq!(
701 r#"/{"bread":"baguette"}"#,
702 pq(r#"/{"bread":"baguette"}"#).path()
703 );
704 }
705
706 fn pq(s: &str) -> PathAndQuery {
707 s.parse().expect(&format!("parsing {}", s))
708 }
709}