1 // Copyright 2013-2016 The rust-url developers.
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.
10 use std
::fmt
::{self, Formatter}
;
11 use std
::net
::{Ipv4Addr, Ipv6Addr}
;
13 use percent_encoding
::{percent_decode, utf8_percent_encode, CONTROLS}
;
14 #[cfg(feature = "serde")]
15 use serde
::{Deserialize, Serialize}
;
17 use crate::parser
::{ParseError, ParseResult}
;
19 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
20 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
21 pub(crate) enum HostInternal
{
28 impl From
<Host
<String
>> for HostInternal
{
29 fn from(host
: Host
<String
>) -> HostInternal
{
31 Host
::Domain(ref s
) if s
.is_empty() => HostInternal
::None
,
32 Host
::Domain(_
) => HostInternal
::Domain
,
33 Host
::Ipv4(address
) => HostInternal
::Ipv4(address
),
34 Host
::Ipv6(address
) => HostInternal
::Ipv6(address
),
39 /// The host name of an URL.
40 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
41 #[derive(Clone, Debug, Eq, Ord, PartialOrd, Hash)]
42 pub enum Host
<S
= String
> {
43 /// A DNS domain name, as '.' dot-separated labels.
44 /// Non-ASCII labels are encoded in punycode per IDNA if this is the host of
45 /// a special URL, or percent encoded for non-special URLs. Hosts for
46 /// non-special URLs are also called opaque hosts.
50 /// `Url::host_str` returns the serialization of this address,
51 /// as four decimal integers separated by `.` dots.
55 /// `Url::host_str` returns the serialization of that address between `[` and `]` brackets,
56 /// in the format per [RFC 5952 *A Recommendation
57 /// for IPv6 Address Text Representation*](https://tools.ietf.org/html/rfc5952):
58 /// lowercase hexadecimal with maximal `::` compression.
62 impl<'a
> Host
<&'a
str> {
63 /// Return a copy of `self` that owns an allocated `String` but does not borrow an `&Url`.
64 pub fn to_owned(&self) -> Host
<String
> {
66 Host
::Domain(domain
) => Host
::Domain(domain
.to_owned()),
67 Host
::Ipv4(address
) => Host
::Ipv4(address
),
68 Host
::Ipv6(address
) => Host
::Ipv6(address
),
74 /// Parse a host: either an IPv6 address in [] square brackets, or a domain.
76 /// <https://url.spec.whatwg.org/#host-parsing>
77 pub fn parse(input
: &str) -> Result
<Self, ParseError
> {
78 if input
.starts_with('
['
) {
79 if !input
.ends_with('
]'
) {
80 return Err(ParseError
::InvalidIpv6Address
);
82 return parse_ipv6addr(&input
[1..input
.len() - 1]).map(Host
::Ipv6
);
84 let domain
= percent_decode(input
.as_bytes()).decode_utf8_lossy();
85 let domain
= idna
::domain_to_ascii(&domain
)?
;
86 if domain
.is_empty() {
87 return Err(ParseError
::EmptyHost
);
90 let is_invalid_domain_char
= |c
| {
112 if domain
.find(is_invalid_domain_char
).is_some() {
113 Err(ParseError
::InvalidDomainCharacter
)
114 } else if let Some(address
) = parse_ipv4addr(&domain
)?
{
115 Ok(Host
::Ipv4(address
))
117 Ok(Host
::Domain(domain
))
121 // <https://url.spec.whatwg.org/#concept-opaque-host-parser>
122 pub fn parse_opaque(input
: &str) -> Result
<Self, ParseError
> {
123 if input
.starts_with('
['
) {
124 if !input
.ends_with('
]'
) {
125 return Err(ParseError
::InvalidIpv6Address
);
127 return parse_ipv6addr(&input
[1..input
.len() - 1]).map(Host
::Ipv6
);
130 let is_invalid_host_char
= |c
| {
151 if input
.find(is_invalid_host_char
).is_some() {
152 Err(ParseError
::InvalidDomainCharacter
)
155 utf8_percent_encode(input
, CONTROLS
).to_string(),
161 impl<S
: AsRef
<str>> fmt
::Display
for Host
<S
> {
162 fn fmt(&self, f
: &mut Formatter
<'_
>) -> fmt
::Result
{
164 Host
::Domain(ref domain
) => domain
.as_ref().fmt(f
),
165 Host
::Ipv4(ref addr
) => addr
.fmt(f
),
166 Host
::Ipv6(ref addr
) => {
168 write_ipv6(addr
, f
)?
;
175 impl<S
, T
> PartialEq
<Host
<T
>> for Host
<S
>
179 fn eq(&self, other
: &Host
<T
>) -> bool
{
180 match (self, other
) {
181 (Host
::Domain(a
), Host
::Domain(b
)) => a
== b
,
182 (Host
::Ipv4(a
), Host
::Ipv4(b
)) => a
== b
,
183 (Host
::Ipv6(a
), Host
::Ipv6(b
)) => a
== b
,
189 fn write_ipv6(addr
: &Ipv6Addr
, f
: &mut Formatter
<'_
>) -> fmt
::Result
{
190 let segments
= addr
.segments();
191 let (compress_start
, compress_end
) = longest_zero_sequence(&segments
);
194 if i
== compress_start
{
199 if compress_end
< 8 {
205 write
!(f
, "{:x}", segments
[i
as usize])?
;
214 // https://url.spec.whatwg.org/#concept-ipv6-serializer step 2 and 3
215 fn longest_zero_sequence(pieces
: &[u16; 8]) -> (isize, isize) {
216 let mut longest
= -1;
217 let mut longest_length
= -1;
219 macro_rules
! finish_sequence(
222 let length
= $end
- start
;
223 if length
> longest_length
{
225 longest_length
= length
;
231 if pieces
[i
as usize] == 0 {
241 // https://url.spec.whatwg.org/#concept-ipv6-serializer
242 // step 3: ignore lone zeroes
243 if longest_length
< 2 {
246 (longest
, longest
+ longest_length
)
250 /// <https://url.spec.whatwg.org/#ipv4-number-parser>
251 fn parse_ipv4number(mut input
: &str) -> Result
<Option
<u32>, ()> {
253 if input
.starts_with("0x") || input
.starts_with("0X") {
256 } else if input
.len() >= 2 && input
.starts_with('
0'
) {
261 // At the moment we can't know the reason why from_str_radix fails
262 // https://github.com/rust-lang/rust/issues/22639
263 // So instead we check if the input looks like a real number and only return
264 // an error when it's an overflow.
265 let valid_number
= match r
{
266 8 => input
.chars().all(|c
| ('
0'
..='
7'
).contains(&c
)),
267 10 => input
.chars().all(|c
| ('
0'
..='
9'
).contains(&c
)),
268 16 => input
.chars().all(|c
| {
269 ('
0'
..='
9'
).contains(&c
) || ('a'
..='f'
).contains(&c
) || ('A'
..='F'
).contains(&c
)
278 if input
.is_empty() {
281 if input
.starts_with('
+'
) {
284 match u32::from_str_radix(input
, r
) {
285 Ok(number
) => Ok(Some(number
)),
290 /// <https://url.spec.whatwg.org/#concept-ipv4-parser>
291 fn parse_ipv4addr(input
: &str) -> ParseResult
<Option
<Ipv4Addr
>> {
292 if input
.is_empty() {
295 let mut parts
: Vec
<&str> = input
.split('
.'
).collect();
296 if parts
.last() == Some(&"") {
302 let mut numbers
: Vec
<u32> = Vec
::new();
303 let mut overflow
= false;
308 match parse_ipv4number(part
) {
309 Ok(Some(n
)) => numbers
.push(n
),
310 Ok(None
) => return Ok(None
),
311 Err(()) => overflow
= true,
315 return Err(ParseError
::InvalidIpv4Address
);
317 let mut ipv4
= numbers
.pop().expect("a non-empty list of numbers");
318 // Equivalent to: ipv4 >= 256 ** (4 − numbers.len())
319 if ipv4
> u32::max_value() >> (8 * numbers
.len() as u32) {
320 return Err(ParseError
::InvalidIpv4Address
);
322 if numbers
.iter().any(|x
| *x
> 255) {
323 return Err(ParseError
::InvalidIpv4Address
);
325 for (counter
, n
) in numbers
.iter().enumerate() {
326 ipv4
+= n
<< (8 * (3 - counter
as u32))
328 Ok(Some(Ipv4Addr
::from(ipv4
)))
331 /// <https://url.spec.whatwg.org/#concept-ipv6-parser>
332 fn parse_ipv6addr(input
: &str) -> ParseResult
<Ipv6Addr
> {
333 let input
= input
.as_bytes();
334 let len
= input
.len();
335 let mut is_ip_v4
= false;
336 let mut pieces
= [0, 0, 0, 0, 0, 0, 0, 0];
337 let mut piece_pointer
= 0;
338 let mut compress_pointer
= None
;
342 return Err(ParseError
::InvalidIpv6Address
);
345 if input
[0] == b'
:'
{
346 if input
[1] != b'
:'
{
347 return Err(ParseError
::InvalidIpv6Address
);
351 compress_pointer
= Some(1);
355 if piece_pointer
== 8 {
356 return Err(ParseError
::InvalidIpv6Address
);
358 if input
[i
] == b'
:'
{
359 if compress_pointer
.is_some() {
360 return Err(ParseError
::InvalidIpv6Address
);
364 compress_pointer
= Some(piece_pointer
);
368 let end
= cmp
::min(len
, start
+ 4);
369 let mut value
= 0u16;
371 match (input
[i
] as char).to_digit(16) {
373 value
= value
* 0x10 + digit
as u16;
383 return Err(ParseError
::InvalidIpv6Address
);
386 if piece_pointer
> 6 {
387 return Err(ParseError
::InvalidIpv6Address
);
394 return Err(ParseError
::InvalidIpv6Address
);
397 _
=> return Err(ParseError
::InvalidIpv6Address
),
403 pieces
[piece_pointer
] = value
;
408 if piece_pointer
> 6 {
409 return Err(ParseError
::InvalidIpv6Address
);
411 let mut numbers_seen
= 0;
413 if numbers_seen
> 0 {
414 if numbers_seen
< 4 && (i
< len
&& input
[i
] == b'
.'
) {
417 return Err(ParseError
::InvalidIpv6Address
);
421 let mut ipv4_piece
= None
;
423 let digit
= match input
[i
] {
424 c @ b'
0'
..=b'
9'
=> c
- b'
0'
,
428 None
=> ipv4_piece
= Some(digit
as u16),
429 Some(0) => return Err(ParseError
::InvalidIpv6Address
), // No leading zero
431 *v
= *v
* 10 + digit
as u16;
433 return Err(ParseError
::InvalidIpv6Address
);
440 pieces
[piece_pointer
] = if let Some(v
) = ipv4_piece
{
441 pieces
[piece_pointer
] * 0x100 + v
443 return Err(ParseError
::InvalidIpv6Address
);
447 if numbers_seen
== 2 || numbers_seen
== 4 {
452 if numbers_seen
!= 4 {
453 return Err(ParseError
::InvalidIpv6Address
);
458 return Err(ParseError
::InvalidIpv6Address
);
461 match compress_pointer
{
462 Some(compress_pointer
) => {
463 let mut swaps
= piece_pointer
- compress_pointer
;
466 pieces
.swap(piece_pointer
, compress_pointer
+ swaps
- 1);
472 if piece_pointer
!= 8 {
473 return Err(ParseError
::InvalidIpv6Address
);
478 pieces
[0], pieces
[1], pieces
[2], pieces
[3], pieces
[4], pieces
[5], pieces
[6], pieces
[7],