Coverage Report

Created: 2025-05-08 06:26

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