/rust/registry/src/index.crates.io-6f17d22bba15001f/url-2.5.4/src/host.rs
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2013-2016 The rust-url developers. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
4 | | // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
5 | | // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
6 | | // option. This file may not be copied, modified, or distributed |
7 | | // except according to those terms. |
8 | | |
9 | | use crate::net::{Ipv4Addr, Ipv6Addr}; |
10 | | use alloc::borrow::Cow; |
11 | | use alloc::borrow::ToOwned; |
12 | | use alloc::string::String; |
13 | | use alloc::string::ToString; |
14 | | use alloc::vec::Vec; |
15 | | use core::cmp; |
16 | | use core::fmt::{self, Formatter}; |
17 | | |
18 | | use percent_encoding::{percent_decode, utf8_percent_encode, CONTROLS}; |
19 | | #[cfg(feature = "serde")] |
20 | | use serde::{Deserialize, Serialize}; |
21 | | |
22 | | use crate::parser::{ParseError, ParseResult}; |
23 | | |
24 | | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] |
25 | | #[derive(Copy, Clone, Debug, Eq, PartialEq)] |
26 | | pub(crate) enum HostInternal { |
27 | | None, |
28 | | Domain, |
29 | | Ipv4(Ipv4Addr), |
30 | | Ipv6(Ipv6Addr), |
31 | | } |
32 | | |
33 | | impl From<Host<String>> for HostInternal { |
34 | 0 | fn from(host: Host<String>) -> HostInternal { |
35 | 0 | match host { |
36 | 0 | Host::Domain(ref s) if s.is_empty() => HostInternal::None, |
37 | 0 | Host::Domain(_) => HostInternal::Domain, |
38 | 0 | Host::Ipv4(address) => HostInternal::Ipv4(address), |
39 | 0 | Host::Ipv6(address) => HostInternal::Ipv6(address), |
40 | | } |
41 | 0 | } |
42 | | } |
43 | | |
44 | | /// The host name of an URL. |
45 | | #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] |
46 | | #[derive(Clone, Debug, Eq, Ord, PartialOrd, Hash)] |
47 | | pub enum Host<S = String> { |
48 | | /// A DNS domain name, as '.' dot-separated labels. |
49 | | /// Non-ASCII labels are encoded in punycode per IDNA if this is the host of |
50 | | /// a special URL, or percent encoded for non-special URLs. Hosts for |
51 | | /// non-special URLs are also called opaque hosts. |
52 | | Domain(S), |
53 | | |
54 | | /// An IPv4 address. |
55 | | /// `Url::host_str` returns the serialization of this address, |
56 | | /// as four decimal integers separated by `.` dots. |
57 | | Ipv4(Ipv4Addr), |
58 | | |
59 | | /// An IPv6 address. |
60 | | /// `Url::host_str` returns the serialization of that address between `[` and `]` brackets, |
61 | | /// in the format per [RFC 5952 *A Recommendation |
62 | | /// for IPv6 Address Text Representation*](https://tools.ietf.org/html/rfc5952): |
63 | | /// lowercase hexadecimal with maximal `::` compression. |
64 | | Ipv6(Ipv6Addr), |
65 | | } |
66 | | |
67 | | impl<'a> Host<&'a str> { |
68 | | /// Return a copy of `self` that owns an allocated `String` but does not borrow an `&Url`. |
69 | 0 | pub fn to_owned(&self) -> Host<String> { |
70 | 0 | match *self { |
71 | 0 | Host::Domain(domain) => Host::Domain(domain.to_owned()), |
72 | 0 | Host::Ipv4(address) => Host::Ipv4(address), |
73 | 0 | Host::Ipv6(address) => Host::Ipv6(address), |
74 | | } |
75 | 0 | } |
76 | | } |
77 | | |
78 | | impl Host<String> { |
79 | | /// Parse a host: either an IPv6 address in [] square brackets, or a domain. |
80 | | /// |
81 | | /// <https://url.spec.whatwg.org/#host-parsing> |
82 | 0 | pub fn parse(input: &str) -> Result<Self, ParseError> { |
83 | 0 | if input.starts_with('[') { |
84 | 0 | if !input.ends_with(']') { |
85 | 0 | return Err(ParseError::InvalidIpv6Address); |
86 | 0 | } |
87 | 0 | return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6); |
88 | 0 | } |
89 | 0 | let domain: Cow<'_, [u8]> = percent_decode(input.as_bytes()).into(); |
90 | | |
91 | 0 | let domain = Self::domain_to_ascii(&domain)?; |
92 | | |
93 | 0 | if domain.is_empty() { |
94 | 0 | return Err(ParseError::EmptyHost); |
95 | 0 | } |
96 | 0 |
|
97 | 0 | if ends_in_a_number(&domain) { |
98 | 0 | let address = parse_ipv4addr(&domain)?; |
99 | 0 | Ok(Host::Ipv4(address)) |
100 | | } else { |
101 | 0 | Ok(Host::Domain(domain.to_string())) |
102 | | } |
103 | 0 | } |
104 | | |
105 | | // <https://url.spec.whatwg.org/#concept-opaque-host-parser> |
106 | 0 | pub fn parse_opaque(input: &str) -> Result<Self, ParseError> { |
107 | 0 | if input.starts_with('[') { |
108 | 0 | if !input.ends_with(']') { |
109 | 0 | return Err(ParseError::InvalidIpv6Address); |
110 | 0 | } |
111 | 0 | return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6); |
112 | 0 | } |
113 | 0 |
|
114 | 0 | let is_invalid_host_char = |c| { |
115 | 0 | matches!( |
116 | 0 | c, |
117 | | '\0' | '\t' |
118 | | | '\n' |
119 | | | '\r' |
120 | | | ' ' |
121 | | | '#' |
122 | | | '/' |
123 | | | ':' |
124 | | | '<' |
125 | | | '>' |
126 | | | '?' |
127 | | | '@' |
128 | | | '[' |
129 | | | '\\' |
130 | | | ']' |
131 | | | '^' |
132 | | | '|' |
133 | | ) |
134 | 0 | }; |
135 | | |
136 | 0 | if input.find(is_invalid_host_char).is_some() { |
137 | 0 | Err(ParseError::InvalidDomainCharacter) |
138 | | } else { |
139 | 0 | Ok(Host::Domain( |
140 | 0 | utf8_percent_encode(input, CONTROLS).to_string(), |
141 | 0 | )) |
142 | | } |
143 | 0 | } |
144 | | |
145 | | /// convert domain with idna |
146 | 0 | fn domain_to_ascii(domain: &[u8]) -> Result<Cow<'_, str>, ParseError> { |
147 | 0 | idna::domain_to_ascii_cow(domain, idna::AsciiDenyList::URL).map_err(Into::into) |
148 | 0 | } |
149 | | } |
150 | | |
151 | | impl<S: AsRef<str>> fmt::Display for Host<S> { |
152 | 0 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
153 | 0 | match *self { |
154 | 0 | Host::Domain(ref domain) => domain.as_ref().fmt(f), |
155 | 0 | Host::Ipv4(ref addr) => addr.fmt(f), |
156 | 0 | Host::Ipv6(ref addr) => { |
157 | 0 | f.write_str("[")?; |
158 | 0 | write_ipv6(addr, f)?; |
159 | 0 | f.write_str("]") |
160 | | } |
161 | | } |
162 | 0 | } |
163 | | } |
164 | | |
165 | | impl<S, T> PartialEq<Host<T>> for Host<S> |
166 | | where |
167 | | S: PartialEq<T>, |
168 | | { |
169 | 0 | fn eq(&self, other: &Host<T>) -> bool { |
170 | 0 | match (self, other) { |
171 | 0 | (Host::Domain(a), Host::Domain(b)) => a == b, |
172 | 0 | (Host::Ipv4(a), Host::Ipv4(b)) => a == b, |
173 | 0 | (Host::Ipv6(a), Host::Ipv6(b)) => a == b, |
174 | 0 | (_, _) => false, |
175 | | } |
176 | 0 | } Unexecuted instantiation: <url::host::Host<&str> as core::cmp::PartialEq>::eq Unexecuted instantiation: <url::host::Host as core::cmp::PartialEq>::eq |
177 | | } |
178 | | |
179 | 0 | fn write_ipv6(addr: &Ipv6Addr, f: &mut Formatter<'_>) -> fmt::Result { |
180 | 0 | let segments = addr.segments(); |
181 | 0 | let (compress_start, compress_end) = longest_zero_sequence(&segments); |
182 | 0 | let mut i = 0; |
183 | 0 | while i < 8 { |
184 | 0 | if i == compress_start { |
185 | 0 | f.write_str(":")?; |
186 | 0 | if i == 0 { |
187 | 0 | f.write_str(":")?; |
188 | 0 | } |
189 | 0 | if compress_end < 8 { |
190 | 0 | i = compress_end; |
191 | 0 | } else { |
192 | 0 | break; |
193 | | } |
194 | 0 | } |
195 | 0 | write!(f, "{:x}", segments[i as usize])?; |
196 | 0 | if i < 7 { |
197 | 0 | f.write_str(":")?; |
198 | 0 | } |
199 | 0 | i += 1; |
200 | | } |
201 | 0 | Ok(()) |
202 | 0 | } |
203 | | |
204 | | // https://url.spec.whatwg.org/#concept-ipv6-serializer step 2 and 3 |
205 | 0 | fn longest_zero_sequence(pieces: &[u16; 8]) -> (isize, isize) { |
206 | 0 | let mut longest = -1; |
207 | 0 | let mut longest_length = -1; |
208 | 0 | let mut start = -1; |
209 | | macro_rules! finish_sequence( |
210 | | ($end: expr) => { |
211 | | if start >= 0 { |
212 | | let length = $end - start; |
213 | | if length > longest_length { |
214 | | longest = start; |
215 | | longest_length = length; |
216 | | } |
217 | | } |
218 | | }; |
219 | | ); |
220 | 0 | for i in 0..8 { |
221 | 0 | if pieces[i as usize] == 0 { |
222 | 0 | if start < 0 { |
223 | 0 | start = i; |
224 | 0 | } |
225 | | } else { |
226 | 0 | finish_sequence!(i); |
227 | 0 | start = -1; |
228 | | } |
229 | | } |
230 | 0 | finish_sequence!(8); |
231 | | // https://url.spec.whatwg.org/#concept-ipv6-serializer |
232 | | // step 3: ignore lone zeroes |
233 | 0 | if longest_length < 2 { |
234 | 0 | (-1, -2) |
235 | | } else { |
236 | 0 | (longest, longest + longest_length) |
237 | | } |
238 | 0 | } |
239 | | |
240 | | /// <https://url.spec.whatwg.org/#ends-in-a-number-checker> |
241 | 0 | fn ends_in_a_number(input: &str) -> bool { |
242 | 0 | let mut parts = input.rsplit('.'); |
243 | 0 | let last = parts.next().unwrap(); |
244 | 0 | let last = if last.is_empty() { |
245 | 0 | if let Some(last) = parts.next() { |
246 | 0 | last |
247 | | } else { |
248 | 0 | return false; |
249 | | } |
250 | | } else { |
251 | 0 | last |
252 | | }; |
253 | 0 | if !last.is_empty() && last.as_bytes().iter().all(|c| c.is_ascii_digit()) { |
254 | 0 | return true; |
255 | 0 | } |
256 | 0 |
|
257 | 0 | parse_ipv4number(last).is_ok() |
258 | 0 | } |
259 | | |
260 | | /// <https://url.spec.whatwg.org/#ipv4-number-parser> |
261 | | /// Ok(None) means the input is a valid number, but it overflows a `u32`. |
262 | 0 | fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> { |
263 | 0 | if input.is_empty() { |
264 | 0 | return Err(()); |
265 | 0 | } |
266 | 0 |
|
267 | 0 | let mut r = 10; |
268 | 0 | if input.starts_with("0x") || input.starts_with("0X") { |
269 | 0 | input = &input[2..]; |
270 | 0 | r = 16; |
271 | 0 | } else if input.len() >= 2 && input.starts_with('0') { |
272 | 0 | input = &input[1..]; |
273 | 0 | r = 8; |
274 | 0 | } |
275 | | |
276 | 0 | if input.is_empty() { |
277 | 0 | return Ok(Some(0)); |
278 | 0 | } |
279 | | |
280 | 0 | let valid_number = match r { |
281 | 0 | 8 => input.as_bytes().iter().all(|c| (b'0'..=b'7').contains(c)), |
282 | 0 | 10 => input.as_bytes().iter().all(|c| c.is_ascii_digit()), |
283 | 0 | 16 => input.as_bytes().iter().all(|c| c.is_ascii_hexdigit()), |
284 | 0 | _ => false, |
285 | | }; |
286 | 0 | if !valid_number { |
287 | 0 | return Err(()); |
288 | 0 | } |
289 | 0 |
|
290 | 0 | match u32::from_str_radix(input, r) { |
291 | 0 | Ok(num) => Ok(Some(num)), |
292 | 0 | Err(_) => Ok(None), // The only possible error kind here is an integer overflow. |
293 | | // The validity of the chars in the input is checked above. |
294 | | } |
295 | 0 | } |
296 | | |
297 | | /// <https://url.spec.whatwg.org/#concept-ipv4-parser> |
298 | 0 | fn parse_ipv4addr(input: &str) -> ParseResult<Ipv4Addr> { |
299 | 0 | let mut parts: Vec<&str> = input.split('.').collect(); |
300 | 0 | if parts.last() == Some(&"") { |
301 | 0 | parts.pop(); |
302 | 0 | } |
303 | 0 | if parts.len() > 4 { |
304 | 0 | return Err(ParseError::InvalidIpv4Address); |
305 | 0 | } |
306 | 0 | let mut numbers: Vec<u32> = Vec::new(); |
307 | 0 | for part in parts { |
308 | 0 | match parse_ipv4number(part) { |
309 | 0 | Ok(Some(n)) => numbers.push(n), |
310 | 0 | Ok(None) => return Err(ParseError::InvalidIpv4Address), // u32 overflow |
311 | 0 | Err(()) => return Err(ParseError::InvalidIpv4Address), |
312 | | }; |
313 | | } |
314 | 0 | let mut ipv4 = numbers.pop().expect("a non-empty list of numbers"); |
315 | 0 | // Equivalent to: ipv4 >= 256 ** (4 − numbers.len()) |
316 | 0 | if ipv4 > u32::MAX >> (8 * numbers.len() as u32) { |
317 | 0 | return Err(ParseError::InvalidIpv4Address); |
318 | 0 | } |
319 | 0 | if numbers.iter().any(|x| *x > 255) { |
320 | 0 | return Err(ParseError::InvalidIpv4Address); |
321 | 0 | } |
322 | 0 | for (counter, n) in numbers.iter().enumerate() { |
323 | 0 | ipv4 += n << (8 * (3 - counter as u32)) |
324 | | } |
325 | 0 | Ok(Ipv4Addr::from(ipv4)) |
326 | 0 | } |
327 | | |
328 | | /// <https://url.spec.whatwg.org/#concept-ipv6-parser> |
329 | 0 | fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> { |
330 | 0 | let input = input.as_bytes(); |
331 | 0 | let len = input.len(); |
332 | 0 | let mut is_ip_v4 = false; |
333 | 0 | let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0]; |
334 | 0 | let mut piece_pointer = 0; |
335 | 0 | let mut compress_pointer = None; |
336 | 0 | let mut i = 0; |
337 | 0 |
|
338 | 0 | if len < 2 { |
339 | 0 | return Err(ParseError::InvalidIpv6Address); |
340 | 0 | } |
341 | 0 |
|
342 | 0 | if input[0] == b':' { |
343 | 0 | if input[1] != b':' { |
344 | 0 | return Err(ParseError::InvalidIpv6Address); |
345 | 0 | } |
346 | 0 | i = 2; |
347 | 0 | piece_pointer = 1; |
348 | 0 | compress_pointer = Some(1); |
349 | 0 | } |
350 | | |
351 | 0 | while i < len { |
352 | 0 | if piece_pointer == 8 { |
353 | 0 | return Err(ParseError::InvalidIpv6Address); |
354 | 0 | } |
355 | 0 | if input[i] == b':' { |
356 | 0 | if compress_pointer.is_some() { |
357 | 0 | return Err(ParseError::InvalidIpv6Address); |
358 | 0 | } |
359 | 0 | i += 1; |
360 | 0 | piece_pointer += 1; |
361 | 0 | compress_pointer = Some(piece_pointer); |
362 | 0 | continue; |
363 | 0 | } |
364 | 0 | let start = i; |
365 | 0 | let end = cmp::min(len, start + 4); |
366 | 0 | let mut value = 0u16; |
367 | 0 | while i < end { |
368 | 0 | match (input[i] as char).to_digit(16) { |
369 | 0 | Some(digit) => { |
370 | 0 | value = value * 0x10 + digit as u16; |
371 | 0 | i += 1; |
372 | 0 | } |
373 | 0 | None => break, |
374 | | } |
375 | | } |
376 | 0 | if i < len { |
377 | 0 | match input[i] { |
378 | | b'.' => { |
379 | 0 | if i == start { |
380 | 0 | return Err(ParseError::InvalidIpv6Address); |
381 | 0 | } |
382 | 0 | i = start; |
383 | 0 | if piece_pointer > 6 { |
384 | 0 | return Err(ParseError::InvalidIpv6Address); |
385 | 0 | } |
386 | 0 | is_ip_v4 = true; |
387 | | } |
388 | | b':' => { |
389 | 0 | i += 1; |
390 | 0 | if i == len { |
391 | 0 | return Err(ParseError::InvalidIpv6Address); |
392 | 0 | } |
393 | | } |
394 | 0 | _ => return Err(ParseError::InvalidIpv6Address), |
395 | | } |
396 | 0 | } |
397 | 0 | if is_ip_v4 { |
398 | 0 | break; |
399 | 0 | } |
400 | 0 | pieces[piece_pointer] = value; |
401 | 0 | piece_pointer += 1; |
402 | | } |
403 | | |
404 | 0 | if is_ip_v4 { |
405 | 0 | if piece_pointer > 6 { |
406 | 0 | return Err(ParseError::InvalidIpv6Address); |
407 | 0 | } |
408 | 0 | let mut numbers_seen = 0; |
409 | 0 | while i < len { |
410 | 0 | if numbers_seen > 0 { |
411 | 0 | if numbers_seen < 4 && (i < len && input[i] == b'.') { |
412 | 0 | i += 1 |
413 | | } else { |
414 | 0 | return Err(ParseError::InvalidIpv6Address); |
415 | | } |
416 | 0 | } |
417 | | |
418 | 0 | let mut ipv4_piece = None; |
419 | 0 | while i < len { |
420 | 0 | let digit = match input[i] { |
421 | 0 | c @ b'0'..=b'9' => c - b'0', |
422 | 0 | _ => break, |
423 | | }; |
424 | 0 | match ipv4_piece { |
425 | 0 | None => ipv4_piece = Some(digit as u16), |
426 | 0 | Some(0) => return Err(ParseError::InvalidIpv6Address), // No leading zero |
427 | 0 | Some(ref mut v) => { |
428 | 0 | *v = *v * 10 + digit as u16; |
429 | 0 | if *v > 255 { |
430 | 0 | return Err(ParseError::InvalidIpv6Address); |
431 | 0 | } |
432 | | } |
433 | | } |
434 | 0 | i += 1; |
435 | | } |
436 | | |
437 | 0 | pieces[piece_pointer] = if let Some(v) = ipv4_piece { |
438 | 0 | pieces[piece_pointer] * 0x100 + v |
439 | | } else { |
440 | 0 | return Err(ParseError::InvalidIpv6Address); |
441 | | }; |
442 | 0 | numbers_seen += 1; |
443 | 0 |
|
444 | 0 | if numbers_seen == 2 || numbers_seen == 4 { |
445 | 0 | piece_pointer += 1; |
446 | 0 | } |
447 | | } |
448 | | |
449 | 0 | if numbers_seen != 4 { |
450 | 0 | return Err(ParseError::InvalidIpv6Address); |
451 | 0 | } |
452 | 0 | } |
453 | | |
454 | 0 | if i < len { |
455 | 0 | return Err(ParseError::InvalidIpv6Address); |
456 | 0 | } |
457 | 0 |
|
458 | 0 | match compress_pointer { |
459 | 0 | Some(compress_pointer) => { |
460 | 0 | let mut swaps = piece_pointer - compress_pointer; |
461 | 0 | piece_pointer = 7; |
462 | 0 | while swaps > 0 { |
463 | 0 | pieces.swap(piece_pointer, compress_pointer + swaps - 1); |
464 | 0 | swaps -= 1; |
465 | 0 | piece_pointer -= 1; |
466 | 0 | } |
467 | | } |
468 | | _ => { |
469 | 0 | if piece_pointer != 8 { |
470 | 0 | return Err(ParseError::InvalidIpv6Address); |
471 | 0 | } |
472 | | } |
473 | | } |
474 | 0 | Ok(Ipv6Addr::new( |
475 | 0 | pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6], pieces[7], |
476 | 0 | )) |
477 | 0 | } |