Skip to main content

parse_net/
host.rs

1//! `Host = uri-host [ ":" port ]` (RFC 9110 section 7.2, citing RFC 3986
2//! section 3.2.2), with real IPv4 and IPv6 validation.
3//!
4//! # Examples
5//!
6//! ```
7//! use core::net::Ipv6Addr;
8//! use parse_net::{self as pn, Host, HostPort};
9//!
10//! assert_eq!(pn::parse_host_port(b"[::1]:8080")?,
11//!            HostPort { host: Host::V6(Ipv6Addr::LOCALHOST), port: Some(8080), empty_port: false });
12//! assert_eq!(pn::parse_host_port(b"Example.COM.")?.host, Host::Name(b"Example.COM."));
13//! assert_eq!(pn::parse_host_port(b"127.1").unwrap_err().kind(), pn::ErrorKind::InvalidAddress);
14//! assert_eq!(pn::parse_host_port(b"a.com:80@b.com").unwrap_err().kind(), pn::ErrorKind::InvalidHost);
15//! # Ok::<(), pn::ParseError>(())
16//! ```
17
18use core::{
19    fmt::{self, Write as _},
20    net::{Ipv4Addr, Ipv6Addr},
21    str::FromStr,
22};
23
24use crate::{
25    class,
26    error::{ErrorKind, Result, WriteError, err},
27    scan::{control_kind, find_byte},
28    text::{skip_ows, trim_ows},
29    write::{CountSink, Sink, reserve},
30};
31
32/// A host: a registered name, an IP address, or a future IP literal.
33///
34/// # Examples
35///
36/// ```
37/// use core::net::Ipv4Addr;
38/// use parse_net::{parse_host_port, Host};
39///
40/// assert_eq!(parse_host_port(b"192.0.2.1")?.host, Host::V4(Ipv4Addr::new(192, 0, 2, 1)));
41/// assert_eq!(parse_host_port(b"example.com")?.host, Host::Name(b"example.com"));
42/// assert_eq!(parse_host_port(b"[v1.fe]")?.host, Host::Future(b"v1.fe"));
43/// # Ok::<(), parse_net::ParseError>(())
44/// ```
45#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
46pub enum Host<'a> {
47    /// A registered name (`reg-name`), as sent: case and a trailing dot are
48    /// kept, `%XX` triples are not decoded. Empty when the field was empty.
49    Name(&'a [u8]),
50    /// A dotted-decimal IPv4 address.
51    V4(Ipv4Addr),
52    /// A bracketed IPv6 address.
53    V6(Ipv6Addr),
54    /// A bracketed `IPvFuture` literal (`vX.yyy`), without the brackets.
55    Future(&'a [u8]),
56}
57
58impl Host<'_> {
59    /// Whether two hosts are the same: names compared ASCII
60    /// case-insensitively, IP literals by value.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use parse_net::host::parse_host;
66    ///
67    /// assert!(parse_host(b"Example.com")?.eq_ignore_ascii_case(&parse_host(b"example.COM")?));
68    /// assert!(parse_host(b"[::1]")?.eq_ignore_ascii_case(&parse_host(b"[0:0::1]")?));
69    /// # Ok::<(), parse_net::ParseError>(())
70    /// ```
71    #[must_use]
72    pub fn eq_ignore_ascii_case(&self, other: &Host<'_>) -> bool {
73        match (self, other) {
74            (Host::Name(a), Host::Name(b)) | (Host::Future(a), Host::Future(b)) => {
75                a.eq_ignore_ascii_case(b)
76            }
77            (Host::V4(a), Host::V4(b)) => a == b,
78            (Host::V6(a), Host::V6(b)) => a == b,
79            _ => false,
80        }
81    }
82
83    /// Whether this is the empty name of an empty `Host` field.
84    ///
85    /// # Examples
86    ///
87    /// ```
88    /// use parse_net::parse_host_port;
89    ///
90    /// assert!(parse_host_port(b"")?.host.is_empty());
91    /// assert!(!parse_host_port(b"example.com")?.host.is_empty());
92    /// # Ok::<(), parse_net::ParseError>(())
93    /// ```
94    #[must_use]
95    pub fn is_empty(&self) -> bool {
96        matches!(self, Host::Name(n) if n.is_empty())
97    }
98
99    /// Writes the host: names as given, IPv6 bracketed in its RFC 5952 form.
100    ///
101    /// # Errors
102    ///
103    /// [`WriteError::Full`]; nothing is written when the sink reports its
104    /// room.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// use parse_net::{host::parse_host, SliceSink};
110    ///
111    /// let mut buf = [0u8; 64];
112    /// let mut out = SliceSink::new(&mut buf);
113    /// parse_host(b"[2001:DB8:0:0:0:0:0:1]")?.write_to(&mut out).expect("room");
114    /// assert_eq!(out.written(), b"[2001:db8::1]");
115    /// # Ok::<(), parse_net::ParseError>(())
116    /// ```
117    pub fn write_to(&self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
118        let mut count = CountSink::new();
119        self.put(&mut count)?;
120        reserve(out, count.count())?;
121        self.put(out)
122    }
123
124    fn put(&self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
125        match self {
126            Host::Name(n) => {
127                check_reg_name(n)?;
128                out.put(n)?;
129            }
130            Host::V4(a) => write!(SinkWriter(out), "{a}").map_err(|_| WriteError::Full)?,
131            Host::V6(a) => write!(SinkWriter(out), "[{a}]").map_err(|_| WriteError::Full)?,
132            Host::Future(f) => {
133                if ip_literal(f).is_err() {
134                    return Err(WriteError::forbidden(0));
135                }
136                out.put(b"[")?;
137                out.put(f)?;
138                out.put(b"]")?;
139            }
140        }
141        Ok(())
142    }
143}
144
145/// Refuses what no parsed name holds: the fields of [`Host`] are public, and
146/// a writer must not carry CR, LF, or any other byte outside a reg-name.
147fn check_reg_name(n: &[u8]) -> core::result::Result<(), WriteError> {
148    let mut i = 0usize;
149    while let Some(&b) = n.get(i) {
150        match (b, n.get(i + 1), n.get(i + 2)) {
151            (b'%', Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
152            _ if REG_NAME.contains(b) => i += 1,
153            _ => return Err(WriteError::forbidden(i)),
154        }
155    }
156    Ok(())
157}
158
159/// Lets `core::fmt` write into a [`Sink`].
160pub(crate) struct SinkWriter<'s, S: Sink>(pub(crate) &'s mut S);
161
162impl<S: Sink> fmt::Write for SinkWriter<'_, S> {
163    fn write_str(&mut self, s: &str) -> fmt::Result {
164        self.0.put(s.as_bytes()).map_err(|_| fmt::Error)
165    }
166}
167
168/// A host and an optional port.
169///
170/// # Examples
171///
172/// ```
173/// use parse_net::{parse_host_port, Host};
174///
175/// let hp = parse_host_port(b"example.com:")?;
176/// assert_eq!((hp.host, hp.port, hp.empty_port), (Host::Name(b"example.com"), None, true));
177/// assert_eq!(parse_host_port(b"example.com:8080")?.port, Some(8080));
178/// # Ok::<(), parse_net::ParseError>(())
179/// ```
180#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
181pub struct HostPort<'a> {
182    /// The host.
183    pub host: Host<'a>,
184    /// The port, when digits followed the `:`.
185    pub port: Option<u16>,
186    /// Whether a `:` with no digits followed the host (`example.com:`),
187    /// which RFC 3986 allows (`port = *DIGIT`).
188    pub empty_port: bool,
189}
190
191impl HostPort<'_> {
192    /// Writes the host, then `:port` when there is a port.
193    ///
194    /// # Errors
195    ///
196    /// [`WriteError::Full`]; nothing is written when the sink reports its
197    /// room.
198    ///
199    /// # Examples
200    ///
201    /// ```
202    /// use parse_net::{parse_host_port, SliceSink};
203    ///
204    /// let mut buf = [0u8; 64];
205    /// let mut out = SliceSink::new(&mut buf);
206    /// parse_host_port(b"[::1]:8080")?.write_to(&mut out).expect("room");
207    /// assert_eq!(out.written(), b"[::1]:8080");
208    /// # Ok::<(), parse_net::ParseError>(())
209    /// ```
210    pub fn write_to(&self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
211        let mut count = CountSink::new();
212        self.put(&mut count)?;
213        reserve(out, count.count())?;
214        self.put(out)
215    }
216
217    fn put(&self, out: &mut impl Sink) -> core::result::Result<(), WriteError> {
218        self.host.put(out)?;
219        if let Some(port) = self.port {
220            out.put(b":")?;
221            crate::write::write_decimal_u64(u64::from(port), out)?;
222        }
223        Ok(())
224    }
225}
226
227/// Parses a `Host` value: `uri-host [ ":" port ]`.
228///
229/// A registered name (`unreserved / pct-encoded / sub-delims`), a
230/// dotted-decimal IPv4 address with no leading zeros, a bracketed IPv6
231/// address in any RFC 4291 text form, or a bracketed IPvFuture literal,
232/// optionally followed by `:` and a port up to 65535. An empty port is
233/// accepted with `empty_port` set. An empty value is accepted as an empty
234/// name (RFC 9112 section 3.2: the target has no authority); the caller
235/// decides what that means.
236///
237/// A name made only of digits and dots, or whose last label is a number
238/// (`127.1`, `0x7f.1`, `example.123`), must be a canonical IPv4 address:
239/// RFC 3986 reads such hosts as names and the WHATWG URL standard as IPv4
240/// addresses, and refusing them removes the disagreement.
241///
242/// # Errors
243///
244/// [`ErrorKind::InvalidHost`] for userinfo (any `@`, checked first), a byte
245/// outside the name class, a port with no host, or unbracketed IPv6;
246/// [`ErrorKind::InvalidAddress`] for a malformed IP literal or a numeric
247/// name that is not canonical IPv4; [`ErrorKind::InvalidPort`];
248/// [`ErrorKind::InvalidPercentEncoding`]; a control kind for a control byte.
249///
250/// # Examples
251///
252/// ```
253/// use parse_net::{parse_host_port, ErrorKind};
254///
255/// let hp = parse_host_port(b"example.com:8443")?;
256/// assert_eq!(hp.port, Some(8443));
257/// assert_eq!(parse_host_port(b"a:65536").unwrap_err().kind(), ErrorKind::InvalidPort);
258/// # Ok::<(), parse_net::ParseError>(())
259/// ```
260pub fn parse_host_port(value: &[u8]) -> Result<HostPort<'_>> {
261    let lead = skip_ows(value);
262    host_port(trim_ows(value)).map_err(|e| e.shifted(lead))
263}
264
265/// Parses `host [ ":" port ]` exactly as given, for a URI authority or a
266/// request target: unlike [`parse_host_port`], no OWS is stripped, so any
267/// whitespace is refused.
268///
269/// Use this wherever the bytes are not a field value: an authority-form or
270/// absolute-form request target, a URL, a `Forwarded` node. Stripping
271/// whitespace there would accept `CONNECT  example.com:443` (two spaces),
272/// which other parsers refuse.
273///
274/// # Errors
275///
276/// As [`parse_host_port`]; whitespace is refused like any other byte outside
277/// the grammar ([`ErrorKind::InvalidHost`] in the host, [`ErrorKind::InvalidPort`]
278/// after a port).
279///
280/// # Examples
281///
282/// ```
283/// use parse_net::{host::parse_uri_host_port, parse_host_port};
284///
285/// assert!(parse_host_port(b" example.com:443").is_ok());
286/// assert!(parse_uri_host_port(b" example.com:443").is_err());
287/// assert_eq!(parse_uri_host_port(b"example.com:443")?.port, Some(443));
288/// # Ok::<(), parse_net::ParseError>(())
289/// ```
290pub fn parse_uri_host_port(value: &[u8]) -> Result<HostPort<'_>> {
291    host_port(value)
292}
293
294/// Parses a host with no port.
295///
296/// # Errors
297///
298/// As [`parse_host_port`], and [`ErrorKind::InvalidHost`] for a port.
299///
300/// # Examples
301///
302/// ```
303/// use parse_net::{host::parse_host, Host};
304///
305/// assert_eq!(parse_host(b"192.0.2.1")?, Host::V4([192, 0, 2, 1].into()));
306/// assert!(parse_host(b"a.example:80").is_err());
307/// # Ok::<(), parse_net::ParseError>(())
308/// ```
309pub fn parse_host(value: &[u8]) -> Result<Host<'_>> {
310    let hp = parse_host_port(value)?;
311    if hp.port.is_some() || hp.empty_port {
312        let at = value.iter().rposition(|&b| b == b':').unwrap_or(0);
313        return err(ErrorKind::InvalidHost, at);
314    }
315    Ok(hp.host)
316}
317
318pub(crate) fn host_port(v: &[u8]) -> Result<HostPort<'_>> {
319    host_port_inner(v).map_err(|e| userinfo_first(v, e))
320}
321
322/// Userinfo is the attack (`a.com:80@b.com`): whatever else is wrong with the
323/// value, an `@` anywhere is reported as `InvalidHost` at the `@`.
324#[cold]
325fn userinfo_first(v: &[u8], e: crate::ParseError) -> crate::ParseError {
326    match find_byte(b'@', v) {
327        Some(at) => crate::ParseError::new(ErrorKind::InvalidHost, at),
328        None => e,
329    }
330}
331
332fn host_port_inner(v: &[u8]) -> Result<HostPort<'_>> {
333    let (host, rest, rest_at) = if v.first() == Some(&b'[') {
334        let Some(close) = find_byte(b']', v) else {
335            return err(ErrorKind::InvalidAddress, v.len());
336        };
337        let inner = v.get(1..close).unwrap_or(&[]);
338        (
339            ip_literal(inner)?,
340            v.get(close + 1..).unwrap_or(&[]),
341            close + 1,
342        )
343    } else {
344        let (host, colon) = reg_name_or_ipv4(v)?;
345        let rest = v.get(colon..).unwrap_or(&[]);
346        if colon == 0 && !rest.is_empty() {
347            return err(ErrorKind::InvalidHost, 0);
348        }
349        (host, rest, colon)
350    };
351    let (port, empty_port) = match rest.split_first() {
352        None => (None, false),
353        Some((b':', digits)) => port(digits).map_err(|e| e.shifted(rest_at + 1))?,
354        Some(_) => return err(ErrorKind::InvalidHost, rest_at),
355    };
356    Ok(HostPort {
357        host,
358        port,
359        empty_port,
360    })
361}
362
363fn port(digits: &[u8]) -> Result<(Option<u16>, bool)> {
364    if digits.is_empty() {
365        return Ok((None, true));
366    }
367    let mut n: u32 = 0;
368    for (i, &d) in digits.iter().enumerate() {
369        if !d.is_ascii_digit() {
370            // A second ":" means unbracketed IPv6.
371            let kind = if d == b':' {
372                ErrorKind::InvalidHost
373            } else {
374                ErrorKind::InvalidPort
375            };
376            return err(kind, i);
377        }
378        n = n * 10 + u32::from(d - b'0');
379        if n > 65_535 {
380            return err(ErrorKind::InvalidPort, i);
381        }
382    }
383    // `n` is at most 65535.
384    Ok((u16::try_from(n).ok(), false))
385}
386
387fn ip_literal(inner: &[u8]) -> Result<Host<'_>> {
388    // Offsets inside the brackets are reported from the "[".
389    match inner.first() {
390        Some(b'v' | b'V') => {
391            // IPvFuture = "v" 1*HEXDIG "." 1*( unreserved / sub-delims / ":" )
392            let rest = inner.get(1..).unwrap_or(&[]);
393            let hex = rest.iter().take_while(|&&b| class::is_hexdig(b)).count();
394            let after = rest.get(hex..).unwrap_or(&[]);
395            let tail_ok = match after.split_first() {
396                Some((b'.', tail)) => {
397                    !tail.is_empty()
398                        && tail.iter().all(|&b| {
399                            class::is_unreserved(b) || class::is_sub_delim(b) || b == b':'
400                        })
401                }
402                _ => false,
403            };
404            if hex == 0 || !tail_ok {
405                return err(ErrorKind::InvalidAddress, 1);
406            }
407            Ok(Host::Future(inner))
408        }
409        _ => {
410            let text = core::str::from_utf8(inner)
411                .map_err(|_| crate::ParseError::new(ErrorKind::InvalidAddress, 1))?;
412            match Ipv6Addr::from_str(text) {
413                Ok(a) => Ok(Host::V6(a)),
414                Err(_) => err(ErrorKind::InvalidAddress, 1),
415            }
416        }
417    }
418}
419
420/// Whether `label` is a number in the WHATWG sense: decimal digits, or `0x`
421/// followed by hex digits.
422fn is_numeric_label(label: &[u8]) -> bool {
423    match label {
424        [] => false,
425        [b'0', b'x' | b'X', hex @ ..] => hex.iter().all(|&b| class::is_hexdig(b)),
426        _ => label.iter().all(u8::is_ascii_digit),
427    }
428}
429
430/// Which number a label of decoded bytes could be, as far as it has been read.
431#[derive(Clone, Copy)]
432struct NumericLabel {
433    len: usize,
434    decimal: bool,
435    hex: bool,
436}
437
438impl NumericLabel {
439    const EMPTY: Self = Self {
440        len: 0,
441        decimal: true,
442        hex: true,
443    };
444
445    fn push(&mut self, b: u8) {
446        self.decimal &= b.is_ascii_digit();
447        self.hex &= match self.len {
448            0 => b == b'0',
449            1 => matches!(b, b'x' | b'X'),
450            _ => class::is_hexdig(b),
451        };
452        self.len += 1;
453    }
454
455    const fn is_number(&self) -> bool {
456        self.decimal && self.len > 0 || self.hex && self.len >= 2
457    }
458}
459
460/// [`is_numeric_label`] and the digits-and-dots rule over the percent-decoded
461/// bytes of a validated reg-name: `%2e` separates labels, as it does for a
462/// browser. No allocation: one pass with the last two labels' state.
463fn decoded_is_numeric(name: &[u8]) -> bool {
464    let (mut label, mut previous) = (NumericLabel::EMPTY, NumericLabel::EMPTY);
465    let (mut digits_dots, mut i) = (true, 0usize);
466    while let Some(&b) = name.get(i) {
467        let byte = match (b, name.get(i + 1), name.get(i + 2)) {
468            (b'%', Some(&h), Some(&l)) => {
469                i += 2;
470                match (class::hex_value(h), class::hex_value(l)) {
471                    (Some(h), Some(l)) => (h << 4) | l,
472                    _ => b,
473                }
474            }
475            _ => b,
476        };
477        i += 1;
478        digits_dots &= byte.is_ascii_digit() || byte == b'.';
479        if byte == b'.' {
480            previous = label;
481            label = NumericLabel::EMPTY;
482        } else {
483            label.push(byte);
484        }
485    }
486    digits_dots
487        || if label.len == 0 {
488            previous.is_number()
489        } else {
490            label.is_number()
491        }
492}
493
494/// `unreserved / sub-delims`: the reg-name bytes other than `pct-encoded`.
495const REG_NAME: class::Class = class::Class::UNRESERVED.union(class::Class::SUB_DELIM);
496
497/// Reads the name at the start of `v` up to a `:` or the end, in one pass:
498/// validates it as a reg-name and notes whether it could be read as IPv4.
499/// Returns the host and the offset where the name ended.
500fn reg_name_or_ipv4(v: &[u8]) -> Result<(Host<'_>, usize)> {
501    let mut i = 0usize;
502    let mut digits_dots = true;
503    let mut label_start = 0usize;
504    let mut prev_label_start = 0usize;
505    let mut escaped = false;
506    while let Some(&b) = v.get(i) {
507        if REG_NAME.contains(b) {
508            if b == b'.' {
509                prev_label_start = label_start;
510                label_start = i + 1;
511            } else if !b.is_ascii_digit() {
512                digits_dots = false;
513            }
514            i += 1;
515        } else if b == b':' {
516            break;
517        } else if b == b'%' {
518            match (v.get(i + 1), v.get(i + 2)) {
519                (Some(&h), Some(&l)) if class::is_hexdig(h) && class::is_hexdig(l) => i += 3,
520                _ => return err(ErrorKind::InvalidPercentEncoding, i),
521            }
522            digits_dots = false;
523            escaped = true;
524        } else if crate::scan::is_control(b) {
525            return err(control_kind(b), i);
526        } else {
527            return err(ErrorKind::InvalidHost, i);
528        }
529    }
530    let name = v.get(..i).unwrap_or(&[]);
531    // A browser decodes first, so `127.0.0.%31` is an address there, and
532    // only the canonical plain form may be one here (D-040).
533    if escaped && decoded_is_numeric(name) {
534        return err(ErrorKind::InvalidAddress, 0);
535    }
536    // The last label, ignoring one trailing dot.
537    let last = if name.last() == Some(&b'.') {
538        name.get(prev_label_start..i - 1).unwrap_or(&[])
539    } else {
540        name.get(label_start..).unwrap_or(&[])
541    };
542    if (digits_dots && !name.is_empty()) || is_numeric_label(last) {
543        return match ipv4(name) {
544            Some(a) => Ok((Host::V4(a), i)),
545            None => err(ErrorKind::InvalidAddress, 0),
546        };
547    }
548    Ok((Host::Name(name), i))
549}
550
551/// Canonical dotted decimal: four parts, 0 to 255, no leading zeros.
552fn ipv4(s: &[u8]) -> Option<Ipv4Addr> {
553    let mut octets = [0u8; 4];
554    let mut parts = s.split(|&b| b == b'.');
555    for slot in &mut octets {
556        let part = parts.next()?;
557        if part.is_empty() || part.len() > 3 || (part.len() > 1 && part.first() == Some(&b'0')) {
558            return None;
559        }
560        let mut n: u16 = 0;
561        for &d in part {
562            if !d.is_ascii_digit() {
563                return None;
564            }
565            n = n * 10 + u16::from(d - b'0');
566        }
567        *slot = u8::try_from(n).ok()?;
568    }
569    if parts.next().is_some() {
570        return None;
571    }
572    Some(Ipv4Addr::from(octets))
573}
574
575#[cfg(test)]
576mod tests {
577    use super::*;
578
579    fn hp(v: &[u8]) -> Result<HostPort<'_>> {
580        parse_host_port(v)
581    }
582
583    #[test]
584    fn test_host_edge_cases() {
585        use ErrorKind::*;
586        assert!(hp(b"").unwrap().host.is_empty());
587        assert_eq!(hp(b"a").unwrap().host, Host::Name(b"a"));
588        let e = hp(b"a:").unwrap();
589        assert_eq!((e.port, e.empty_port), (None, true));
590        assert_eq!(hp(b"a:0").unwrap().port, Some(0));
591        assert_eq!(hp(b"a:65535").unwrap().port, Some(65535));
592        assert_eq!(hp(b"a:65536").unwrap_err().kind(), InvalidPort);
593        assert_eq!(hp(b"a:-1").unwrap_err().kind(), InvalidPort);
594        assert_eq!(hp(b"a:8x").unwrap_err().kind(), InvalidPort);
595        assert_eq!(hp(b"[::1]").unwrap().host, Host::V6(Ipv6Addr::LOCALHOST));
596        assert_eq!(hp(b"[::1]:80").unwrap().port, Some(80));
597        assert_eq!(hp(b"[::1").unwrap_err().kind(), InvalidAddress);
598        assert_eq!(hp(b"[zz]").unwrap_err().kind(), InvalidAddress);
599        assert_eq!(hp(b"[]").unwrap_err().kind(), InvalidAddress);
600        assert_eq!(hp(b"::1").unwrap_err().kind(), InvalidHost);
601        assert_eq!(hp(b"a:1:2").unwrap_err().kind(), InvalidHost);
602        assert_eq!(hp(b"[v1.x]").unwrap().host, Host::Future(b"v1.x"));
603        assert_eq!(hp(b"[v.x]").unwrap_err().kind(), InvalidAddress);
604        assert_eq!(hp(b"[v1.]").unwrap_err().kind(), InvalidAddress);
605        assert_eq!(
606            hp(b"1.2.3.4").unwrap().host,
607            Host::V4(Ipv4Addr::new(1, 2, 3, 4))
608        );
609        assert_eq!(hp(b"[::1]x").unwrap_err().kind(), InvalidHost);
610        assert_eq!(hp(b"%41.com").unwrap().host, Host::Name(b"%41.com"));
611        assert_eq!(hp(b"%4.com").unwrap_err().kind(), InvalidPercentEncoding);
612        assert_eq!(hp(b"a b").unwrap_err().kind(), InvalidHost);
613        assert_eq!(hp(b"a/b").unwrap_err().kind(), InvalidHost);
614        assert_eq!(
615            hp(b"example.com.").unwrap().host,
616            Host::Name(b"example.com.")
617        );
618        assert_eq!(hp(b":80").unwrap_err().kind(), InvalidHost);
619        assert_eq!(hp(b"a\rb").unwrap_err().kind(), BareCr);
620    }
621
622    #[test]
623    fn test_host_partial_ipv4_refused() {
624        for v in [
625            &b"1.2.3"[..],
626            b"01.2.3.4",
627            b"256.0.0.1",
628            b"127.1",
629            b"0x7f.0.0.1",
630            b"1.2.3.4.",
631            b"example.123",
632            b"a.0x10",
633            b"1..2.3",
634        ] {
635            assert_eq!(
636                hp(v).unwrap_err().kind(),
637                ErrorKind::InvalidAddress,
638                "{}",
639                std::string::String::from_utf8_lossy(v)
640            );
641        }
642        assert_eq!(hp(b"123.example").unwrap().host, Host::Name(b"123.example"));
643        assert_eq!(
644            hp(b"0.0.0.0").unwrap().host,
645            Host::V4(Ipv4Addr::UNSPECIFIED)
646        );
647    }
648
649    #[test]
650    fn test_host_userinfo_refused() {
651        let e = hp(b"a.com:80@b.com").unwrap_err();
652        assert_eq!((e.kind(), e.offset()), (ErrorKind::InvalidHost, 8));
653        assert_eq!(
654            hp(b"user@b.com").unwrap_err().kind(),
655            ErrorKind::InvalidHost
656        );
657        assert_eq!(hp(b"[::1]@x").unwrap_err().kind(), ErrorKind::InvalidHost);
658    }
659
660    #[test]
661    fn test_ipv6_text_forms() {
662        for v in [
663            &b"[::]"[..],
664            b"[::1]",
665            b"[2001:db8::1]",
666            b"[2001:DB8:0:0:8:800:200C:417A]",
667            b"[::ffff:192.0.2.1]",
668            b"[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]",
669            b"[1::]",
670        ] {
671            assert!(
672                matches!(hp(v).unwrap().host, Host::V6(_)),
673                "{}",
674                std::string::String::from_utf8_lossy(v)
675            );
676        }
677        for v in [
678            &b"[1:2:3:4:5:6:7:8:9]"[..],
679            b"[12345::]",
680            b"[::1%25eth0]",
681            b"[:::]",
682            b"[1::2::3]",
683        ] {
684            assert_eq!(hp(v).unwrap_err().kind(), ErrorKind::InvalidAddress);
685        }
686    }
687
688    #[test]
689    fn test_host_compare_and_write() {
690        assert!(Host::V6(Ipv6Addr::LOCALHOST).eq_ignore_ascii_case(&hp(b"[0:0::1]").unwrap().host));
691        assert!(!Host::Name(b"a").eq_ignore_ascii_case(&Host::V4(Ipv4Addr::LOCALHOST)));
692        let mut buf = [0u8; 64];
693        let mut out = crate::SliceSink::new(&mut buf);
694        hp(b"1.2.3.4:5").unwrap().write_to(&mut out).unwrap();
695        assert_eq!(out.written(), b"1.2.3.4:5");
696        let mut small = [0u8; 3];
697        let mut out = crate::SliceSink::new(&mut small);
698        assert_eq!(
699            hp(b"[::1]").unwrap().write_to(&mut out),
700            Err(WriteError::Full)
701        );
702        assert!(out.is_empty());
703        assert_eq!(
704            parse_host(b"a:").unwrap_err().kind(),
705            ErrorKind::InvalidHost
706        );
707    }
708}