Skip to content

Commit ecc7bb7

Browse files
committed
Add IPv4 variant to Host and remove custom Ipv6 address type
The Whatwg URL Standard was changed to describe Ipv4 address parsing. Before this change there was no difference made between domains and Ipv4 addresses. This change also removes the custom Ipv6 type and uses the types provided by std for ip addresses. This is a breaking change. Version bumped to 0.3.0
1 parent 845e14b commit ecc7bb7

File tree

4 files changed

+45
-232
lines changed

4 files changed

+45
-232
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22

33
name = "url"
4-
version = "0.2.37"
4+
version = "0.3.0"
55
authors = [ "Simon Sapin <[email protected]>" ]
66

77
description = "URL library for Rust, based on the WHATWG URL Standard"
@@ -17,7 +17,7 @@ serde_serialization = ["serde"]
1717
heap_size = ["heapsize", "heapsize_plugin"]
1818

1919
[dependencies.heapsize]
20-
version = "0.1.1"
20+
version = "0.1.3"
2121
optional = true
2222

2323
[dependencies.heapsize_plugin]

src/host.rs

Lines changed: 30 additions & 228 deletions
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,23 @@
77
// except according to those terms.
88

99
use std::ascii::AsciiExt;
10-
use std::cmp;
1110
use std::fmt::{self, Formatter};
11+
use std::net::{Ipv4Addr, Ipv6Addr};
1212
use parser::{ParseResult, ParseError};
13-
use percent_encoding::{from_hex, percent_decode};
13+
use percent_encoding::{percent_decode};
1414

1515

1616
/// The host name of an URL.
1717
#[derive(PartialEq, Eq, Clone, Debug, Hash, PartialOrd, Ord)]
1818
#[cfg_attr(feature="heap_size", derive(HeapSizeOf))]
1919
pub enum Host {
20-
/// A (DNS) domain name or an IPv4 address.
21-
///
22-
/// FIXME: IPv4 probably should be a separate variant.
23-
/// See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26431
20+
/// A (DNS) domain name.
2421
Domain(String),
25-
26-
/// An IPv6 address, represented inside `[...]` square brackets
27-
/// so that `:` colon characters in the address are not ambiguous
28-
/// with the port number delimiter.
29-
Ipv6(Ipv6Address),
30-
}
31-
32-
33-
/// A 128 bit IPv6 address
34-
#[derive(Clone, Eq, PartialEq, Copy, Debug, Hash, PartialOrd, Ord)]
35-
pub struct Ipv6Address {
36-
pub pieces: [u16; 8]
22+
/// An IPv4 address.
23+
V4(Ipv4Addr),
24+
/// An IPv6 address.
25+
V6(Ipv6Addr),
3726
}
38-
#[cfg(feature="heap_size")]
39-
known_heap_size!(0, Ipv6Address);
40-
4127

4228
impl Host {
4329
/// Parse a host: either an IPv6 address in [] square brackets, or a domain.
@@ -51,22 +37,30 @@ impl Host {
5137
Err(ParseError::EmptyHost)
5238
} else if input.starts_with("[") {
5339
if input.ends_with("]") {
54-
Ipv6Address::parse(&input[1..input.len() - 1]).map(Host::Ipv6)
40+
if let Ok(addr) = input[1..input.len() - 1].parse() {
41+
Ok(Host::V6(addr))
42+
} else {
43+
Err(ParseError::InvalidIpv6Address)
44+
}
5545
} else {
5646
Err(ParseError::InvalidIpv6Address)
5747
}
5848
} else {
59-
let decoded = percent_decode(input.as_bytes());
60-
let domain = String::from_utf8_lossy(&decoded);
61-
// TODO: Remove this check and use IDNA "domain to ASCII"
62-
if !domain.is_ascii() {
63-
Err(ParseError::NonAsciiDomainsNotSupportedYet)
64-
} else if domain.find(&[
65-
'\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']'
66-
][..]).is_some() {
67-
Err(ParseError::InvalidDomainCharacter)
49+
if let Ok(addr) = input.parse() {
50+
Ok(Host::V4(addr))
6851
} else {
69-
Ok(Host::Domain(domain.to_ascii_lowercase()))
52+
let decoded = percent_decode(input.as_bytes());
53+
let domain = String::from_utf8_lossy(&decoded);
54+
// TODO: Remove this check and use IDNA "domain to ASCII"
55+
if !domain.is_ascii() {
56+
Err(ParseError::NonAsciiDomainsNotSupportedYet)
57+
} else if domain.find(&[
58+
'\0', '\t', '\n', '\r', ' ', '#', '%', '/', ':', '?', '@', '[', '\\', ']'
59+
][..]).is_some() {
60+
Err(ParseError::InvalidDomainCharacter)
61+
} else {
62+
Ok(Host::Domain(domain.to_ascii_lowercase()))
63+
}
7064
}
7165
}
7266
}
@@ -81,203 +75,11 @@ impl Host {
8175

8276

8377
impl fmt::Display for Host {
84-
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
78+
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
8579
match *self {
86-
Host::Domain(ref domain) => domain.fmt(formatter),
87-
Host::Ipv6(ref address) => {
88-
try!(formatter.write_str("["));
89-
try!(address.fmt(formatter));
90-
formatter.write_str("]")
91-
}
92-
}
93-
}
94-
}
95-
96-
97-
impl Ipv6Address {
98-
/// Parse an IPv6 address, without the [] square brackets.
99-
pub fn parse(input: &str) -> ParseResult<Ipv6Address> {
100-
let input = input.as_bytes();
101-
let len = input.len();
102-
let mut is_ip_v4 = false;
103-
let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0];
104-
let mut piece_pointer = 0;
105-
let mut compress_pointer = None;
106-
let mut i = 0;
107-
108-
if len < 2 {
109-
return Err(ParseError::InvalidIpv6Address)
110-
}
111-
112-
if input[0] == b':' {
113-
if input[1] != b':' {
114-
return Err(ParseError::InvalidIpv6Address)
115-
}
116-
i = 2;
117-
piece_pointer = 1;
118-
compress_pointer = Some(1);
119-
}
120-
121-
while i < len {
122-
if piece_pointer == 8 {
123-
return Err(ParseError::InvalidIpv6Address)
124-
}
125-
if input[i] == b':' {
126-
if compress_pointer.is_some() {
127-
return Err(ParseError::InvalidIpv6Address)
128-
}
129-
i += 1;
130-
piece_pointer += 1;
131-
compress_pointer = Some(piece_pointer);
132-
continue
133-
}
134-
let start = i;
135-
let end = cmp::min(len, start + 4);
136-
let mut value = 0u16;
137-
while i < end {
138-
match from_hex(input[i]) {
139-
Some(digit) => {
140-
value = value * 0x10 + digit as u16;
141-
i += 1;
142-
},
143-
None => break
144-
}
145-
}
146-
if i < len {
147-
match input[i] {
148-
b'.' => {
149-
if i == start {
150-
return Err(ParseError::InvalidIpv6Address)
151-
}
152-
i = start;
153-
is_ip_v4 = true;
154-
},
155-
b':' => {
156-
i += 1;
157-
if i == len {
158-
return Err(ParseError::InvalidIpv6Address)
159-
}
160-
},
161-
_ => return Err(ParseError::InvalidIpv6Address)
162-
}
163-
}
164-
if is_ip_v4 {
165-
break
166-
}
167-
pieces[piece_pointer] = value;
168-
piece_pointer += 1;
169-
}
170-
171-
if is_ip_v4 {
172-
if piece_pointer > 6 {
173-
return Err(ParseError::InvalidIpv6Address)
174-
}
175-
let mut dots_seen = 0;
176-
while i < len {
177-
// FIXME: https://github.com/whatwg/url/commit/1c22aa119c354e0020117e02571cec53f7c01064
178-
let mut value = 0u16;
179-
while i < len {
180-
let digit = match input[i] {
181-
c @ b'0' ... b'9' => c - b'0',
182-
_ => break
183-
};
184-
value = value * 10 + digit as u16;
185-
if value == 0 || value > 255 {
186-
return Err(ParseError::InvalidIpv6Address)
187-
}
188-
}
189-
if dots_seen < 3 && !(i < len && input[i] == b'.') {
190-
return Err(ParseError::InvalidIpv6Address)
191-
}
192-
pieces[piece_pointer] = pieces[piece_pointer] * 0x100 + value;
193-
if dots_seen == 0 || dots_seen == 2 {
194-
piece_pointer += 1;
195-
}
196-
i += 1;
197-
if dots_seen == 3 && i < len {
198-
return Err(ParseError::InvalidIpv6Address)
199-
}
200-
dots_seen += 1;
201-
}
202-
}
203-
204-
match compress_pointer {
205-
Some(compress_pointer) => {
206-
let mut swaps = piece_pointer - compress_pointer;
207-
piece_pointer = 7;
208-
while swaps > 0 {
209-
pieces[piece_pointer] = pieces[compress_pointer + swaps - 1];
210-
pieces[compress_pointer + swaps - 1] = 0;
211-
swaps -= 1;
212-
piece_pointer -= 1;
213-
}
214-
}
215-
_ => if piece_pointer != 8 {
216-
return Err(ParseError::InvalidIpv6Address)
217-
}
218-
}
219-
Ok(Ipv6Address { pieces: pieces })
220-
}
221-
222-
/// Serialize the IPv6 address to a string.
223-
pub fn serialize(&self) -> String {
224-
self.to_string()
225-
}
226-
}
227-
228-
229-
impl fmt::Display for Ipv6Address {
230-
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
231-
let (compress_start, compress_end) = longest_zero_sequence(&self.pieces);
232-
let mut i = 0;
233-
while i < 8 {
234-
if i == compress_start {
235-
try!(formatter.write_str(":"));
236-
if i == 0 {
237-
try!(formatter.write_str(":"));
238-
}
239-
if compress_end < 8 {
240-
i = compress_end;
241-
} else {
242-
break;
243-
}
244-
}
245-
try!(write!(formatter, "{:x}", self.pieces[i as usize]));
246-
if i < 7 {
247-
try!(formatter.write_str(":"));
248-
}
249-
i += 1;
250-
}
251-
Ok(())
252-
}
253-
}
254-
255-
256-
fn longest_zero_sequence(pieces: &[u16; 8]) -> (isize, isize) {
257-
let mut longest = -1;
258-
let mut longest_length = -1;
259-
let mut start = -1;
260-
macro_rules! finish_sequence(
261-
($end: expr) => {
262-
if start >= 0 {
263-
let length = $end - start;
264-
if length > longest_length {
265-
longest = start;
266-
longest_length = length;
267-
}
268-
}
269-
};
270-
);
271-
for i in 0..8 {
272-
if pieces[i as usize] == 0 {
273-
if start < 0 {
274-
start = i;
275-
}
276-
} else {
277-
finish_sequence!(i);
278-
start = -1;
80+
Host::Domain(ref domain) => domain.fmt(f),
81+
Host::V4(ref addr) => addr.fmt(f),
82+
Host::V6(ref addr) => write!(f, "[{}]", addr),
27983
}
28084
}
281-
finish_sequence!(8);
282-
(longest, longest + longest_length)
28385
}

src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ use std::cmp::Ordering;
143143
#[cfg(feature="serde_serialization")]
144144
use std::str::FromStr;
145145

146-
pub use host::{Host, Ipv6Address};
146+
pub use host::Host;
147147
pub use parser::{ErrorHandler, ParseResult, ParseError};
148148

149149
use percent_encoding::{percent_encode, lossy_utf8_percent_decode, DEFAULT_ENCODE_SET};
@@ -1140,4 +1140,3 @@ fn file_url_path_to_pathbuf_windows(path: &[String]) -> Result<PathBuf, ()> {
11401140
"to_file_path() failed to produce an absolute Path");
11411141
Ok(path)
11421142
}
1143-

src/tests.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99

1010
use std::char;
11+
use std::net::{Ipv4Addr, Ipv6Addr};
1112
use super::{UrlParser, Url, SchemeData, RelativeSchemeData, Host};
1213

1314

@@ -347,3 +348,14 @@ fn relative_scheme_data_equality() {
347348
let b: Url = url("http://foo.com/");
348349
check_eq(&a, &b);
349350
}
351+
352+
#[test]
353+
fn host() {
354+
let a = Host::parse("www.mozilla.org").unwrap();
355+
let b = Host::parse("1.35.33.49").unwrap();
356+
let c = Host::parse("[2001:0db8:85a3:08d3:1319:8a2e:0370:7344]").unwrap();
357+
assert_eq!(a, Host::Domain("www.mozilla.org".to_owned()));
358+
assert_eq!(b, Host::V4(Ipv4Addr::new(1, 35, 33, 49)));
359+
assert_eq!(c, Host::V6(Ipv6Addr::new(0x2001, 0x0db8, 0x85a3, 0x08d3,
360+
0x1319, 0x8a2e, 0x0370, 0x7344)));
361+
}

0 commit comments

Comments
 (0)