netrc/
lib.rs

1/*!
2The `netrc` crate provides a parser for the netrc file.
3
4The `reqwest-netrc` crate adds the support of netrc to the `reqwest` crate via the `reqwest-middleware` wrapper.
5
6# Setup
7
8```text
9$ crago add rust-netrc
10```
11
12# Example
13
14```no_run
15use netrc::Netrc;
16
17// ...
18
19let nrc = Netrc::new().unwrap();
20
21// ...
22println!(
23    "login = {}\naccount = {}\npassword = {}",
24    nrc.hosts["my.host"].login,
25    nrc.hosts["my.host"].account,
26    nrc.hosts["my.host"].password,
27);
28```
29
30*/
31
32pub use netrc::{Authenticator, Netrc};
33use std::fs;
34use std::io;
35use std::io::ErrorKind;
36#[cfg(windows)]
37use std::iter::repeat;
38use std::path::{Path, PathBuf};
39use std::result;
40
41mod lex;
42mod netrc;
43
44pub type Result<T> = result::Result<T, Error>;
45
46/// An error that can occur when processing a Netrc file.
47#[derive(thiserror::Error, Debug)]
48pub enum Error {
49    /// Wrap `std::io::Error` when we try to open the netrc file.
50    #[error("I/O error: {0}")]
51    Io(#[from] std::io::Error),
52
53    /// Parsing error.
54    #[error("{parser} in the file '{filename}'")]
55    Parsing {
56        parser: netrc::ParsingError,
57        filename: String,
58    },
59}
60
61impl Netrc {
62    /// Create a new `Netrc` object.
63    ///
64    /// Look up the `NETRC` environment variable if it is defined else that the
65    /// default `~/.netrc` file.
66    pub fn new() -> Result<Self> {
67        Self::get_file()
68            .ok_or(Error::Io(io::Error::new(
69                ErrorKind::NotFound,
70                "no netrc file found",
71            )))
72            .and_then(|f| Netrc::from_file(f.as_path()))
73    }
74
75    /// Create a new `Netrc` object from a file.
76    pub fn from_file(file: &Path) -> Result<Self> {
77        String::from_utf8_lossy(&fs::read(file)?)
78            .parse()
79            .map_err(|e| Error::Parsing {
80                parser: e,
81                filename: file.display().to_string(),
82            })
83    }
84
85    /// Search a netrc file.
86    ///
87    /// Look up the `NETRC` environment variable if it is defined else use the .netrc (or _netrc
88    /// file on windows) in the user's home directory.
89    pub fn get_file() -> Option<PathBuf> {
90        let env_var = std::env::var("NETRC")
91            .map(PathBuf::from)
92            .map(|f| shellexpand::path::tilde(&f).into_owned());
93
94        #[cfg(windows)]
95        let default = std::env::var("USERPROFILE")
96            .into_iter()
97            .flat_map(|home| repeat(home).zip([".netrc", "_netrc"]))
98            .map(|(home, file)| PathBuf::from(home).join(file));
99
100        #[cfg(not(windows))]
101        let default = std::env::var("HOME").map(|home| PathBuf::from(home).join(".netrc"));
102
103        env_var.into_iter().chain(default).find(|f| f.exists())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    const CONTENT: &str = "\
112machine cocolog-nifty.com
113login jmarten0
114password cC2&yt7OT
115
116machine wired.com
117login mstanlack1
118password gH4={wx=>VixU
119
120machine joomla.org
121login mbutterley2
122password hY5>yKqU&$vq&0
123";
124
125    fn create_netrc_file() -> PathBuf {
126        let dest = std::env::temp_dir().join("mynetrc");
127        if !dest.exists() {
128            std::fs::write(&dest, CONTENT).unwrap();
129        }
130        dest
131    }
132
133    fn check_nrc(nrc: &Netrc) {
134        assert_eq!(nrc.hosts.len(), 3);
135        assert_eq!(
136            nrc.hosts["cocolog-nifty.com"],
137            Authenticator::new("jmarten0", "", "cC2&yt7OT")
138        );
139        assert_eq!(
140            nrc.hosts["wired.com"],
141            Authenticator::new("mstanlack1", "", "gH4={wx=>VixU")
142        );
143        assert_eq!(
144            nrc.hosts["joomla.org"],
145            Authenticator::new("mbutterley2", "", "hY5>yKqU&$vq&0")
146        );
147    }
148
149    #[test]
150    fn test_new_env() {
151        let fi = create_netrc_file();
152        std::env::set_var("NETRC", fi);
153        let nrc = Netrc::new().unwrap();
154        check_nrc(&nrc);
155    }
156
157    #[test]
158    fn test_new_default() {}
159
160    #[test]
161    fn test_from_file_failed() {
162        assert_eq!(
163            Netrc::from_file(Path::new("/netrc/file/not/exists/on/no/netrc"))
164                .unwrap_err()
165                .to_string(),
166            "I/O error: No such file or directory (os error 2)"
167        );
168    }
169
170    #[test]
171    fn test_from_file() {
172        let fi = create_netrc_file();
173        let nrc = Netrc::from_file(fi.as_path()).unwrap();
174        check_nrc(&nrc);
175    }
176}