Coverage Report

Created: 2025-12-28 06:31

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/resolv-conf-0.7.0/src/grammar.rs
Line
Count
Source
1
use std::net::{Ipv4Addr, Ipv6Addr};
2
use std::str::{Utf8Error, from_utf8};
3
4
use {AddrParseError, Config, Network, Lookup, Family};
5
6
quick_error!{
7
    /// Error while parsing resolv.conf file
8
    #[derive(Debug)]
9
    pub enum ParseError {
10
        /// Error that may be returned when the string to parse contains invalid UTF-8 sequences
11
        InvalidUtf8(line: usize, err: Utf8Error) {
12
            display("bad unicode at line {}: {}", line, err)
13
            cause(err)
14
        }
15
        /// Error returned a value for a given directive is invalid.
16
        /// This can also happen when the value is missing, if the directive requires a value.
17
        InvalidValue(line: usize) {
18
            display("directive at line {} is improperly formatted \
19
                or contains invalid value", line)
20
        }
21
        /// Error returned when a value for a given option is invalid.
22
        /// This can also happen when the value is missing, if the option requires a value.
23
        InvalidOptionValue(line: usize) {
24
            display("directive options at line {} contains invalid \
25
                value of some option", line)
26
        }
27
        /// Error returned when a invalid option is found.
28
        InvalidOption(line: usize) {
29
            display("option at line {} is not recognized", line)
30
        }
31
        /// Error returned when a invalid directive is found.
32
        InvalidDirective(line: usize) {
33
            display("directive at line {} is not recognized", line)
34
        }
35
        /// Error returned when a value cannot be parsed an an IP address.
36
        InvalidIp(line: usize, err: AddrParseError) {
37
            display("directive at line {} contains invalid IP: {}", line, err)
38
        }
39
        /// Error returned when there is extra data at the end of a line.
40
        ExtraData(line: usize) {
41
            display("extra data at the end of the line {}", line)
42
        }
43
    }
44
}
45
46
0
fn ip_v4_netw(val: &str) -> Result<Network, AddrParseError> {
47
0
    let mut pair = val.splitn(2, '/');
48
0
    let ip: Ipv4Addr = pair.next().unwrap().parse()?;
49
0
    if ip.is_unspecified() {
50
0
        return Err(AddrParseError);
51
0
    }
52
0
    if let Some(mask) = pair.next() {
53
0
        let mask = mask.parse()?;
54
        // make sure this is a valid mask
55
0
        let value: u32 = ip.octets().iter().fold(0, |acc, &x| acc + u32::from(x));
56
0
        if value == 0 || (value & !value != 0) {
57
0
            Err(AddrParseError)
58
        } else {
59
0
            Ok(Network::V4(ip, mask))
60
        }
61
    } else {
62
        // We have to "guess" the mask.
63
        //
64
        // FIXME(@little-dude) right now, we look at the number or bytes that are 0, but maybe we
65
        // should use the number of bits that are 0.
66
        //
67
        // In other words, with this implementation, the mask of `128.192.0.0` will be
68
        // `255.255.0.0` (a.k.a `/16`). But we could also consider that the mask is `/10` (a.k.a
69
        // `255.63.0.0`).
70
        //
71
        // My only source on topic is the "DNS and Bind" book which suggests using bytes, not bits.
72
0
        let octets = ip.octets();
73
0
        let mask = if octets[3] == 0 {
74
0
            if octets[2] == 0 {
75
0
                if octets[1] == 0 {
76
0
                    Ipv4Addr::new(255, 0, 0, 0)
77
                } else {
78
0
                    Ipv4Addr::new(255, 255, 0, 0)
79
                }
80
            } else {
81
0
                Ipv4Addr::new(255, 255, 255, 0)
82
            }
83
        } else {
84
0
            Ipv4Addr::new(255, 255, 255, 255)
85
        };
86
0
        Ok(Network::V4(ip, mask))
87
    }
88
0
}
89
90
0
fn ip_v6_netw(val: &str) -> Result<Network, AddrParseError> {
91
0
    let mut pair = val.splitn(2, '/');
92
0
    let ip = pair.next().unwrap().parse()?;
93
0
    if let Some(msk) = pair.next() {
94
        // FIXME: validate the mask
95
0
        Ok(Network::V6(ip, msk.parse()?))
96
    } else {
97
        // FIXME: "guess" an appropriate mask for the IP
98
0
        Ok(Network::V6(
99
0
            ip,
100
0
            Ipv6Addr::new(
101
0
                65_535,
102
0
                65_535,
103
0
                65_535,
104
0
                65_535,
105
0
                65_535,
106
0
                65_535,
107
0
                65_535,
108
0
                65_535,
109
0
            ),
110
0
        ))
111
    }
112
0
}
113
114
0
pub(crate) fn parse(bytes: &[u8]) -> Result<Config, ParseError> {
115
    use self::ParseError::*;
116
0
    let mut cfg = Config::new();
117
0
    'lines: for (lineno, line) in bytes.split(|&x| x == b'\n').enumerate() {
118
0
        for &c in line.iter() {
119
0
            if c != b'\t' && c != b' ' {
120
0
                if c == b';' || c == b'#' {
121
0
                    continue 'lines;
122
                } else {
123
0
                    break;
124
                }
125
0
            }
126
        }
127
        // All that dances above to allow invalid utf-8 inside the comments
128
0
        let mut words = from_utf8(line)
129
0
            .map_err(|e| InvalidUtf8(lineno, e))?
130
            // ignore everything after ';' or '#'
131
0
            .split(|c| c == ';' || c == '#')
132
0
            .next()
133
0
            .ok_or_else(|| InvalidValue(lineno))?
134
0
            .split_whitespace();
135
0
        let keyword = match words.next() {
136
0
            Some(x) => x,
137
0
            None => continue,
138
        };
139
0
        match keyword {
140
0
            "nameserver" => {
141
0
                let srv = words
142
0
                    .next()
143
0
                    .ok_or_else(|| InvalidValue(lineno))
144
0
                    .map(|addr| addr.parse().map_err(|e| InvalidIp(lineno, e)))??;
145
0
                cfg.nameservers.push(srv);
146
0
                if words.next().is_some() {
147
0
                    return Err(ExtraData(lineno));
148
0
                }
149
            }
150
0
            "domain" => {
151
0
                let dom = words
152
0
                    .next()
153
0
                    .and_then(|x| x.parse().ok())
154
0
                    .ok_or_else(|| InvalidValue(lineno))?;
155
0
                cfg.set_domain(dom);
156
0
                if words.next().is_some() {
157
0
                    return Err(ExtraData(lineno));
158
0
                }
159
            }
160
0
            "search" => {
161
0
                cfg.set_search(words.map(|x| x.to_string()).collect());
162
            }
163
0
            "sortlist" => {
164
0
                cfg.sortlist.clear();
165
0
                for pair in words {
166
0
                    let netw = ip_v4_netw(pair)
167
0
                        .or_else(|_| ip_v6_netw(pair))
168
0
                        .map_err(|e| InvalidIp(lineno, e))?;
169
0
                    cfg.sortlist.push(netw);
170
                }
171
            }
172
0
            "options" => {
173
0
                for pair in words {
174
0
                    let mut iter = pair.splitn(2, ':');
175
0
                    let key = iter.next().unwrap();
176
0
                    let value = iter.next();
177
0
                    if iter.next().is_some() {
178
0
                        return Err(ExtraData(lineno));
179
0
                    }
180
0
                    match (key, value) {
181
                        // TODO(tailhook) ensure that values are None?
182
0
                        ("debug", _) => cfg.debug = true,
183
0
                        ("ndots", Some(x)) => {
184
0
                            cfg.ndots = x.parse().map_err(|_| InvalidOptionValue(lineno))?
185
                        }
186
0
                        ("timeout", Some(x)) => {
187
0
                            cfg.timeout = x.parse().map_err(|_| InvalidOptionValue(lineno))?
188
                        }
189
0
                        ("attempts", Some(x)) => {
190
0
                            cfg.attempts = x.parse().map_err(|_| InvalidOptionValue(lineno))?
191
                        }
192
0
                        ("rotate", _) => cfg.rotate = true,
193
0
                        ("no-check-names", _) => cfg.no_check_names = true,
194
0
                        ("inet6", _) => cfg.inet6 = true,
195
0
                        ("ip6-bytestring", _) => cfg.ip6_bytestring = true,
196
0
                        ("ip6-dotint", _) => cfg.ip6_dotint = true,
197
0
                        ("no-ip6-dotint", _) => cfg.ip6_dotint = false,
198
0
                        ("edns0", _) => cfg.edns0 = true,
199
0
                        ("single-request", _) => cfg.single_request = true,
200
0
                        ("single-request-reopen", _) => cfg.single_request_reopen = true,
201
0
                        ("no-reload", _) => cfg.no_reload = true,
202
0
                        ("trust-ad", _) => cfg.trust_ad = true,
203
0
                        ("no-tld-query", _) => cfg.no_tld_query = true,
204
0
                        ("use-vc", _) => cfg.use_vc = true,
205
0
                        _ => return Err(InvalidOption(lineno)),
206
                    }
207
                }
208
            }
209
0
            "lookup" => {
210
0
                for word in words {
211
0
                    match word {
212
0
                        "file" => cfg.lookup.push(Lookup::File),
213
0
                        "bind" => cfg.lookup.push(Lookup::Bind),
214
0
                        extra => cfg.lookup.push(Lookup::Extra(extra.to_string())),
215
                    }
216
                }
217
            }
218
0
            "family" => {
219
0
                for word in words {
220
0
                    match word {
221
0
                        "inet4" => cfg.family.push(Family::Inet4),
222
0
                        "inet6" => cfg.family.push(Family::Inet6),
223
0
                        _ => return Err(InvalidValue(lineno)),
224
                    }
225
                }
226
            }
227
0
            _ => return Err(InvalidDirective(lineno)),
228
        }
229
    }
230
0
    Ok(cfg)
231
0
}