Skip to content

Commit

Permalink
try
Browse files Browse the repository at this point in the history
  • Loading branch information
eatradish committed Dec 31, 2024
1 parent e35d5cf commit acb0a63
Show file tree
Hide file tree
Showing 5 changed files with 245 additions and 240 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions apt-auth-config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@ license = "MIT"
[dependencies]
thiserror = "2"
tracing = "0.1"
rustix = { version = "0.38", features = ["process"] }
winnow = "0.6.20"
winnow = { version = "0.6.20" }
275 changes: 41 additions & 234 deletions apt-auth-config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
mod parser;

use std::{
fs::{self, read_dir},
io::{self},
path::{Path, PathBuf},
str::FromStr,
};

use rustix::process;
use parser::{line, multiline};
use thiserror::Error;
use tracing::debug;
use winnow::{
ascii::{line_ending, multispace0, multispace1, space1},
combinator::{alt, eof, repeat, separated_pair, terminated},
stream::AsChar,
token::{any, take_till, take_until, take_while},
PResult, Parser,
};

#[derive(Debug, Error)]
pub enum AuthConfigError {
Expand All @@ -26,19 +21,13 @@ pub enum AuthConfigError {
OpenFile { path: PathBuf, err: io::Error },
#[error("Auth config file missing entry: {0}")]
MissingEntry(&'static str),
#[error("Parse failed, unknown entry: {0}")]
#[error("Parse failed, unknown line: {0}")]
ParseError(String),
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AuthConfig {
pub inner: Vec<AuthConfigLine>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
pub struct AuthConfigLine {
pub entry: Option<AuthConfigEntry>,
pub comment: String,
pub inner: Vec<AuthConfigEntry>,
}

#[derive(Debug, PartialEq, Eq, Clone)]
Expand All @@ -48,144 +37,57 @@ pub struct AuthConfigEntry {
pub password: Box<str>,
}

impl FromStr for AuthConfigLine {
impl FromStr for AuthConfigEntry {
type Err = AuthConfigError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut host = None;
let mut login = None;
let mut password = None;
let mut comment = String::new();

let mut match_machine = false;
let mut match_login = false;
let mut match_password = false;
let mut is_comment = false;
let mut s = s;
let parse = line(&mut s).map_err(|e| AuthConfigError::ParseError(e.to_string()))?;

for i in s.split_ascii_whitespace() {
match i {
x if x.starts_with('#') => {
is_comment = true;
let comment_slice = x.strip_prefix('#').unwrap();
comment.push_str(comment_slice);
}
x if is_comment => {
comment.push_str(x);
}
"machine" => match_machine = true,
"login" => match_login = true,
"password" => match_password = true,
x if match_machine => {
match_machine = false;
host = Some(x);
}
x if match_login => {
match_login = false;
login = Some(x);
}
x if match_password => {
match_password = false;
password = Some(x);
}
x => return Err(AuthConfigError::ParseError(x.to_string())),
}
}

let entry = if host.and(login).and(password).is_none() {
None
} else if host.is_none() {
return Err(AuthConfigError::MissingEntry("machine"));
} else if login.is_none() {
return Err(AuthConfigError::MissingEntry("login"));
} else if password.is_none() {
return Err(AuthConfigError::MissingEntry("password"));
} else {
Some(AuthConfigEntry {
host: (*host.unwrap()).into(),
user: (*login.unwrap()).into(),
password: (*password.unwrap()).into(),
})
};

Ok(AuthConfigLine { entry, comment })
Ok(parse_entry_inner(parse))
}
}

#[inline]
fn multiline<'a>(input: &mut &'a str) -> PResult<Vec<Vec<(&'a str, &'a str)>>> {
repeat(1.., line).parse_next(input)
}

#[inline]
fn line<'a>(input: &mut &'a str) -> PResult<Vec<(&'a str, &'a str)>> {
terminated(repeat(1..=3, kv), garbage)
.verify(|res: &[(&str, &str)]| {
res.len() == 3
&& res.iter().any(|x| x.0 == "machine")
&& res.iter().any(|x| x.0 == "login")
&& res.iter().any(|x| x.0 == "password")
})
.parse_next(input)
}

#[inline]
fn kv<'a>(input: &mut &'a str) -> PResult<(&'a str, &'a str)> {
separated_pair(key, separator, right).parse_next(input)
}
impl FromStr for AuthConfig {
type Err = AuthConfigError;

#[inline]
fn key<'a>(input: &mut &'a str) -> PResult<&'a str> {
alt(("machine", "login", "password")).parse_next(input)
}
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut s = s;
let parse = multiline(&mut s).map_err(|e| AuthConfigError::ParseError(e.to_string()))?;
let mut res = vec![];

#[inline]
fn separator<'a>(input: &mut &'a str) -> PResult<()> {
multispace1.void().parse_next(input)
}
for r in parse {
res.push(parse_entry_inner(r));
}

#[inline]
fn right<'a>(input: &mut &'a str) -> PResult<&'a str> {
terminated(
take_till(1.., |c: char| c.is_whitespace()).verify(|s: &str| !s.starts_with('#')),
multispace0,
)
.parse_next(input)
Ok(AuthConfig { inner: res })
}
}

#[inline]
fn garbage<'a>(input: &mut &'a str) -> PResult<()> {
alt((garbage_1, garbage_2)).void().parse_next(input)
}
fn parse_entry_inner(input: Vec<(&str, &str)>) -> AuthConfigEntry {
let mut machine = None;
let mut login = None;
let mut password = None;

#[inline]
fn garbage_1<'a>(input: &mut &'a str) -> PResult<()> {
eof.void().parse_next(input)
}
for i in input {
match i.0 {
"machine" => machine = Some(i.1),
"login" => login = Some(i.1),
"password" => password = Some(i.1),
x => panic!("unexcept {x}"),
}
}

#[inline]
fn garbage_2<'a>(input: &mut &'a str) -> PResult<()> {
(
take_till(0.., |c: char| c.is_newline())
.verify(|s: &str| s.is_empty() || s.starts_with('#')),
line_ending,
)
.void()
.parse_next(input)
AuthConfigEntry {
host: machine.unwrap().into(),
user: login.unwrap().into(),
password: password.unwrap().into(),
}
}

impl AuthConfig {
/// Read system auth.conf.d config (/etc/apt/auth.conf.d)
///
/// Note that this function returns empty vector if run as a non-root user.
pub fn system(sysroot: impl AsRef<Path>) -> Result<Self, AuthConfigError> {
// 在 auth.conf.d 的使用惯例中
// 配置文件的权限一般为 600,并且所有者为 root
// 以普通用户身份下载文件时,会没有权限读取 auth 配置
// 因此,在以普通用户访问时,不读取 auth 配置
if !process::geteuid().is_root() {
return Ok(Self { inner: vec![] });
}

let p = sysroot.as_ref().join("etc/apt/auth.conf.d");
Self::from_path(p)
}
Expand Down Expand Up @@ -215,7 +117,7 @@ impl AuthConfig {
Ok(Self { inner: v })
}

pub fn find(&self, url: &str) -> Option<&AuthConfigLine> {
pub fn find(&self, url: &str) -> Option<&AuthConfigEntry> {
let url = url
.strip_prefix("http://")
.or_else(|| url.strip_prefix("https://"))
Expand All @@ -224,7 +126,7 @@ impl AuthConfig {
debug!("auth find url is: {}", url);

self.inner.iter().find_map(|x| {
let mut host = x.entry.as_ref()?.host.to_string();
let mut host = x.host.to_string();
while host.ends_with('/') {
host.pop();
}
Expand All @@ -242,7 +144,7 @@ impl AuthConfig {
})

Check warning on line 144 in apt-auth-config/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

this `.find_map` can be written more simply using `.find`

warning: this `.find_map` can be written more simply using `.find` --> apt-auth-config/src/lib.rs:128:9 | 128 | / self.inner.iter().find_map(|x| { 129 | | let mut host = x.host.to_string(); 130 | | while host.ends_with('/') { 131 | | host.pop(); ... | 143 | | } 144 | | }) | |__________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map = note: `#[warn(clippy::unnecessary_find_map)]` on by default
}

pub fn find_package_url(&self, url: &str) -> Option<&AuthConfigLine> {
pub fn find_package_url(&self, url: &str) -> Option<&AuthConfigEntry> {
let url = url
.strip_prefix("http://")
.or_else(|| url.strip_prefix("https://"))
Expand All @@ -251,106 +153,11 @@ impl AuthConfig {
debug!("auth find package url is: {}", url);

self.inner.iter().find_map(|x| {
let entry = x.entry.as_ref()?;
if url.starts_with(entry.host.as_ref()) {
if url.starts_with(x.host.as_ref()) {
Some(x)
} else {
None
}
})

Check warning on line 161 in apt-auth-config/src/lib.rs

View workflow job for this annotation

GitHub Actions / clippy

this `.find_map` can be written more simply using `.find`

warning: this `.find_map` can be written more simply using `.find` --> apt-auth-config/src/lib.rs:155:9 | 155 | / self.inner.iter().find_map(|x| { 156 | | if url.starts_with(x.host.as_ref()) { 157 | | Some(x) 158 | | } else { 159 | | None 160 | | } 161 | | }) | |__________^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_find_map
}
}

impl FromStr for AuthConfig {
type Err = AuthConfigError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut v = vec![];

for i in s.lines().filter(|x| !x.starts_with('#')) {
let entry = i.parse()?;
v.push(entry);
}

Ok(Self { inner: v })
}
}

#[test]
fn test_config_parser() {
let config = r#"machine esm.ubuntu.com/apps/ubuntu/ login bearer password qaq # ubuntu-pro-client
machine esm.ubuntu.com/infra/ubuntu/ login bearer password qaq # ubuntu-pro-client
"#;

let config: AuthConfig = config.parse().unwrap();

assert_eq!(
config,
AuthConfig {
inner: vec![
AuthConfigLine {
entry: Some(AuthConfigEntry {
host: "esm.ubuntu.com/apps/ubuntu/".into(),
user: "bearer".into(),
password: "qaq".into(),
}),
comment: "ubuntu-pro-client".into(),
},
AuthConfigLine {
entry: Some(AuthConfigEntry {
host: "esm.ubuntu.com/infra/ubuntu/".into(),
user: "bearer".into(),
password: "qaq".into(),
}),
comment: "ubuntu-pro-client".into(),
},
]
}
);
}

#[test]
fn test_single_line() {
let s = "machine esm.ubuntu.com/apps/ubuntu/ login bearer password qaq #sdadasdas\n";
let s2 = "machine esm.ubuntu.com/apps/ubuntu/ login bearer password qaq\n";
let s3 = "machine esm.ubuntu.com/apps/ubuntu/ login bearer password qaq";
let s4 = "machine esm.ubuntu.com/apps/ubuntu/ password qaq login bearer";
let s5 = "machine esm.ubuntu.com/apps/ubuntu/ login bearer password qaq # sdadasdas\n";
for mut i in [s, s2, s3, s4, s5] {
let mut l = line(&mut i).unwrap();
l.sort_by(|a, b| a.0.cmp(&b.0));

assert_eq!(
l,
vec![
("login", "bearer"),
("machine", "esm.ubuntu.com/apps/ubuntu/"),
("password", "qaq")
]
);
assert_eq!(i, "");
}

let mut i = "machine esm.ubuntu.com/apps/ubuntu/ login bearer password qaq sdadasdas\n";
let l = line(&mut i);
assert!(l.is_err());

let mut i = "machine esm.ubuntu.com/apps/ubuntu/ # password qaq login bearer";
let l = line(&mut i);
assert!(l.is_err());

let mut i = "machine #esm.ubuntu.com/apps/ubuntu/ password qaq login bearer";
let l = line(&mut i);
assert!(l.is_err());
}

#[test]
fn test_multi_line() {
let mut config = r#"machine esm.ubuntu.com/apps/ubuntu/ login bearer password qaq # ubuntu-pro-client
machine esm.ubuntu.com/infra/ubuntu/ login bearer password qaq # ubuntu-pro-client
"#;

let out = multiline(&mut config);
dbg!(out);
dbg!(config);
}
Loading

0 comments on commit acb0a63

Please sign in to comment.