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 parser
::{ParseError, ParseResult}
;
11 use percent_encoding
::{percent_decode, utf8_percent_encode, CONTROLS}
;
12 #[cfg(feature = "serde")]
13 use serde
::{Deserialize, Serialize}
;
15 use std
::fmt
::{self, Formatter}
;
16 use std
::net
::{Ipv4Addr, Ipv6Addr}
;
18 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
19 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
20 pub(crate) enum HostInternal
{
27 impl From
<Host
<String
>> for HostInternal
{
28 fn from(host
: Host
<String
>) -> HostInternal
{
30 Host
::Domain(ref s
) if s
.is_empty() => HostInternal
::None
,
31 Host
::Domain(_
) => HostInternal
::Domain
,
32 Host
::Ipv4(address
) => HostInternal
::Ipv4(address
),
33 Host
::Ipv6(address
) => HostInternal
::Ipv6(address
),
38 /// The host name of an URL.
39 #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
40 #[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
41 pub enum Host
<S
= String
> {
42 /// A DNS domain name, as '.' dot-separated labels.
43 /// Non-ASCII labels are encoded in punycode per IDNA if this is the host of
44 /// a special URL, or percent encoded for non-special URLs. Hosts for
45 /// non-special URLs are also called opaque hosts.
49 /// `Url::host_str` returns the serialization of this address,
50 /// as four decimal integers separated by `.` dots.
54 /// `Url::host_str` returns the serialization of that address between `[` and `]` brackets,
55 /// in the format per [RFC 5952 *A Recommendation
56 /// for IPv6 Address Text Representation*](https://tools.ietf.org/html/rfc5952):
57 /// lowercase hexadecimal with maximal `::` compression.
61 impl<'a
> Host
<&'a
str> {
62 /// Return a copy of `self` that owns an allocated `String` but does not borrow an `&Url`.
63 pub fn to_owned(&self) -> Host
<String
> {
65 Host
::Domain(domain
) => Host
::Domain(domain
.to_owned()),
66 Host
::Ipv4(address
) => Host
::Ipv4(address
),
67 Host
::Ipv6(address
) => Host
::Ipv6(address
),
73 /// Parse a host: either an IPv6 address in [] square brackets, or a domain.
75 /// <https://url.spec.whatwg.org/#host-parsing>
76 pub fn parse(input
: &str) -> Result
<Self, ParseError
> {
77 if input
.starts_with('
['
) {
78 if !input
.ends_with('
]'
) {
79 return Err(ParseError
::InvalidIpv6Address
);
81 return parse_ipv6addr(&input
[1..input
.len() - 1]).map(Host
::Ipv6
);
83 let domain
= percent_decode(input
.as_bytes()).decode_utf8_lossy();
84 let domain
= idna
::domain_to_ascii(&domain
)?
;
106 return Err(ParseError
::InvalidDomainCharacter
);
108 if let Some(address
) = parse_ipv4addr(&domain
)?
{
109 Ok(Host
::Ipv4(address
))
111 Ok(Host
::Domain(domain
.into()))
115 // <https://url.spec.whatwg.org/#concept-opaque-host-parser>
116 pub fn parse_opaque(input
: &str) -> Result
<Self, ParseError
> {
117 if input
.starts_with('
['
) {
118 if !input
.ends_with('
]'
) {
119 return Err(ParseError
::InvalidIpv6Address
);
121 return parse_ipv6addr(&input
[1..input
.len() - 1]).map(Host
::Ipv6
);
143 return Err(ParseError
::InvalidDomainCharacter
);
145 let s
= utf8_percent_encode(input
, CONTROLS
).to_string();
150 impl<S
: AsRef
<str>> fmt
::Display
for Host
<S
> {
151 fn fmt(&self, f
: &mut Formatter
) -> fmt
::Result
{
153 Host
::Domain(ref domain
) => domain
.as_ref().fmt(f
),
154 Host
::Ipv4(ref addr
) => addr
.fmt(f
),
155 Host
::Ipv6(ref addr
) => {
157 write_ipv6(addr
, f
)?
;
164 fn write_ipv6(addr
: &Ipv6Addr
, f
: &mut Formatter
) -> fmt
::Result
{
165 let segments
= addr
.segments();
166 let (compress_start
, compress_end
) = longest_zero_sequence(&segments
);
169 if i
== compress_start
{
174 if compress_end
< 8 {
180 write
!(f
, "{:x}", segments
[i
as usize])?
;
189 // https://url.spec.whatwg.org/#concept-ipv6-serializer step 2 and 3
190 fn longest_zero_sequence(pieces
: &[u16; 8]) -> (isize, isize) {
191 let mut longest
= -1;
192 let mut longest_length
= -1;
194 macro_rules
! finish_sequence(
197 let length
= $end
- start
;
198 if length
> longest_length
{
200 longest_length
= length
;
206 if pieces
[i
as usize] == 0 {
216 // https://url.spec.whatwg.org/#concept-ipv6-serializer
217 // step 3: ignore lone zeroes
218 if longest_length
< 2 {
221 (longest
, longest
+ longest_length
)
225 /// <https://url.spec.whatwg.org/#ipv4-number-parser>
226 fn parse_ipv4number(mut input
: &str) -> Result
<Option
<u32>, ()> {
228 if input
.starts_with("0x") || input
.starts_with("0X") {
231 } else if input
.len() >= 2 && input
.starts_with('
0'
) {
236 // At the moment we can't know the reason why from_str_radix fails
237 // https://github.com/rust-lang/rust/issues/22639
238 // So instead we check if the input looks like a real number and only return
239 // an error when it's an overflow.
240 let valid_number
= match r
{
241 8 => input
.chars().all(|c
| c
>= '
0'
&& c
<= '
7'
),
242 10 => input
.chars().all(|c
| c
>= '
0'
&& c
<= '
9'
),
245 .all(|c
| (c
>= '
0'
&& c
<= '
9'
) || (c
>= 'a'
&& c
<= 'f'
) || (c
>= 'A'
&& c
<= 'F'
)),
253 if input
.is_empty() {
256 if input
.starts_with('
+'
) {
259 match u32::from_str_radix(input
, r
) {
260 Ok(number
) => Ok(Some(number
)),
265 /// <https://url.spec.whatwg.org/#concept-ipv4-parser>
266 fn parse_ipv4addr(input
: &str) -> ParseResult
<Option
<Ipv4Addr
>> {
267 if input
.is_empty() {
270 let mut parts
: Vec
<&str> = input
.split('
.'
).collect();
271 if parts
.last() == Some(&"") {
277 let mut numbers
: Vec
<u32> = Vec
::new();
278 let mut overflow
= false;
283 match parse_ipv4number(part
) {
284 Ok(Some(n
)) => numbers
.push(n
),
285 Ok(None
) => return Ok(None
),
286 Err(()) => overflow
= true,
290 return Err(ParseError
::InvalidIpv4Address
);
292 let mut ipv4
= numbers
.pop().expect("a non-empty list of numbers");
293 // Equivalent to: ipv4 >= 256 ** (4 − numbers.len())
294 if ipv4
> u32::max_value() >> (8 * numbers
.len() as u32) {
295 return Err(ParseError
::InvalidIpv4Address
);
297 if numbers
.iter().any(|x
| *x
> 255) {
298 return Err(ParseError
::InvalidIpv4Address
);
300 for (counter
, n
) in numbers
.iter().enumerate() {
301 ipv4
+= n
<< (8 * (3 - counter
as u32))
303 Ok(Some(Ipv4Addr
::from(ipv4
)))
306 /// <https://url.spec.whatwg.org/#concept-ipv6-parser>
307 fn parse_ipv6addr(input
: &str) -> ParseResult
<Ipv6Addr
> {
308 let input
= input
.as_bytes();
309 let len
= input
.len();
310 let mut is_ip_v4
= false;
311 let mut pieces
= [0, 0, 0, 0, 0, 0, 0, 0];
312 let mut piece_pointer
= 0;
313 let mut compress_pointer
= None
;
317 return Err(ParseError
::InvalidIpv6Address
);
320 if input
[0] == b'
:'
{
321 if input
[1] != b'
:'
{
322 return Err(ParseError
::InvalidIpv6Address
);
326 compress_pointer
= Some(1);
330 if piece_pointer
== 8 {
331 return Err(ParseError
::InvalidIpv6Address
);
333 if input
[i
] == b'
:'
{
334 if compress_pointer
.is_some() {
335 return Err(ParseError
::InvalidIpv6Address
);
339 compress_pointer
= Some(piece_pointer
);
343 let end
= cmp
::min(len
, start
+ 4);
344 let mut value
= 0u16;
346 match (input
[i
] as char).to_digit(16) {
348 value
= value
* 0x10 + digit
as u16;
358 return Err(ParseError
::InvalidIpv6Address
);
361 if piece_pointer
> 6 {
362 return Err(ParseError
::InvalidIpv6Address
);
369 return Err(ParseError
::InvalidIpv6Address
);
372 _
=> return Err(ParseError
::InvalidIpv6Address
),
378 pieces
[piece_pointer
] = value
;
383 if piece_pointer
> 6 {
384 return Err(ParseError
::InvalidIpv6Address
);
386 let mut numbers_seen
= 0;
388 if numbers_seen
> 0 {
389 if numbers_seen
< 4 && (i
< len
&& input
[i
] == b'
.'
) {
392 return Err(ParseError
::InvalidIpv6Address
);
396 let mut ipv4_piece
= None
;
398 let digit
= match input
[i
] {
399 c @ b'
0'
..=b'
9'
=> c
- b'
0'
,
403 None
=> ipv4_piece
= Some(digit
as u16),
404 Some(0) => return Err(ParseError
::InvalidIpv6Address
), // No leading zero
406 *v
= *v
* 10 + digit
as u16;
408 return Err(ParseError
::InvalidIpv6Address
);
415 pieces
[piece_pointer
] = if let Some(v
) = ipv4_piece
{
416 pieces
[piece_pointer
] * 0x100 + v
418 return Err(ParseError
::InvalidIpv6Address
);
422 if numbers_seen
== 2 || numbers_seen
== 4 {
427 if numbers_seen
!= 4 {
428 return Err(ParseError
::InvalidIpv6Address
);
433 return Err(ParseError
::InvalidIpv6Address
);
436 match compress_pointer
{
437 Some(compress_pointer
) => {
438 let mut swaps
= piece_pointer
- compress_pointer
;
441 pieces
.swap(piece_pointer
, compress_pointer
+ swaps
- 1);
447 if piece_pointer
!= 8 {
448 return Err(ParseError
::InvalidIpv6Address
);
453 pieces
[0], pieces
[1], pieces
[2], pieces
[3], pieces
[4], pieces
[5], pieces
[6], pieces
[7],