Skip to main content

apt_auth_config/
lib.rs

1use std::{
2    fs::read_dir,
3    io::{self},
4    path::{Path, PathBuf},
5};
6
7pub use netrc::Authenticator;
8use netrc::Netrc;
9use thiserror::Error;
10use url::Url;
11
12#[derive(Debug, Error)]
13pub enum AuthConfigError {
14    #[error("Failed to read dir: {path}")]
15    ReadDir { path: PathBuf, err: io::Error },
16    #[error("Failed to read dir entry")]
17    DirEntry(std::io::Error),
18    #[error("Failed to open file: {path}")]
19    OpenFile { path: PathBuf, err: io::Error },
20    #[error(transparent)]
21    ParseError(#[from] netrc::Error),
22}
23
24#[derive(Debug, Eq)]
25pub struct AuthUrl {
26    schema: Option<String>,
27    host_and_path: String,
28}
29
30impl AuthUrl {
31    fn drop_suffix(&self) -> &str {
32        let mut res = self.host_and_path.as_str();
33
34        while let Some(x) = res.strip_suffix('/') {
35            res = x;
36        }
37
38        res
39    }
40}
41
42impl From<&str> for AuthUrl {
43    fn from(value: &str) -> Self {
44        if let Ok(url) = Url::parse(value) {
45            AuthUrl {
46                schema: Some(url.scheme().to_string()),
47                host_and_path: {
48                    let mut s = String::new();
49                    if let Some(host) = url.host_str() {
50                        s.push_str(host);
51                    }
52                    s.push_str(url.path());
53                    s
54                },
55            }
56        } else {
57            AuthUrl {
58                schema: None,
59                host_and_path: value.to_string(),
60            }
61        }
62    }
63}
64
65impl From<&Url> for AuthUrl {
66    fn from(value: &Url) -> Self {
67        let mut host_and_path = String::new();
68        let schema = value.scheme().to_string();
69
70        if let Some(host) = value.host_str() {
71            host_and_path.push_str(host);
72        }
73
74        host_and_path.push_str(value.path());
75
76        AuthUrl {
77            schema: Some(schema),
78            host_and_path,
79        }
80    }
81}
82
83impl PartialEq for AuthUrl {
84    fn eq(&self, other: &Self) -> bool {
85        if let Some((a, b)) = self.schema.as_ref().zip(other.schema.as_ref()) {
86            return a == b && self.drop_suffix() == other.drop_suffix();
87        }
88
89        self.drop_suffix() == other.drop_suffix()
90    }
91}
92
93#[derive(Debug)]
94pub struct AuthConfig(pub Vec<(AuthUrl, Authenticator)>);
95
96impl AuthConfig {
97    /// Read system auth.conf.d config (/etc/apt/auth.conf.d)
98    pub fn system(sysroot: impl AsRef<Path>) -> Result<Self, AuthConfigError> {
99        Self::from_path(sysroot.as_ref().join("etc/apt"))
100    }
101
102    pub fn from_path(p: impl AsRef<Path>) -> Result<Self, AuthConfigError> {
103        let mut v = vec![];
104
105        let auth_conf = p.as_ref().join("auth.conf");
106
107        if auth_conf.exists() {
108            let config = Netrc::from_file(&auth_conf)?;
109            v.push(config);
110        }
111
112        let auth_conf_d = p.as_ref().join("auth.conf.d");
113
114        if auth_conf_d.exists() {
115            for i in read_dir(auth_conf_d).map_err(|e| AuthConfigError::ReadDir {
116                path: p.as_ref().to_path_buf(),
117                err: e,
118            })? {
119                let i = i.map_err(AuthConfigError::DirEntry)?;
120
121                if !i.path().is_file() {
122                    continue;
123                }
124
125                let config = Netrc::from_file(&i.path())?;
126                v.push(config);
127            }
128        }
129
130        let v = v
131            .into_iter()
132            .flat_map(|x| x.hosts)
133            .map(|x| (AuthUrl::from(x.0.as_str()), x.1))
134            .collect::<Vec<_>>();
135
136        Ok(Self(v))
137    }
138
139    pub fn find(&self, url: &str) -> Option<&Authenticator> {
140        self.0
141            .iter()
142            .find(|x| AuthUrl::from(url) == x.0)
143            .map(|x| &x.1)
144    }
145}