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 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}