Skip to main content

wp_primitives/net/
mod.rs

1use std::net::{IpAddr, Ipv4Addr};
2
3use winnow::{
4    ModalResult as WResult, Parser,
5    ascii::{Caseless, multispace0},
6    combinator::{fail, peek, repeat},
7    error::ContextError,
8    token::any,
9};
10
11use crate::symbol::ctx_desc;
12use crate::utils::peek_one;
13
14#[derive(PartialEq)]
15enum AddrKind {
16    Ipv4,
17    Ipv6,
18}
19
20fn head_ip<'a>(last: &mut Option<AddrKind>) -> impl Parser<&'a str, char, ContextError> + '_ {
21    move |input: &mut &'a str| {
22        let initial = (peek(any)).parse_next(input)?;
23        match initial {
24            '0'..='9' => any.parse_next(input),
25            'A'..='F' | 'a'..='f' => {
26                *last = Some(AddrKind::Ipv6);
27                any.parse_next(input)
28            }
29            '.' => {
30                if *last == Some(AddrKind::Ipv6) {
31                    fail.parse_next(input)
32                } else {
33                    *last = Some(AddrKind::Ipv4);
34                    any.parse_next(input)
35                }
36            }
37            ':' => {
38                if *last == Some(AddrKind::Ipv4) {
39                    fail.parse_next(input)
40                } else {
41                    *last = Some(AddrKind::Ipv6);
42                    any.parse_next(input)
43                }
44            }
45            _ => fail.parse_next(input),
46        }
47    }
48}
49
50pub fn ip_v4(input: &mut &str) -> WResult<IpAddr> {
51    let mut last_kind = None;
52    // Build the candidate ip string, then parse using std::net::IpAddr::from_str.
53    // Avoids relying on `try_map` error conversion semantics across winnow versions.
54    let ip_str = match repeat(1.., head_ip(&mut last_kind))
55        .fold(String::new, |mut acc, c| {
56            acc.push(c);
57            acc
58        })
59        .parse_next(input)
60    {
61        Ok(s) => s,
62        Err(_e) => return fail.context(ctx_desc("<ipv4>")).parse_next(input),
63    };
64    match ip_str.parse::<IpAddr>() {
65        Ok(ip) => Ok(ip),
66        Err(_e) => fail.context(ctx_desc("<ipv4>")).parse_next(input),
67    }
68}
69pub fn ip(input: &mut &str) -> WResult<IpAddr> {
70    multispace0.parse_next(input)?;
71
72    let str = peek_one.parse_next(input);
73    if let Ok(s) = str {
74        let addr = if s == "l" {
75            Caseless("localhost")
76                .map(|_| IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)))
77                .context(ctx_desc("<localhost>"))
78                .parse_next(input)?
79        } else {
80            ip_v4.context(ctx_desc("<ipv4>")).parse_next(input)?
81        };
82        Ok(addr)
83    } else {
84        fail.context(ctx_desc("ip error")).parse_next(input)
85    }
86}