Coverage Report

Created: 2023-04-25 07:07

/rust/registry/src/index.crates.io-6f17d22bba15001f/url-2.3.1/src/host.rs
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2013-2016 The rust-url developers.
2
//
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.
8
9
use std::cmp;
10
use std::fmt::{self, Formatter};
11
use std::net::{Ipv4Addr, Ipv6Addr};
12
13
use percent_encoding::{percent_decode, utf8_percent_encode, CONTROLS};
14
#[cfg(feature = "serde")]
15
use serde::{Deserialize, Serialize};
16
17
use crate::parser::{ParseError, ParseResult};
18
19
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
20
1.05M
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
Unexecuted instantiation: <url::host::HostInternal as core::clone::Clone>::clone
Unexecuted instantiation: <url::host::HostInternal as core::clone::Clone>::clone
<url::host::HostInternal as core::clone::Clone>::clone
Line
Count
Source
20
1.05M
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
Unexecuted instantiation: <url::host::HostInternal as core::fmt::Debug>::fmt
Unexecuted instantiation: <url::host::HostInternal as core::fmt::Debug>::fmt
Unexecuted instantiation: <url::host::HostInternal as core::cmp::PartialEq>::eq
Unexecuted instantiation: <url::host::HostInternal as core::cmp::PartialEq>::eq
21
pub(crate) enum HostInternal {
22
    None,
23
    Domain,
24
    Ipv4(Ipv4Addr),
25
    Ipv6(Ipv6Addr),
26
}
27
28
impl From<Host<String>> for HostInternal {
29
    fn from(host: Host<String>) -> HostInternal {
30
0
        match host {
31
0
            Host::Domain(ref s) if s.is_empty() => HostInternal::None,
32
0
            Host::Domain(_) => HostInternal::Domain,
33
0
            Host::Ipv4(address) => HostInternal::Ipv4(address),
34
0
            Host::Ipv6(address) => HostInternal::Ipv6(address),
35
        }
36
0
    }
Unexecuted instantiation: <url::host::HostInternal as core::convert::From<url::host::Host>>::from
Unexecuted instantiation: <url::host::HostInternal as core::convert::From<url::host::Host>>::from
37
}
38
39
/// The host name of an URL.
40
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
41
0
#[derive(Clone, Debug, Eq, Ord, PartialOrd, Hash)]
Unexecuted instantiation: <url::host::Host as core::clone::Clone>::clone
Unexecuted instantiation: <url::host::Host as core::clone::Clone>::clone
Unexecuted instantiation: <url::host::Host as core::fmt::Debug>::fmt
Unexecuted instantiation: <url::host::Host<&str> as core::fmt::Debug>::fmt
Unexecuted instantiation: <url::host::Host<&str> as core::fmt::Debug>::fmt
Unexecuted instantiation: <url::host::Host as core::fmt::Debug>::fmt
Unexecuted instantiation: <url::host::Host<_> as core::cmp::Ord>::cmp
Unexecuted instantiation: <url::host::Host<_> as core::cmp::Ord>::cmp
Unexecuted instantiation: <url::host::Host<_> as core::cmp::PartialOrd>::partial_cmp
Unexecuted instantiation: <url::host::Host<_> as core::cmp::PartialOrd>::partial_cmp
Unexecuted instantiation: <url::host::Host<_> as core::hash::Hash>::hash::<_>
Unexecuted instantiation: <url::host::Host<_> as core::hash::Hash>::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.
47
    Domain(S),
48
49
    /// An IPv4 address.
50
    /// `Url::host_str` returns the serialization of this address,
51
    /// as four decimal integers separated by `.` dots.
52
    Ipv4(Ipv4Addr),
53
54
    /// An IPv6 address.
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.
59
    Ipv6(Ipv6Addr),
60
}
61
62
impl<'a> Host<&'a str> {
63
    /// Return a copy of `self` that owns an allocated `String` but does not borrow an `&Url`.
64
0
    pub fn to_owned(&self) -> Host<String> {
65
0
        match *self {
66
0
            Host::Domain(domain) => Host::Domain(domain.to_owned()),
67
0
            Host::Ipv4(address) => Host::Ipv4(address),
68
0
            Host::Ipv6(address) => Host::Ipv6(address),
69
        }
70
0
    }
Unexecuted instantiation: <url::host::Host<&str>>::to_owned
Unexecuted instantiation: <url::host::Host<&str>>::to_owned
71
}
72
73
impl Host<String> {
74
    /// Parse a host: either an IPv6 address in [] square brackets, or a domain.
75
    ///
76
    /// <https://url.spec.whatwg.org/#host-parsing>
77
0
    pub fn parse(input: &str) -> Result<Self, ParseError> {
78
0
        if input.starts_with('[') {
79
0
            if !input.ends_with(']') {
80
0
                return Err(ParseError::InvalidIpv6Address);
81
0
            }
82
0
            return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6);
83
0
        }
84
0
        let domain = percent_decode(input.as_bytes()).decode_utf8_lossy();
85
86
0
        let domain = Self::domain_to_ascii(&domain)?;
87
88
0
        if domain.is_empty() {
89
0
            return Err(ParseError::EmptyHost);
90
0
        }
91
0
92
0
        let is_invalid_domain_char = |c| {
93
0
            matches!(
94
0
                c,
95
0
                '\0'..='\u{001F}'
96
                    | ' '
97
                    | '#'
98
                    | '%'
99
                    | '/'
100
                    | ':'
101
                    | '<'
102
                    | '>'
103
                    | '?'
104
                    | '@'
105
                    | '['
106
                    | '\\'
107
                    | ']'
108
                    | '^'
109
                    | '\u{007F}'
110
                    | '|'
111
            )
112
0
        };
Unexecuted instantiation: <url::host::Host>::parse::{closure#0}
Unexecuted instantiation: <url::host::Host>::parse::{closure#0}
113
114
0
        if domain.find(is_invalid_domain_char).is_some() {
115
0
            Err(ParseError::InvalidDomainCharacter)
116
0
        } else if ends_in_a_number(&domain) {
117
0
            let address = parse_ipv4addr(&domain)?;
118
0
            Ok(Host::Ipv4(address))
119
        } else {
120
0
            Ok(Host::Domain(domain))
121
        }
122
0
    }
Unexecuted instantiation: <url::host::Host>::parse
Unexecuted instantiation: <url::host::Host>::parse
123
124
    // <https://url.spec.whatwg.org/#concept-opaque-host-parser>
125
0
    pub fn parse_opaque(input: &str) -> Result<Self, ParseError> {
126
0
        if input.starts_with('[') {
127
0
            if !input.ends_with(']') {
128
0
                return Err(ParseError::InvalidIpv6Address);
129
0
            }
130
0
            return parse_ipv6addr(&input[1..input.len() - 1]).map(Host::Ipv6);
131
0
        }
132
0
133
0
        let is_invalid_host_char = |c| {
134
0
            matches!(
135
0
                c,
136
                '\0' | '\t'
137
                    | '\n'
138
                    | '\r'
139
                    | ' '
140
                    | '#'
141
                    | '/'
142
                    | ':'
143
                    | '<'
144
                    | '>'
145
                    | '?'
146
                    | '@'
147
                    | '['
148
                    | '\\'
149
                    | ']'
150
                    | '^'
151
                    | '|'
152
            )
153
0
        };
Unexecuted instantiation: <url::host::Host>::parse_opaque::{closure#0}
Unexecuted instantiation: <url::host::Host>::parse_opaque::{closure#0}
154
155
0
        if input.find(is_invalid_host_char).is_some() {
156
0
            Err(ParseError::InvalidDomainCharacter)
157
        } else {
158
0
            Ok(Host::Domain(
159
0
                utf8_percent_encode(input, CONTROLS).to_string(),
160
0
            ))
161
        }
162
0
    }
Unexecuted instantiation: <url::host::Host>::parse_opaque
Unexecuted instantiation: <url::host::Host>::parse_opaque
163
164
    /// convert domain with idna
165
0
    fn domain_to_ascii(domain: &str) -> Result<String, ParseError> {
166
0
        idna::domain_to_ascii(domain).map_err(Into::into)
167
0
    }
Unexecuted instantiation: <url::host::Host>::domain_to_ascii
Unexecuted instantiation: <url::host::Host>::domain_to_ascii
168
}
169
170
impl<S: AsRef<str>> fmt::Display for Host<S> {
171
0
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
172
0
        match *self {
173
0
            Host::Domain(ref domain) => domain.as_ref().fmt(f),
174
0
            Host::Ipv4(ref addr) => addr.fmt(f),
175
0
            Host::Ipv6(ref addr) => {
176
0
                f.write_str("[")?;
177
0
                write_ipv6(addr, f)?;
178
0
                f.write_str("]")
179
            }
180
        }
181
0
    }
Unexecuted instantiation: <url::host::Host as core::fmt::Display>::fmt
Unexecuted instantiation: <url::host::Host as core::fmt::Display>::fmt
182
}
183
184
impl<S, T> PartialEq<Host<T>> for Host<S>
185
where
186
    S: PartialEq<T>,
187
{
188
    fn eq(&self, other: &Host<T>) -> bool {
189
0
        match (self, other) {
190
0
            (Host::Domain(a), Host::Domain(b)) => a == b,
191
0
            (Host::Ipv4(a), Host::Ipv4(b)) => a == b,
192
0
            (Host::Ipv6(a), Host::Ipv6(b)) => a == b,
193
0
            (_, _) => false,
194
        }
195
0
    }
Unexecuted instantiation: <url::host::Host<&str> as core::cmp::PartialEq>::eq
Unexecuted instantiation: <url::host::Host as core::cmp::PartialEq>::eq
Unexecuted instantiation: <url::host::Host as core::cmp::PartialEq>::eq
Unexecuted instantiation: <url::host::Host<&str> as core::cmp::PartialEq>::eq
196
}
197
198
0
fn write_ipv6(addr: &Ipv6Addr, f: &mut Formatter<'_>) -> fmt::Result {
199
0
    let segments = addr.segments();
200
0
    let (compress_start, compress_end) = longest_zero_sequence(&segments);
201
0
    let mut i = 0;
202
0
    while i < 8 {
203
0
        if i == compress_start {
204
0
            f.write_str(":")?;
205
0
            if i == 0 {
206
0
                f.write_str(":")?;
207
0
            }
208
0
            if compress_end < 8 {
209
0
                i = compress_end;
210
0
            } else {
211
0
                break;
212
            }
213
0
        }
214
0
        write!(f, "{:x}", segments[i as usize])?;
215
0
        if i < 7 {
216
0
            f.write_str(":")?;
217
0
        }
218
0
        i += 1;
219
    }
220
0
    Ok(())
221
0
}
Unexecuted instantiation: url::host::write_ipv6
Unexecuted instantiation: url::host::write_ipv6
222
223
// https://url.spec.whatwg.org/#concept-ipv6-serializer step 2 and 3
224
0
fn longest_zero_sequence(pieces: &[u16; 8]) -> (isize, isize) {
225
0
    let mut longest = -1;
226
0
    let mut longest_length = -1;
227
0
    let mut start = -1;
228
    macro_rules! finish_sequence(
229
        ($end: expr) => {
230
            if start >= 0 {
231
                let length = $end - start;
232
                if length > longest_length {
233
                    longest = start;
234
                    longest_length = length;
235
                }
236
            }
237
        };
238
    );
239
0
    for i in 0..8 {
240
0
        if pieces[i as usize] == 0 {
241
0
            if start < 0 {
242
0
                start = i;
243
0
            }
244
        } else {
245
0
            finish_sequence!(i);
246
0
            start = -1;
247
        }
248
    }
249
0
    finish_sequence!(8);
250
    // https://url.spec.whatwg.org/#concept-ipv6-serializer
251
    // step 3: ignore lone zeroes
252
0
    if longest_length < 2 {
253
0
        (-1, -2)
254
    } else {
255
0
        (longest, longest + longest_length)
256
    }
257
0
}
Unexecuted instantiation: url::host::longest_zero_sequence
Unexecuted instantiation: url::host::longest_zero_sequence
258
259
/// <https://url.spec.whatwg.org/#ends-in-a-number-checker>
260
0
fn ends_in_a_number(input: &str) -> bool {
261
0
    let mut parts = input.rsplit('.');
262
0
    let last = parts.next().unwrap();
263
0
    let last = if last.is_empty() {
264
0
        if let Some(last) = parts.next() {
265
0
            last
266
        } else {
267
0
            return false;
268
        }
269
    } else {
270
0
        last
271
    };
272
0
    if !last.is_empty() && last.chars().all(|c| ('0'..='9').contains(&c)) {
Unexecuted instantiation: url::host::ends_in_a_number::{closure#0}
Unexecuted instantiation: url::host::ends_in_a_number::{closure#0}
273
0
        return true;
274
0
    }
275
0
276
0
    parse_ipv4number(last).is_ok()
277
0
}
Unexecuted instantiation: url::host::ends_in_a_number
Unexecuted instantiation: url::host::ends_in_a_number
278
279
/// <https://url.spec.whatwg.org/#ipv4-number-parser>
280
/// Ok(None) means the input is a valid number, but it overflows a `u32`.
281
0
fn parse_ipv4number(mut input: &str) -> Result<Option<u32>, ()> {
282
0
    if input.is_empty() {
283
0
        return Err(());
284
0
    }
285
0
286
0
    let mut r = 10;
287
0
    if input.starts_with("0x") || input.starts_with("0X") {
288
0
        input = &input[2..];
289
0
        r = 16;
290
0
    } else if input.len() >= 2 && input.starts_with('0') {
291
0
        input = &input[1..];
292
0
        r = 8;
293
0
    }
294
295
0
    if input.is_empty() {
296
0
        return Ok(Some(0));
297
0
    }
298
299
0
    let valid_number = match r {
300
0
        8 => input.chars().all(|c| ('0'..='7').contains(&c)),
Unexecuted instantiation: url::host::parse_ipv4number::{closure#0}
Unexecuted instantiation: url::host::parse_ipv4number::{closure#0}
301
0
        10 => input.chars().all(|c| ('0'..='9').contains(&c)),
Unexecuted instantiation: url::host::parse_ipv4number::{closure#1}
Unexecuted instantiation: url::host::parse_ipv4number::{closure#1}
302
0
        16 => input.chars().all(|c| {
303
0
            ('0'..='9').contains(&c) || ('a'..='f').contains(&c) || ('A'..='F').contains(&c)
304
0
        }),
Unexecuted instantiation: url::host::parse_ipv4number::{closure#2}
Unexecuted instantiation: url::host::parse_ipv4number::{closure#2}
305
0
        _ => false,
306
    };
307
0
    if !valid_number {
308
0
        return Err(());
309
0
    }
310
0
311
0
    match u32::from_str_radix(input, r) {
312
0
        Ok(num) => Ok(Some(num)),
313
0
        Err(_) => Ok(None), // The only possible error kind here is an integer overflow.
314
                            // The validity of the chars in the input is checked above.
315
    }
316
0
}
Unexecuted instantiation: url::host::parse_ipv4number
Unexecuted instantiation: url::host::parse_ipv4number
317
318
/// <https://url.spec.whatwg.org/#concept-ipv4-parser>
319
0
fn parse_ipv4addr(input: &str) -> ParseResult<Ipv4Addr> {
320
0
    let mut parts: Vec<&str> = input.split('.').collect();
321
0
    if parts.last() == Some(&"") {
322
0
        parts.pop();
323
0
    }
324
0
    if parts.len() > 4 {
325
0
        return Err(ParseError::InvalidIpv4Address);
326
0
    }
327
0
    let mut numbers: Vec<u32> = Vec::new();
328
0
    for part in parts {
329
0
        match parse_ipv4number(part) {
330
0
            Ok(Some(n)) => numbers.push(n),
331
0
            Ok(None) => return Err(ParseError::InvalidIpv4Address), // u32 overflow
332
0
            Err(()) => return Err(ParseError::InvalidIpv4Address),
333
        };
334
    }
335
0
    let mut ipv4 = numbers.pop().expect("a non-empty list of numbers");
336
0
    // Equivalent to: ipv4 >= 256 ** (4 − numbers.len())
337
0
    if ipv4 > u32::max_value() >> (8 * numbers.len() as u32) {
338
0
        return Err(ParseError::InvalidIpv4Address);
339
0
    }
340
0
    if numbers.iter().any(|x| *x > 255) {
Unexecuted instantiation: url::host::parse_ipv4addr::{closure#0}
Unexecuted instantiation: url::host::parse_ipv4addr::{closure#0}
341
0
        return Err(ParseError::InvalidIpv4Address);
342
0
    }
343
0
    for (counter, n) in numbers.iter().enumerate() {
344
0
        ipv4 += n << (8 * (3 - counter as u32))
345
    }
346
0
    Ok(Ipv4Addr::from(ipv4))
347
0
}
Unexecuted instantiation: url::host::parse_ipv4addr
Unexecuted instantiation: url::host::parse_ipv4addr
348
349
/// <https://url.spec.whatwg.org/#concept-ipv6-parser>
350
0
fn parse_ipv6addr(input: &str) -> ParseResult<Ipv6Addr> {
351
0
    let input = input.as_bytes();
352
0
    let len = input.len();
353
0
    let mut is_ip_v4 = false;
354
0
    let mut pieces = [0, 0, 0, 0, 0, 0, 0, 0];
355
0
    let mut piece_pointer = 0;
356
0
    let mut compress_pointer = None;
357
0
    let mut i = 0;
358
0
359
0
    if len < 2 {
360
0
        return Err(ParseError::InvalidIpv6Address);
361
0
    }
362
0
363
0
    if input[0] == b':' {
364
0
        if input[1] != b':' {
365
0
            return Err(ParseError::InvalidIpv6Address);
366
0
        }
367
0
        i = 2;
368
0
        piece_pointer = 1;
369
0
        compress_pointer = Some(1);
370
0
    }
371
372
0
    while i < len {
373
0
        if piece_pointer == 8 {
374
0
            return Err(ParseError::InvalidIpv6Address);
375
0
        }
376
0
        if input[i] == b':' {
377
0
            if compress_pointer.is_some() {
378
0
                return Err(ParseError::InvalidIpv6Address);
379
0
            }
380
0
            i += 1;
381
0
            piece_pointer += 1;
382
0
            compress_pointer = Some(piece_pointer);
383
0
            continue;
384
0
        }
385
0
        let start = i;
386
0
        let end = cmp::min(len, start + 4);
387
0
        let mut value = 0u16;
388
0
        while i < end {
389
0
            match (input[i] as char).to_digit(16) {
390
0
                Some(digit) => {
391
0
                    value = value * 0x10 + digit as u16;
392
0
                    i += 1;
393
0
                }
394
0
                None => break,
395
            }
396
        }
397
0
        if i < len {
398
0
            match input[i] {
399
                b'.' => {
400
0
                    if i == start {
401
0
                        return Err(ParseError::InvalidIpv6Address);
402
0
                    }
403
0
                    i = start;
404
0
                    if piece_pointer > 6 {
405
0
                        return Err(ParseError::InvalidIpv6Address);
406
0
                    }
407
0
                    is_ip_v4 = true;
408
                }
409
                b':' => {
410
0
                    i += 1;
411
0
                    if i == len {
412
0
                        return Err(ParseError::InvalidIpv6Address);
413
0
                    }
414
                }
415
0
                _ => return Err(ParseError::InvalidIpv6Address),
416
            }
417
0
        }
418
0
        if is_ip_v4 {
419
0
            break;
420
0
        }
421
0
        pieces[piece_pointer] = value;
422
0
        piece_pointer += 1;
423
    }
424
425
0
    if is_ip_v4 {
426
0
        if piece_pointer > 6 {
427
0
            return Err(ParseError::InvalidIpv6Address);
428
0
        }
429
0
        let mut numbers_seen = 0;
430
0
        while i < len {
431
0
            if numbers_seen > 0 {
432
0
                if numbers_seen < 4 && (i < len && input[i] == b'.') {
433
0
                    i += 1
434
                } else {
435
0
                    return Err(ParseError::InvalidIpv6Address);
436
                }
437
0
            }
438
439
0
            let mut ipv4_piece = None;
440
0
            while i < len {
441
0
                let digit = match input[i] {
442
0
                    c @ b'0'..=b'9' => c - b'0',
443
0
                    _ => break,
444
                };
445
0
                match ipv4_piece {
446
0
                    None => ipv4_piece = Some(digit as u16),
447
0
                    Some(0) => return Err(ParseError::InvalidIpv6Address), // No leading zero
448
0
                    Some(ref mut v) => {
449
0
                        *v = *v * 10 + digit as u16;
450
0
                        if *v > 255 {
451
0
                            return Err(ParseError::InvalidIpv6Address);
452
0
                        }
453
                    }
454
                }
455
0
                i += 1;
456
            }
457
458
0
            pieces[piece_pointer] = if let Some(v) = ipv4_piece {
459
0
                pieces[piece_pointer] * 0x100 + v
460
            } else {
461
0
                return Err(ParseError::InvalidIpv6Address);
462
            };
463
0
            numbers_seen += 1;
464
0
465
0
            if numbers_seen == 2 || numbers_seen == 4 {
466
0
                piece_pointer += 1;
467
0
            }
468
        }
469
470
0
        if numbers_seen != 4 {
471
0
            return Err(ParseError::InvalidIpv6Address);
472
0
        }
473
0
    }
474
475
0
    if i < len {
476
0
        return Err(ParseError::InvalidIpv6Address);
477
0
    }
478
0
479
0
    match compress_pointer {
480
0
        Some(compress_pointer) => {
481
0
            let mut swaps = piece_pointer - compress_pointer;
482
0
            piece_pointer = 7;
483
0
            while swaps > 0 {
484
0
                pieces.swap(piece_pointer, compress_pointer + swaps - 1);
485
0
                swaps -= 1;
486
0
                piece_pointer -= 1;
487
0
            }
488
        }
489
        _ => {
490
0
            if piece_pointer != 8 {
491
0
                return Err(ParseError::InvalidIpv6Address);
492
0
            }
493
        }
494
    }
495
0
    Ok(Ipv6Addr::new(
496
0
        pieces[0], pieces[1], pieces[2], pieces[3], pieces[4], pieces[5], pieces[6], pieces[7],
497
0
    ))
498
0
}
Unexecuted instantiation: url::host::parse_ipv6addr
Unexecuted instantiation: url::host::parse_ipv6addr