7
7
// except according to those terms.
8
8
9
9
use std:: ascii:: AsciiExt ;
10
+ use std:: cmp;
10
11
use std:: fmt:: { self , Formatter } ;
11
- use std:: net:: { Ipv4Addr , Ipv6Addr } ;
12
12
use parser:: { ParseResult , ParseError } ;
13
- use percent_encoding:: { percent_decode} ;
13
+ use percent_encoding:: { from_hex , percent_decode} ;
14
14
15
15
16
16
/// The host name of an URL.
17
17
#[ derive( PartialEq , Eq , Clone , Debug , Hash , PartialOrd , Ord ) ]
18
18
#[ cfg_attr( feature="heap_size" , derive( HeapSizeOf ) ) ]
19
19
pub 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
21
24
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 ]
26
37
}
38
+ #[ cfg( feature="heap_size" ) ]
39
+ known_heap_size ! ( 0 , Ipv6Address ) ;
40
+
27
41
28
42
impl Host {
29
43
/// Parse a host: either an IPv6 address in [] square brackets, or a domain.
@@ -37,30 +51,22 @@ impl Host {
37
51
Err ( ParseError :: EmptyHost )
38
52
} else if input. starts_with ( "[" ) {
39
53
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 )
45
55
} else {
46
56
Err ( ParseError :: InvalidIpv6Address )
47
57
}
48
58
} 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 )
51
68
} 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 ( ) ) )
64
70
}
65
71
}
66
72
}
@@ -75,11 +81,203 @@ impl Host {
75
81
76
82
77
83
impl fmt:: Display for Host {
78
- fn fmt ( & self , f : & mut Formatter ) -> fmt:: Result {
84
+ fn fmt ( & self , formatter : & mut Formatter ) -> fmt:: Result {
79
85
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 ;
83
279
}
84
280
}
281
+ finish_sequence ! ( 8 ) ;
282
+ ( longest, longest + longest_length)
85
283
}
0 commit comments