netrc_util/
raw_netrc_parser.rs

1use std::io::Read;
2
3use anyhow::Result;
4use url::Host;
5
6use crate::parser_combinator::{parse_config, NetrcConfig};
7
8/// A raw netrc entry which may contain values.
9#[derive(Debug, Default, Clone, PartialEq, Eq)]
10pub struct RawEntry {
11    pub(crate) login: Option<String>,
12    pub(crate) password: Option<String>,
13    pub(crate) account: Option<String>,
14}
15
16/// A raw netrc entry containing some values.
17impl RawEntry {
18    /// Get the login value for the entry.
19    pub fn login(&self) -> Option<&String> {
20        self.login.as_ref()
21    }
22
23    /// Get the password value for the entry.
24    pub fn password(&self) -> Option<&String> {
25        self.password.as_ref()
26    }
27
28    /// Get the account value for the entry.
29    pub fn account(&self) -> Option<&String> {
30        self.account.as_ref()
31    }
32}
33
34/// A lower-level netrc parser without any business rules related to it. Not recommended for most
35/// use-cases. For a higher-level parser use the [crate::netrc_parser::NetrcParser].
36#[derive(Debug)]
37pub struct RawNetrcParser<R: Read> {
38    buffer: R,
39    config: Option<NetrcConfig>,
40}
41
42impl<R: Read> RawNetrcParser<R> {
43    /// Create a new parser from a buffer
44    pub fn new(buffer: R) -> Self {
45        Self {
46            buffer,
47            config: None,
48        }
49    }
50
51    /// Parse the config file from the constructor and attempt to find the entry related to the
52    /// given host. Entries are not validated to contain any values and could be empty.
53    ///
54    /// # Returns
55    ///
56    /// - An error if reading the input buffer failed
57    /// - `Ok(None)` if the host was not found and no default was setup
58    /// - `Ok(Some)` if either a default was setup or the host was found
59    pub fn entry_for_host(&mut self, host: &Host) -> Result<Option<RawEntry>> {
60        let mut buf_content = String::new();
61        self.buffer.read_to_string(&mut buf_content)?;
62
63        let config = match &self.config {
64            Some(config) => config.clone(),
65            None => {
66                let config = parse_config(&buf_content);
67                self.config = Some(config.clone());
68
69                config
70            }
71        };
72
73        Ok(config
74            .entries
75            .get(host)
76            .or(config.default.as_ref())
77            .cloned())
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use std::io::BufReader;
84
85    use super::*;
86
87    const COM: &str = "example.com";
88    const ORG: &str = "example.org";
89    const UNI: &str = "xn--9ca.com";
90    const IP1: &str = "1.1.1.1";
91    const IP2: &str = "2.2.2.2.";
92
93    #[test]
94    fn parse_simple_config() {
95        const SIMPLE: &str = "
96            machine example.com
97            login user
98            password pass
99            account acc
100        ";
101        found(SIMPLE, COM, "user", "pass", "acc");
102        notfound(SIMPLE, ORG);
103        notfound(SIMPLE, UNI);
104        notfound(SIMPLE, IP1);
105    }
106
107    #[test]
108    fn parse_empty_config() {
109        const SIMPLE: &str = "
110            machine example.com
111        ";
112        found(SIMPLE, COM, None, None, None);
113        notfound(SIMPLE, ORG);
114        notfound(SIMPLE, UNI);
115        notfound(SIMPLE, IP1);
116    }
117
118    #[track_caller]
119    fn found(
120        netrc: &str,
121        host: &str,
122        login: impl Into<Option<&'static str>>,
123        password: impl Into<Option<&'static str>>,
124        account: impl Into<Option<&'static str>>,
125    ) {
126        let entry = RawNetrcParser::new(BufReader::new(netrc.as_bytes()))
127            .entry_for_host(&Host::parse(host).unwrap());
128        let entry = entry.unwrap().expect("Didn't find entry");
129
130        assert_eq!(entry.login.as_deref(), login.into());
131        assert_eq!(entry.password.as_deref(), password.into());
132        assert_eq!(entry.account.as_deref(), account.into());
133    }
134
135    #[track_caller]
136    fn notfound(netrc: &str, host: &str) {
137        let entry =
138            RawNetrcParser::new(netrc.as_bytes()).entry_for_host(&Host::parse(host).unwrap());
139
140        assert!(entry.unwrap().is_none(), "Found entry");
141    }
142}