/src/http/src/uri/authority.rs
Line | Count | Source |
1 | | use std::convert::TryFrom; |
2 | | use std::hash::{Hash, Hasher}; |
3 | | use std::str::FromStr; |
4 | | use std::{cmp, fmt, str}; |
5 | | |
6 | | use bytes::Bytes; |
7 | | |
8 | | use super::{ErrorKind, InvalidUri, Port, URI_CHARS}; |
9 | | use crate::byte_str::ByteStr; |
10 | | |
11 | | /// Validation result for authority parsing. |
12 | | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
13 | | enum AuthorityError { |
14 | | Empty, |
15 | | InvalidUriChar, |
16 | | InvalidAuthority, |
17 | | TooManyColons, |
18 | | MismatchedBrackets, |
19 | | InvalidBracketUsage, |
20 | | EmptyAfterAt, |
21 | | InvalidPercent, |
22 | | } |
23 | | |
24 | | /// Represents the authority component of a URI. |
25 | | #[derive(Clone)] |
26 | | pub struct Authority { |
27 | | pub(super) data: ByteStr, |
28 | | } |
29 | | |
30 | | impl Authority { |
31 | 6.40k | pub(super) fn empty() -> Self { |
32 | 6.40k | Authority { |
33 | 6.40k | data: ByteStr::new(), |
34 | 6.40k | } |
35 | 6.40k | } |
36 | | |
37 | | // Not public while `bytes` is unstable. |
38 | 1.07k | pub(super) fn from_shared(s: Bytes) -> Result<Self, InvalidUri> { |
39 | | // Precondition on create_authority: trivially satisfied by the |
40 | | // identity closure |
41 | 1.07k | create_authority(s, |s| s) |
42 | 1.07k | } |
43 | | |
44 | | /// Attempt to convert an `Authority` from a static string. |
45 | | /// |
46 | | /// This function will not perform any copying, and the string will be |
47 | | /// checked if it is empty or contains an invalid character. |
48 | | /// |
49 | | /// # Panics |
50 | | /// |
51 | | /// This function panics if the argument contains invalid characters or |
52 | | /// is empty. |
53 | | /// |
54 | | /// # Examples |
55 | | /// |
56 | | /// ``` |
57 | | /// # use http::uri::Authority; |
58 | | /// let authority = Authority::from_static("example.com"); |
59 | | /// assert_eq!(authority.host(), "example.com"); |
60 | | /// ``` |
61 | | #[inline] |
62 | | pub const fn from_static(src: &'static str) -> Self { |
63 | | match validate_authority_bytes(src.as_bytes()) { |
64 | | Ok(_) => Authority { |
65 | | data: ByteStr::from_static(src), |
66 | | }, |
67 | | Err(_) => panic!("static str is not valid authority"), |
68 | | } |
69 | | } |
70 | | |
71 | | /// Attempt to convert a `Bytes` buffer to a `Authority`. |
72 | | /// |
73 | | /// This will try to prevent a copy if the type passed is the type used |
74 | | /// internally, and will copy the data if it is not. |
75 | | pub fn from_maybe_shared<T>(src: T) -> Result<Self, InvalidUri> |
76 | | where |
77 | | T: AsRef<[u8]> + 'static, |
78 | | { |
79 | | if_downcast_into!(T, Bytes, src, { |
80 | | return Authority::from_shared(src); |
81 | | }); |
82 | | |
83 | | Authority::try_from(src.as_ref()) |
84 | | } |
85 | | |
86 | | // Note: this may return an *empty* Authority. You might want `parse_non_empty`. |
87 | | // Postcondition: for all Ok() returns, s[..ret.unwrap()] is valid UTF-8 where |
88 | | // ret is the return value. |
89 | 1.51k | pub(super) fn parse(s: &[u8]) -> Result<usize, InvalidUri> { |
90 | 1.51k | validate_authority_bytes(s).map_err(|e| { |
91 | 326 | match e { |
92 | 6 | AuthorityError::Empty => ErrorKind::Empty, |
93 | 278 | AuthorityError::InvalidUriChar => ErrorKind::InvalidUriChar, |
94 | | AuthorityError::InvalidAuthority |
95 | | | AuthorityError::MismatchedBrackets |
96 | | | AuthorityError::InvalidBracketUsage |
97 | | | AuthorityError::EmptyAfterAt |
98 | | | AuthorityError::InvalidPercent |
99 | 42 | | AuthorityError::TooManyColons => ErrorKind::InvalidAuthority, |
100 | | } |
101 | 326 | .into() |
102 | 326 | }) |
103 | 1.51k | } |
104 | | |
105 | | // Parse bytes as an Authority, not allowing an empty string. |
106 | | // |
107 | | // This should be used by functions that allow a user to parse |
108 | | // an `Authority` by itself. |
109 | | // |
110 | | // Postcondition: for all Ok() returns, s[..ret.unwrap()] is valid UTF-8 where |
111 | | // ret is the return value. |
112 | 1.07k | fn parse_non_empty(s: &[u8]) -> Result<usize, InvalidUri> { |
113 | 1.07k | if s.is_empty() { |
114 | 0 | return Err(ErrorKind::Empty.into()); |
115 | 1.07k | } |
116 | 1.07k | Authority::parse(s) |
117 | 1.07k | } |
118 | | |
119 | | /// Get the host of this `Authority`. |
120 | | /// |
121 | | /// The host subcomponent of authority is identified by an IP literal |
122 | | /// encapsulated within square brackets, an IPv4 address in dotted- decimal |
123 | | /// form, or a registered name. The host subcomponent is **case-insensitive**. |
124 | | /// |
125 | | /// ```notrust |
126 | | /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 |
127 | | /// |---------| |
128 | | /// | |
129 | | /// host |
130 | | /// ``` |
131 | | /// |
132 | | /// # Examples |
133 | | /// |
134 | | /// ``` |
135 | | /// # use http::uri::*; |
136 | | /// let authority: Authority = "example.org:80".parse().unwrap(); |
137 | | /// |
138 | | /// assert_eq!(authority.host(), "example.org"); |
139 | | /// ``` |
140 | | #[inline] |
141 | | pub fn host(&self) -> &str { |
142 | | host(self.as_str()) |
143 | | } |
144 | | |
145 | | /// Get the port part of this `Authority`. |
146 | | /// |
147 | | /// The port subcomponent of authority is designated by an optional port |
148 | | /// number following the host and delimited from it by a single colon (":") |
149 | | /// character. It can be turned into a decimal port number with the `as_u16` |
150 | | /// method or as a `str` with the `as_str` method. |
151 | | /// |
152 | | /// ```notrust |
153 | | /// abc://username:password@example.com:123/path/data?key=value&key2=value2#fragid1 |
154 | | /// |-| |
155 | | /// | |
156 | | /// port |
157 | | /// ``` |
158 | | /// |
159 | | /// # Examples |
160 | | /// |
161 | | /// Authority with port |
162 | | /// |
163 | | /// ``` |
164 | | /// # use http::uri::Authority; |
165 | | /// let authority: Authority = "example.org:80".parse().unwrap(); |
166 | | /// |
167 | | /// let port = authority.port().unwrap(); |
168 | | /// assert_eq!(port.as_u16(), 80); |
169 | | /// assert_eq!(port.as_str(), "80"); |
170 | | /// ``` |
171 | | /// |
172 | | /// Authority without port |
173 | | /// |
174 | | /// ``` |
175 | | /// # use http::uri::Authority; |
176 | | /// let authority: Authority = "example.org".parse().unwrap(); |
177 | | /// |
178 | | /// assert!(authority.port().is_none()); |
179 | | /// ``` |
180 | 0 | pub fn port(&self) -> Option<Port<&str>> { |
181 | 0 | let bytes = self.as_str(); |
182 | 0 | bytes |
183 | 0 | .rfind(':') |
184 | 0 | .and_then(|i| Port::from_str(&bytes[i + 1..]).ok()) |
185 | 0 | } |
186 | | |
187 | | /// Get the port of this `Authority` as a `u16`. |
188 | | /// |
189 | | /// # Example |
190 | | /// |
191 | | /// ``` |
192 | | /// # use http::uri::Authority; |
193 | | /// let authority: Authority = "example.org:80".parse().unwrap(); |
194 | | /// |
195 | | /// assert_eq!(authority.port_u16(), Some(80)); |
196 | | /// ``` |
197 | 0 | pub fn port_u16(&self) -> Option<u16> { |
198 | 0 | self.port().map(|p| p.as_u16()) |
199 | 0 | } |
200 | | |
201 | | /// Return a str representation of the authority |
202 | | #[inline] |
203 | 0 | pub fn as_str(&self) -> &str { |
204 | 0 | &self.data[..] |
205 | 0 | } |
206 | | } |
207 | | |
208 | | // Purposefully not public while `bytes` is unstable. |
209 | | // impl TryFrom<Bytes> for Authority |
210 | | |
211 | | impl AsRef<str> for Authority { |
212 | 0 | fn as_ref(&self) -> &str { |
213 | 0 | self.as_str() |
214 | 0 | } |
215 | | } |
216 | | |
217 | | impl PartialEq for Authority { |
218 | 0 | fn eq(&self, other: &Authority) -> bool { |
219 | 0 | self.data.eq_ignore_ascii_case(&other.data) |
220 | 0 | } |
221 | | } |
222 | | |
223 | | impl Eq for Authority {} |
224 | | |
225 | | /// Case-insensitive equality |
226 | | /// |
227 | | /// # Examples |
228 | | /// |
229 | | /// ``` |
230 | | /// # use http::uri::Authority; |
231 | | /// let authority: Authority = "HELLO.com".parse().unwrap(); |
232 | | /// assert_eq!(authority, "hello.coM"); |
233 | | /// assert_eq!("hello.com", authority); |
234 | | /// ``` |
235 | | impl PartialEq<str> for Authority { |
236 | 0 | fn eq(&self, other: &str) -> bool { |
237 | 0 | self.data.eq_ignore_ascii_case(other) |
238 | 0 | } |
239 | | } |
240 | | |
241 | | impl PartialEq<Authority> for str { |
242 | 0 | fn eq(&self, other: &Authority) -> bool { |
243 | 0 | self.eq_ignore_ascii_case(other.as_str()) |
244 | 0 | } |
245 | | } |
246 | | |
247 | | impl PartialEq<Authority> for &str { |
248 | 0 | fn eq(&self, other: &Authority) -> bool { |
249 | 0 | self.eq_ignore_ascii_case(other.as_str()) |
250 | 0 | } |
251 | | } |
252 | | |
253 | | impl PartialEq<&str> for Authority { |
254 | 0 | fn eq(&self, other: &&str) -> bool { |
255 | 0 | self.data.eq_ignore_ascii_case(other) |
256 | 0 | } |
257 | | } |
258 | | |
259 | | impl PartialEq<String> for Authority { |
260 | 0 | fn eq(&self, other: &String) -> bool { |
261 | 0 | self.data.eq_ignore_ascii_case(other.as_str()) |
262 | 0 | } |
263 | | } |
264 | | |
265 | | impl PartialEq<Authority> for String { |
266 | 0 | fn eq(&self, other: &Authority) -> bool { |
267 | 0 | self.as_str().eq_ignore_ascii_case(other.as_str()) |
268 | 0 | } |
269 | | } |
270 | | |
271 | | /// Case-insensitive ordering |
272 | | /// |
273 | | /// # Examples |
274 | | /// |
275 | | /// ``` |
276 | | /// # use http::uri::Authority; |
277 | | /// let authority: Authority = "DEF.com".parse().unwrap(); |
278 | | /// assert!(authority < "ghi.com"); |
279 | | /// assert!(authority > "abc.com"); |
280 | | /// ``` |
281 | | impl PartialOrd for Authority { |
282 | 0 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
283 | 0 | let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
284 | 0 | let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
285 | 0 | left.partial_cmp(right) |
286 | 0 | } |
287 | | } |
288 | | |
289 | | impl PartialOrd<str> for Authority { |
290 | 0 | fn partial_cmp(&self, other: &str) -> Option<cmp::Ordering> { |
291 | 0 | let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
292 | 0 | let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
293 | 0 | left.partial_cmp(right) |
294 | 0 | } |
295 | | } |
296 | | |
297 | | impl PartialOrd<Authority> for str { |
298 | 0 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
299 | 0 | let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
300 | 0 | let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
301 | 0 | left.partial_cmp(right) |
302 | 0 | } |
303 | | } |
304 | | |
305 | | impl PartialOrd<Authority> for &str { |
306 | 0 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
307 | 0 | let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
308 | 0 | let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
309 | 0 | left.partial_cmp(right) |
310 | 0 | } |
311 | | } |
312 | | |
313 | | impl PartialOrd<&str> for Authority { |
314 | 0 | fn partial_cmp(&self, other: &&str) -> Option<cmp::Ordering> { |
315 | 0 | let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
316 | 0 | let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
317 | 0 | left.partial_cmp(right) |
318 | 0 | } |
319 | | } |
320 | | |
321 | | impl PartialOrd<String> for Authority { |
322 | 0 | fn partial_cmp(&self, other: &String) -> Option<cmp::Ordering> { |
323 | 0 | let left = self.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
324 | 0 | let right = other.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
325 | 0 | left.partial_cmp(right) |
326 | 0 | } |
327 | | } |
328 | | |
329 | | impl PartialOrd<Authority> for String { |
330 | 0 | fn partial_cmp(&self, other: &Authority) -> Option<cmp::Ordering> { |
331 | 0 | let left = self.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
332 | 0 | let right = other.data.as_bytes().iter().map(|b| b.to_ascii_lowercase()); |
333 | 0 | left.partial_cmp(right) |
334 | 0 | } |
335 | | } |
336 | | |
337 | | /// Case-insensitive hashing |
338 | | /// |
339 | | /// # Examples |
340 | | /// |
341 | | /// ``` |
342 | | /// # use http::uri::Authority; |
343 | | /// # use std::hash::{Hash, Hasher}; |
344 | | /// # use std::collections::hash_map::DefaultHasher; |
345 | | /// |
346 | | /// let a: Authority = "HELLO.com".parse().unwrap(); |
347 | | /// let b: Authority = "hello.coM".parse().unwrap(); |
348 | | /// |
349 | | /// let mut s = DefaultHasher::new(); |
350 | | /// a.hash(&mut s); |
351 | | /// let a = s.finish(); |
352 | | /// |
353 | | /// let mut s = DefaultHasher::new(); |
354 | | /// b.hash(&mut s); |
355 | | /// let b = s.finish(); |
356 | | /// |
357 | | /// assert_eq!(a, b); |
358 | | /// ``` |
359 | | impl Hash for Authority { |
360 | | fn hash<H>(&self, state: &mut H) |
361 | | where |
362 | | H: Hasher, |
363 | | { |
364 | | self.data.len().hash(state); |
365 | | for &b in self.data.as_bytes() { |
366 | | state.write_u8(b.to_ascii_lowercase()); |
367 | | } |
368 | | } |
369 | | } |
370 | | |
371 | | impl TryFrom<&[u8]> for Authority { |
372 | | type Error = InvalidUri; |
373 | | #[inline] |
374 | 0 | fn try_from(s: &[u8]) -> Result<Self, Self::Error> { |
375 | | // parse first, and only turn into Bytes if valid |
376 | | |
377 | | // Preconditon on create_authority: copy_from_slice() copies all of |
378 | | // bytes from the [u8] parameter into a new Bytes |
379 | 0 | create_authority(s, Bytes::copy_from_slice) |
380 | 0 | } |
381 | | } |
382 | | |
383 | | impl TryFrom<&str> for Authority { |
384 | | type Error = InvalidUri; |
385 | | #[inline] |
386 | 0 | fn try_from(s: &str) -> Result<Self, Self::Error> { |
387 | 0 | TryFrom::try_from(s.as_bytes()) |
388 | 0 | } |
389 | | } |
390 | | |
391 | | impl TryFrom<Vec<u8>> for Authority { |
392 | | type Error = InvalidUri; |
393 | | |
394 | | #[inline] |
395 | | fn try_from(vec: Vec<u8>) -> Result<Self, Self::Error> { |
396 | | Authority::from_shared(vec.into()) |
397 | | } |
398 | | } |
399 | | |
400 | | impl TryFrom<String> for Authority { |
401 | | type Error = InvalidUri; |
402 | | |
403 | | #[inline] |
404 | | fn try_from(t: String) -> Result<Self, Self::Error> { |
405 | | Authority::from_shared(t.into()) |
406 | | } |
407 | | } |
408 | | |
409 | | impl FromStr for Authority { |
410 | | type Err = InvalidUri; |
411 | | |
412 | 0 | fn from_str(s: &str) -> Result<Self, InvalidUri> { |
413 | 0 | TryFrom::try_from(s) |
414 | 0 | } |
415 | | } |
416 | | |
417 | | impl fmt::Debug for Authority { |
418 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
419 | 0 | f.write_str(self.as_str()) |
420 | 0 | } |
421 | | } |
422 | | |
423 | | impl fmt::Display for Authority { |
424 | 0 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
425 | 0 | f.write_str(self.as_str()) |
426 | 0 | } |
427 | | } |
428 | | |
429 | 0 | fn host(auth: &str) -> &str { |
430 | 0 | let host_port = auth |
431 | 0 | .rsplit('@') |
432 | 0 | .next() |
433 | 0 | .expect("split always has at least 1 item"); |
434 | | |
435 | 0 | if host_port.as_bytes()[0] == b'[' { |
436 | 0 | let i = host_port |
437 | 0 | .find(']') |
438 | 0 | .expect("parsing should validate brackets"); |
439 | | // ..= ranges aren't available in 1.20, our minimum Rust version... |
440 | 0 | &host_port[0..i + 1] |
441 | | } else { |
442 | 0 | host_port |
443 | 0 | .split(':') |
444 | 0 | .next() |
445 | 0 | .expect("split always has at least 1 item") |
446 | | } |
447 | 0 | } |
448 | | |
449 | | // Precondition: f converts all of the bytes in the passed in B into the |
450 | | // returned Bytes. |
451 | 1.07k | fn create_authority<B, F>(b: B, f: F) -> Result<Authority, InvalidUri> |
452 | 1.07k | where |
453 | 1.07k | B: AsRef<[u8]>, |
454 | 1.07k | F: FnOnce(B) -> Bytes, |
455 | | { |
456 | 1.07k | let s = b.as_ref(); |
457 | 1.07k | let authority_end = Authority::parse_non_empty(s)?; |
458 | | |
459 | 1.04k | if authority_end != s.len() { |
460 | 3 | return Err(ErrorKind::InvalidUriChar.into()); |
461 | 1.03k | } |
462 | | |
463 | 1.03k | let bytes = f(b); |
464 | | |
465 | 1.03k | Ok(Authority { |
466 | 1.03k | // Safety: the postcondition on parse_non_empty() and the check against |
467 | 1.03k | // s.len() ensure that b is valid UTF-8. The precondition on f ensures |
468 | 1.03k | // that this is carried through to bytes. |
469 | 1.03k | data: unsafe { ByteStr::from_utf8_unchecked(bytes) }, |
470 | 1.03k | }) |
471 | 1.07k | } http::uri::authority::create_authority::<bytes::bytes::Bytes, <http::uri::authority::Authority>::from_shared::{closure#0}>Line | Count | Source | 451 | 1.07k | fn create_authority<B, F>(b: B, f: F) -> Result<Authority, InvalidUri> | 452 | 1.07k | where | 453 | 1.07k | B: AsRef<[u8]>, | 454 | 1.07k | F: FnOnce(B) -> Bytes, | 455 | | { | 456 | 1.07k | let s = b.as_ref(); | 457 | 1.07k | let authority_end = Authority::parse_non_empty(s)?; | 458 | | | 459 | 1.04k | if authority_end != s.len() { | 460 | 3 | return Err(ErrorKind::InvalidUriChar.into()); | 461 | 1.03k | } | 462 | | | 463 | 1.03k | let bytes = f(b); | 464 | | | 465 | 1.03k | Ok(Authority { | 466 | 1.03k | // Safety: the postcondition on parse_non_empty() and the check against | 467 | 1.03k | // s.len() ensure that b is valid UTF-8. The precondition on f ensures | 468 | 1.03k | // that this is carried through to bytes. | 469 | 1.03k | data: unsafe { ByteStr::from_utf8_unchecked(bytes) }, | 470 | 1.03k | }) | 471 | 1.07k | } |
Unexecuted instantiation: http::uri::authority::create_authority::<&[u8], <bytes::bytes::Bytes>::copy_from_slice> |
472 | | |
473 | | /// Shared validation logic for authority bytes. |
474 | | /// Returns the end position of valid authority bytes, or an error. |
475 | 1.51k | const fn validate_authority_bytes(s: &[u8]) -> Result<usize, AuthorityError> { |
476 | 1.51k | if s.is_empty() { |
477 | 6 | return Err(AuthorityError::Empty); |
478 | 1.50k | } |
479 | | |
480 | 1.50k | let mut colon_cnt: u32 = 0; |
481 | 1.50k | let mut start_bracket = false; |
482 | 1.50k | let mut end_bracket = false; |
483 | 1.50k | let mut has_percent = false; |
484 | 1.50k | let mut end = s.len(); |
485 | 1.50k | let mut at_sign_pos: usize = s.len(); |
486 | | const MAX_COLONS: u32 = 8; // e.g., [FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:80 |
487 | | |
488 | 1.50k | let mut i = 0; |
489 | | // Among other things, this loop checks that every byte in s up to the |
490 | | // first '/', '?', or '#' is a valid URI character (or in some contexts, |
491 | | // a '%'). This means that each such byte is a valid single-byte UTF-8 |
492 | | // code point. |
493 | 409k | while i < s.len() { |
494 | 408k | let b = s[i]; |
495 | 408k | let ch = URI_CHARS[b as usize]; |
496 | | |
497 | 408k | if ch == b'/' || ch == b'?' || ch == b'#' { |
498 | 120 | end = i; |
499 | 120 | break; |
500 | 408k | } |
501 | | |
502 | 408k | if ch == 0 { |
503 | 637 | if b == b'%' { |
504 | 359 | // Per https://tools.ietf.org/html/rfc3986#section-3.2.1 and |
505 | 359 | // https://url.spec.whatwg.org/#authority-state |
506 | 359 | // the userinfo can have a percent-encoded username and password, |
507 | 359 | // so record that a `%` was found. If this turns out to be |
508 | 359 | // part of the userinfo, this flag will be cleared. |
509 | 359 | // Also per https://tools.ietf.org/html/rfc6874, percent-encoding can |
510 | 359 | // be used to indicate a zone identifier. |
511 | 359 | // If the flag hasn't been cleared at the end, that means this |
512 | 359 | // was part of the hostname (and not part of an IPv6 address), and |
513 | 359 | // will fail with an error. |
514 | 359 | has_percent = true; |
515 | 359 | } else { |
516 | 278 | return Err(AuthorityError::InvalidUriChar); |
517 | | } |
518 | 407k | } else if ch == b':' { |
519 | 387 | if colon_cnt >= MAX_COLONS { |
520 | 3 | return Err(AuthorityError::TooManyColons); |
521 | 384 | } |
522 | 384 | colon_cnt += 1; |
523 | 407k | } else if ch == b'[' { |
524 | 18 | if has_percent || start_bracket { |
525 | | // Something other than the userinfo has a `%`, so reject it. |
526 | 4 | return Err(AuthorityError::InvalidBracketUsage); |
527 | 14 | } |
528 | 14 | start_bracket = true; |
529 | 407k | } else if ch == b']' { |
530 | 7 | if !start_bracket || end_bracket { |
531 | 5 | return Err(AuthorityError::InvalidBracketUsage); |
532 | 2 | } |
533 | 2 | end_bracket = true; |
534 | | |
535 | | // Those were part of an IPv6 hostname, so forget them... |
536 | 2 | colon_cnt = 0; |
537 | 2 | has_percent = false; |
538 | 407k | } else if ch == b'@' { |
539 | 238 | at_sign_pos = i; |
540 | 238 | |
541 | 238 | // Those weren't a port colon, but part of the |
542 | 238 | // userinfo, so it needs to be forgotten. |
543 | 238 | colon_cnt = 0; |
544 | 238 | has_percent = false; |
545 | 407k | } |
546 | | |
547 | 408k | i += 1; |
548 | | } |
549 | | |
550 | 1.21k | if start_bracket != end_bracket { |
551 | 2 | return Err(AuthorityError::MismatchedBrackets); |
552 | 1.21k | } |
553 | | |
554 | 1.21k | if colon_cnt > 1 { |
555 | | // Things like 'localhost:8080:3030' are rejected. |
556 | 9 | return Err(AuthorityError::InvalidAuthority); |
557 | 1.20k | } |
558 | | |
559 | 1.20k | if end > 0 && at_sign_pos == end - 1 { |
560 | | // If there's nothing after an `@`, this is bonkers. |
561 | 8 | return Err(AuthorityError::EmptyAfterAt); |
562 | 1.19k | } |
563 | | |
564 | 1.19k | if has_percent { |
565 | | // Something after the userinfo has a `%`, so reject it. |
566 | 11 | return Err(AuthorityError::InvalidPercent); |
567 | 1.18k | } |
568 | | |
569 | 1.18k | Ok(end) |
570 | 1.51k | } |
571 | | |
572 | | #[cfg(test)] |
573 | | mod tests { |
574 | | use super::*; |
575 | | |
576 | | #[test] |
577 | | fn parse_empty_string_is_error() { |
578 | | let err = Authority::parse_non_empty(b"").unwrap_err(); |
579 | | assert_eq!(err.0, ErrorKind::Empty); |
580 | | } |
581 | | |
582 | | #[test] |
583 | | fn equal_to_self_of_same_authority() { |
584 | | let authority1: Authority = "example.com".parse().unwrap(); |
585 | | let authority2: Authority = "EXAMPLE.COM".parse().unwrap(); |
586 | | assert_eq!(authority1, authority2); |
587 | | assert_eq!(authority2, authority1); |
588 | | } |
589 | | |
590 | | #[test] |
591 | | fn not_equal_to_self_of_different_authority() { |
592 | | let authority1: Authority = "example.com".parse().unwrap(); |
593 | | let authority2: Authority = "test.com".parse().unwrap(); |
594 | | assert_ne!(authority1, authority2); |
595 | | assert_ne!(authority2, authority1); |
596 | | } |
597 | | |
598 | | #[test] |
599 | | fn equates_with_a_str() { |
600 | | let authority: Authority = "example.com".parse().unwrap(); |
601 | | assert_eq!(&authority, "EXAMPLE.com"); |
602 | | assert_eq!("EXAMPLE.com", &authority); |
603 | | assert_eq!(authority, "EXAMPLE.com"); |
604 | | assert_eq!("EXAMPLE.com", authority); |
605 | | } |
606 | | |
607 | | #[test] |
608 | | fn from_static_equates_with_a_str() { |
609 | | let authority = Authority::from_static("example.com"); |
610 | | assert_eq!(authority, "example.com"); |
611 | | } |
612 | | |
613 | | #[test] |
614 | | fn not_equal_with_a_str_of_a_different_authority() { |
615 | | let authority: Authority = "example.com".parse().unwrap(); |
616 | | assert_ne!(&authority, "test.com"); |
617 | | assert_ne!("test.com", &authority); |
618 | | assert_ne!(authority, "test.com"); |
619 | | assert_ne!("test.com", authority); |
620 | | } |
621 | | |
622 | | #[test] |
623 | | fn equates_with_a_string() { |
624 | | let authority: Authority = "example.com".parse().unwrap(); |
625 | | assert_eq!(authority, "EXAMPLE.com".to_string()); |
626 | | assert_eq!("EXAMPLE.com".to_string(), authority); |
627 | | } |
628 | | |
629 | | #[test] |
630 | | fn equates_with_a_string_of_a_different_authority() { |
631 | | let authority: Authority = "example.com".parse().unwrap(); |
632 | | assert_ne!(authority, "test.com".to_string()); |
633 | | assert_ne!("test.com".to_string(), authority); |
634 | | } |
635 | | |
636 | | #[test] |
637 | | fn compares_to_self() { |
638 | | let authority1: Authority = "abc.com".parse().unwrap(); |
639 | | let authority2: Authority = "def.com".parse().unwrap(); |
640 | | assert!(authority1 < authority2); |
641 | | assert!(authority2 > authority1); |
642 | | } |
643 | | |
644 | | #[test] |
645 | | fn compares_with_a_str() { |
646 | | let authority: Authority = "def.com".parse().unwrap(); |
647 | | // with ref |
648 | | assert!(&authority < "ghi.com"); |
649 | | assert!("ghi.com" > &authority); |
650 | | assert!(&authority > "abc.com"); |
651 | | assert!("abc.com" < &authority); |
652 | | |
653 | | // no ref |
654 | | assert!(authority < "ghi.com"); |
655 | | assert!("ghi.com" > authority); |
656 | | assert!(authority > "abc.com"); |
657 | | assert!("abc.com" < authority); |
658 | | } |
659 | | |
660 | | #[test] |
661 | | fn compares_with_a_string() { |
662 | | let authority: Authority = "def.com".parse().unwrap(); |
663 | | assert!(authority < "ghi.com".to_string()); |
664 | | assert!("ghi.com".to_string() > authority); |
665 | | assert!(authority > "abc.com".to_string()); |
666 | | assert!("abc.com".to_string() < authority); |
667 | | } |
668 | | |
669 | | #[test] |
670 | | fn allows_percent_in_userinfo() { |
671 | | let authority_str = "a%2f:b%2f@example.com"; |
672 | | let authority: Authority = authority_str.parse().unwrap(); |
673 | | assert_eq!(authority, authority_str); |
674 | | } |
675 | | |
676 | | #[test] |
677 | | fn rejects_percent_in_hostname() { |
678 | | let err = Authority::parse_non_empty(b"example%2f.com").unwrap_err(); |
679 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
680 | | |
681 | | let err = Authority::parse_non_empty(b"a%2f:b%2f@example%2f.com").unwrap_err(); |
682 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
683 | | } |
684 | | |
685 | | #[test] |
686 | | fn allows_percent_in_ipv6_address() { |
687 | | let authority_str = "[fe80::1:2:3:4%25eth0]"; |
688 | | let result: Authority = authority_str.parse().unwrap(); |
689 | | assert_eq!(result, authority_str); |
690 | | } |
691 | | |
692 | | #[test] |
693 | | fn reject_obviously_invalid_ipv6_address() { |
694 | | let err = Authority::parse_non_empty(b"[0:1:2:3:4:5:6:7:8:9:10:11:12:13:14]").unwrap_err(); |
695 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
696 | | } |
697 | | |
698 | | #[test] |
699 | | fn rejects_percent_outside_ipv6_address() { |
700 | | let err = Authority::parse_non_empty(b"1234%20[fe80::1:2:3:4]").unwrap_err(); |
701 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
702 | | |
703 | | let err = Authority::parse_non_empty(b"[fe80::1:2:3:4]%20").unwrap_err(); |
704 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
705 | | } |
706 | | |
707 | | #[test] |
708 | | fn rejects_invalid_utf8() { |
709 | | let err = Authority::try_from([0xc0u8].as_ref()).unwrap_err(); |
710 | | assert_eq!(err.0, ErrorKind::InvalidUriChar); |
711 | | |
712 | | let err = Authority::from_shared(Bytes::from_static([0xc0u8].as_ref())).unwrap_err(); |
713 | | assert_eq!(err.0, ErrorKind::InvalidUriChar); |
714 | | } |
715 | | |
716 | | #[test] |
717 | | fn rejects_invalid_use_of_brackets() { |
718 | | let err = Authority::parse_non_empty(b"[]@[").unwrap_err(); |
719 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
720 | | |
721 | | // reject tie-fighter |
722 | | let err = Authority::parse_non_empty(b"]o[").unwrap_err(); |
723 | | assert_eq!(err.0, ErrorKind::InvalidAuthority); |
724 | | } |
725 | | } |