77// except according to those terms.
88
99use std:: ascii:: AsciiExt ;
10+ use std:: cmp;
1011use std:: fmt:: { self , Formatter } ;
11- use std:: net:: { Ipv4Addr , Ipv6Addr } ;
1212use parser:: { ParseResult , ParseError } ;
13- use percent_encoding:: { percent_decode} ;
13+ use percent_encoding:: { from_hex , 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 ) ) ]
1919pub enum Host {
20- /// A (DNS) domain name.
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
2124 Domain ( String ) ,
22- /// An IPv4 address.
23- V4 ( Ipv4Addr ) ,
24- /// An IPv6 address.
25- V6 ( Ipv6Addr ) ,
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 ]
2637}
38+ #[ cfg( feature="heap_size" ) ]
39+ known_heap_size ! ( 0 , Ipv6Address ) ;
40+
2741
2842impl Host {
2943 /// Parse a host: either an IPv6 address in [] square brackets, or a domain.
@@ -37,30 +51,22 @@ impl Host {
3751 Err ( ParseError :: EmptyHost )
3852 } else if input. starts_with ( "[" ) {
3953 if input. ends_with ( "]" ) {
40- if let Ok ( addr) = input[ 1 ..input. len ( ) - 1 ] . parse ( ) {
41- Ok ( Host :: V6 ( addr) )
42- } else {
43- Err ( ParseError :: InvalidIpv6Address )
44- }
54+ Ipv6Address :: parse ( & input[ 1 ..input. len ( ) - 1 ] ) . map ( Host :: Ipv6 )
4555 } else {
4656 Err ( ParseError :: InvalidIpv6Address )
4757 }
4858 } else {
49- if let Ok ( addr) = input. parse ( ) {
50- Ok ( Host :: V4 ( addr) )
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 )
5168 } else {
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- }
69+ Ok ( Host :: Domain ( domain. to_ascii_lowercase ( ) ) )
6470 }
6571 }
6672 }
@@ -75,11 +81,203 @@ impl Host {
7581
7682
7783impl fmt:: Display for Host {
78- fn fmt ( & self , f : & mut Formatter ) -> fmt:: Result {
84+ fn fmt ( & self , formatter : & mut Formatter ) -> fmt:: Result {
7985 match * self {
80- Host :: Domain ( ref domain) => domain. fmt ( f) ,
81- Host :: V4 ( ref addr) => addr. fmt ( f) ,
82- Host :: V6 ( ref addr) => write ! ( f, "[{}]" , addr) ,
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 ;
83279 }
84280 }
281+ finish_sequence ! ( 8 ) ;
282+ ( longest, longest + longest_length)
85283}
0 commit comments