1 | use std::convert::TryFrom; |
2 | use std::str::FromStr; |
3 | use std::{cmp, fmt, hash, str}; |
4 | |
5 | use bytes::Bytes; |
6 | |
7 | use super::{ErrorKind, InvalidUri}; |
8 | use crate::byte_str::ByteStr; |
9 | |
10 | /// Represents the path component of a URI |
11 | #[derive (Clone)] |
12 | pub struct PathAndQuery { |
13 | pub(super) data: ByteStr, |
14 | pub(super) query: u16, |
15 | } |
16 | |
17 | const NONE: u16 = ::std::u16::MAX; |
18 | |
19 | impl 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 | |
278 | impl<'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(src:Bytes::copy_from_slice(data:s)) |
283 | } |
284 | } |
285 | |
286 | impl<'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 | |
294 | impl<'a> TryFrom<Vec<u8>> for PathAndQuery { |
295 | type Error = InvalidUri; |
296 | #[inline ] |
297 | fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> { |
298 | PathAndQuery::from_shared(src:vec.into()) |
299 | } |
300 | } |
301 | |
302 | impl TryFrom<String> for PathAndQuery { |
303 | type Error = InvalidUri; |
304 | #[inline ] |
305 | fn try_from(s: String) -> Result<Self, Self::Error> { |
306 | PathAndQuery::from_shared(src:s.into()) |
307 | } |
308 | } |
309 | |
310 | impl TryFrom<&String> for PathAndQuery { |
311 | type Error = InvalidUri; |
312 | #[inline ] |
313 | fn try_from(s: &String) -> Result<Self, Self::Error> { |
314 | TryFrom::try_from(s.as_bytes()) |
315 | } |
316 | } |
317 | |
318 | impl FromStr for PathAndQuery { |
319 | type Err = InvalidUri; |
320 | #[inline ] |
321 | fn from_str(s: &str) -> Result<Self, InvalidUri> { |
322 | TryFrom::try_from(s) |
323 | } |
324 | } |
325 | |
326 | impl fmt::Debug for PathAndQuery { |
327 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
328 | fmt::Display::fmt(self, f) |
329 | } |
330 | } |
331 | |
332 | impl fmt::Display for PathAndQuery { |
333 | fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { |
334 | if !self.data.is_empty() { |
335 | match self.data.as_bytes()[0] { |
336 | b'/' | b'*' => write!(fmt, " {}" , &self.data[..]), |
337 | _ => write!(fmt, "/ {}" , &self.data[..]), |
338 | } |
339 | } else { |
340 | write!(fmt, "/" ) |
341 | } |
342 | } |
343 | } |
344 | |
345 | impl hash::Hash for PathAndQuery { |
346 | fn hash<H: hash::Hasher>(&self, state: &mut H) { |
347 | self.data.hash(state); |
348 | } |
349 | } |
350 | |
351 | // ===== PartialEq / PartialOrd ===== |
352 | |
353 | impl PartialEq for PathAndQuery { |
354 | #[inline ] |
355 | fn eq(&self, other: &PathAndQuery) -> bool { |
356 | self.data == other.data |
357 | } |
358 | } |
359 | |
360 | impl Eq for PathAndQuery {} |
361 | |
362 | impl PartialEq<str> for PathAndQuery { |
363 | #[inline ] |
364 | fn eq(&self, other: &str) -> bool { |
365 | self.as_str() == other |
366 | } |
367 | } |
368 | |
369 | impl<'a> PartialEq<PathAndQuery> for &'a str { |
370 | #[inline ] |
371 | fn eq(&self, other: &PathAndQuery) -> bool { |
372 | self == &other.as_str() |
373 | } |
374 | } |
375 | |
376 | impl<'a> PartialEq<&'a str> for PathAndQuery { |
377 | #[inline ] |
378 | fn eq(&self, other: &&'a str) -> bool { |
379 | self.as_str() == *other |
380 | } |
381 | } |
382 | |
383 | impl PartialEq<PathAndQuery> for str { |
384 | #[inline ] |
385 | fn eq(&self, other: &PathAndQuery) -> bool { |
386 | self == other.as_str() |
387 | } |
388 | } |
389 | |
390 | impl PartialEq<String> for PathAndQuery { |
391 | #[inline ] |
392 | fn eq(&self, other: &String) -> bool { |
393 | self.as_str() == other.as_str() |
394 | } |
395 | } |
396 | |
397 | impl PartialEq<PathAndQuery> for String { |
398 | #[inline ] |
399 | fn eq(&self, other: &PathAndQuery) -> bool { |
400 | self.as_str() == other.as_str() |
401 | } |
402 | } |
403 | |
404 | impl PartialOrd for PathAndQuery { |
405 | #[inline ] |
406 | fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> { |
407 | self.as_str().partial_cmp(other.as_str()) |
408 | } |
409 | } |
410 | |
411 | impl PartialOrd<str> for PathAndQuery { |
412 | #[inline ] |
413 | fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { |
414 | self.as_str().partial_cmp(other) |
415 | } |
416 | } |
417 | |
418 | impl PartialOrd<PathAndQuery> for str { |
419 | #[inline ] |
420 | fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> { |
421 | self.partial_cmp(other.as_str()) |
422 | } |
423 | } |
424 | |
425 | impl<'a> PartialOrd<&'a str> for PathAndQuery { |
426 | #[inline ] |
427 | fn partial_cmp(&self, other: &&'a str) -> Option<cmp::Ordering> { |
428 | self.as_str().partial_cmp(*other) |
429 | } |
430 | } |
431 | |
432 | impl<'a> PartialOrd<PathAndQuery> for &'a str { |
433 | #[inline ] |
434 | fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> { |
435 | self.partial_cmp(&other.as_str()) |
436 | } |
437 | } |
438 | |
439 | impl PartialOrd<String> for PathAndQuery { |
440 | #[inline ] |
441 | fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> { |
442 | self.as_str().partial_cmp(other.as_str()) |
443 | } |
444 | } |
445 | |
446 | impl PartialOrd<PathAndQuery> for String { |
447 | #[inline ] |
448 | fn partial_cmp(&self, other: &PathAndQuery) -> Option<cmp::Ordering> { |
449 | self.as_str().partial_cmp(other.as_str()) |
450 | } |
451 | } |
452 | |
453 | #[cfg (test)] |
454 | mod tests { |
455 | use super::*; |
456 | |
457 | #[test ] |
458 | fn equal_to_self_of_same_path() { |
459 | let p1: PathAndQuery = "/hello/world&foo=bar" .parse().unwrap(); |
460 | let p2: PathAndQuery = "/hello/world&foo=bar" .parse().unwrap(); |
461 | assert_eq!(p1, p2); |
462 | assert_eq!(p2, p1); |
463 | } |
464 | |
465 | #[test ] |
466 | fn not_equal_to_self_of_different_path() { |
467 | let p1: PathAndQuery = "/hello/world&foo=bar" .parse().unwrap(); |
468 | let p2: PathAndQuery = "/world&foo=bar" .parse().unwrap(); |
469 | assert_ne!(p1, p2); |
470 | assert_ne!(p2, p1); |
471 | } |
472 | |
473 | #[test ] |
474 | fn equates_with_a_str() { |
475 | let path_and_query: PathAndQuery = "/hello/world&foo=bar" .parse().unwrap(); |
476 | assert_eq!(&path_and_query, "/hello/world&foo=bar" ); |
477 | assert_eq!("/hello/world&foo=bar" , &path_and_query); |
478 | assert_eq!(path_and_query, "/hello/world&foo=bar" ); |
479 | assert_eq!("/hello/world&foo=bar" , path_and_query); |
480 | } |
481 | |
482 | #[test ] |
483 | fn not_equal_with_a_str_of_a_different_path() { |
484 | let path_and_query: PathAndQuery = "/hello/world&foo=bar" .parse().unwrap(); |
485 | // as a reference |
486 | assert_ne!(&path_and_query, "/hello&foo=bar" ); |
487 | assert_ne!("/hello&foo=bar" , &path_and_query); |
488 | // without reference |
489 | assert_ne!(path_and_query, "/hello&foo=bar" ); |
490 | assert_ne!("/hello&foo=bar" , path_and_query); |
491 | } |
492 | |
493 | #[test ] |
494 | fn equates_with_a_string() { |
495 | let path_and_query: PathAndQuery = "/hello/world&foo=bar" .parse().unwrap(); |
496 | assert_eq!(path_and_query, "/hello/world&foo=bar" .to_string()); |
497 | assert_eq!("/hello/world&foo=bar" .to_string(), path_and_query); |
498 | } |
499 | |
500 | #[test ] |
501 | fn not_equal_with_a_string_of_a_different_path() { |
502 | let path_and_query: PathAndQuery = "/hello/world&foo=bar" .parse().unwrap(); |
503 | assert_ne!(path_and_query, "/hello&foo=bar" .to_string()); |
504 | assert_ne!("/hello&foo=bar" .to_string(), path_and_query); |
505 | } |
506 | |
507 | #[test ] |
508 | fn compares_to_self() { |
509 | let p1: PathAndQuery = "/a/world&foo=bar" .parse().unwrap(); |
510 | let p2: PathAndQuery = "/b/world&foo=bar" .parse().unwrap(); |
511 | assert!(p1 < p2); |
512 | assert!(p2 > p1); |
513 | } |
514 | |
515 | #[test ] |
516 | fn compares_with_a_str() { |
517 | let path_and_query: PathAndQuery = "/b/world&foo=bar" .parse().unwrap(); |
518 | // by ref |
519 | assert!(&path_and_query < "/c/world&foo=bar" ); |
520 | assert!("/c/world&foo=bar" > &path_and_query); |
521 | assert!(&path_and_query > "/a/world&foo=bar" ); |
522 | assert!("/a/world&foo=bar" < &path_and_query); |
523 | |
524 | // by val |
525 | assert!(path_and_query < "/c/world&foo=bar" ); |
526 | assert!("/c/world&foo=bar" > path_and_query); |
527 | assert!(path_and_query > "/a/world&foo=bar" ); |
528 | assert!("/a/world&foo=bar" < path_and_query); |
529 | } |
530 | |
531 | #[test ] |
532 | fn compares_with_a_string() { |
533 | let path_and_query: PathAndQuery = "/b/world&foo=bar" .parse().unwrap(); |
534 | assert!(path_and_query < "/c/world&foo=bar" .to_string()); |
535 | assert!("/c/world&foo=bar" .to_string() > path_and_query); |
536 | assert!(path_and_query > "/a/world&foo=bar" .to_string()); |
537 | assert!("/a/world&foo=bar" .to_string() < path_and_query); |
538 | } |
539 | |
540 | #[test ] |
541 | fn ignores_valid_percent_encodings() { |
542 | assert_eq!("/a%20b" , pq("/a%20b?r=1" ).path()); |
543 | assert_eq!("qr=%31" , pq("/a/b?qr=%31" ).query().unwrap()); |
544 | } |
545 | |
546 | #[test ] |
547 | fn ignores_invalid_percent_encodings() { |
548 | assert_eq!("/a%%b" , pq("/a%%b?r=1" ).path()); |
549 | assert_eq!("/aaa%" , pq("/aaa%" ).path()); |
550 | assert_eq!("/aaa%" , pq("/aaa%?r=1" ).path()); |
551 | assert_eq!("/aa%2" , pq("/aa%2" ).path()); |
552 | assert_eq!("/aa%2" , pq("/aa%2?r=1" ).path()); |
553 | assert_eq!("qr=%3" , pq("/a/b?qr=%3" ).query().unwrap()); |
554 | } |
555 | |
556 | #[test ] |
557 | fn json_is_fine() { |
558 | assert_eq!(r#"/{"bread":"baguette"}"# , pq(r#"/{"bread":"baguette"}"# ).path()); |
559 | } |
560 | |
561 | fn pq(s: &str) -> PathAndQuery { |
562 | s.parse().expect(&format!("parsing {}" , s)) |
563 | } |
564 | } |
565 | |