app
Adom Chip Fetcher
Public Made by Adomby adom
Your whole parts library — manufacturer-grade chip CAD (symbol, footprint, 3D) one tap from your EDA tool.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Serialize, Deserialize, Clone)]
pub struct Credential {
pub host: String,
pub username: String,
pub password: String,
}
#[derive(Serialize)]
pub struct PublicCred {
pub host: String,
pub username: String,
pub password: String,
}
fn store_path() -> Result<PathBuf> {
let home = std::env::var("HOME").map_err(|_| anyhow::anyhow!("HOME not set"))?;
let dir = PathBuf::from(home).join(".config").join("adom-chip-fetcher");
std::fs::create_dir_all(&dir)?;
Ok(dir.join("credentials.json"))
}
pub fn load() -> Result<Vec<Credential>> {
let path = store_path()?;
if !path.exists() {
return Ok(Vec::new());
}
let body = std::fs::read_to_string(&path)?;
let creds: Vec<Credential> = serde_json::from_str(&body).unwrap_or_default();
Ok(creds)
}
fn save(creds: &[Credential]) -> Result<()> {
let path = store_path()?;
let body = serde_json::to_string_pretty(creds)?;
std::fs::write(&path, body)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
let _ = std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600));
}
Ok(())
}
pub fn list_public() -> Result<Vec<PublicCred>> {
Ok(load()?
.into_iter()
.map(|c| PublicCred { host: c.host, username: c.username, password: c.password })
.collect())
}
pub fn set(host: &str, username: &str, password: &str) -> Result<()> {
let mut creds = load()?;
creds.retain(|c| c.host != host);
creds.push(Credential {
host: host.to_string(),
username: username.to_string(),
password: password.to_string(),
});
save(&creds)
}
pub fn delete(host: &str) -> Result<bool> {
let mut creds = load()?;
let before = creds.len();
creds.retain(|c| c.host != host);
let removed = creds.len() != before;
if removed { save(&creds)?; }
Ok(removed)
}
/// Match a URL's hostname against stored host patterns. Glob `*` matches any subdomain.
/// Longest non-glob suffix wins.
pub fn match_url(url: &str) -> Result<Option<Credential>> {
let creds = load()?;
let host = match url_host(url) {
Some(h) => h,
None => return Ok(None),
};
let mut best: Option<(usize, Credential)> = None;
for c in creds {
if let Some(score) = match_score(&c.host, &host) {
match &best {
Some((bs, _)) if *bs >= score => {}
_ => best = Some((score, c)),
}
}
}
Ok(best.map(|(_, c)| c))
}
fn url_host(url: &str) -> Option<String> {
let after_scheme = url.split_once("://").map(|(_, r)| r).unwrap_or(url);
let host = after_scheme.split(['/', '?', '#']).next()?;
let host = host.split('@').next_back()?;
let host = host.split(':').next()?;
Some(host.to_lowercase())
}
fn match_score(pattern: &str, host: &str) -> Option<usize> {
let pat = pattern.to_lowercase();
let h = host.to_lowercase();
if let Some(suffix) = pat.strip_prefix("*.") {
if h.ends_with(&format!(".{suffix}")) || h == suffix {
return Some(suffix.len());
}
None
} else if pat == h {
Some(pat.len() * 2)
} else {
None
}
}
/// Wrap a URL with basic-auth user:pass@ if a credential matches.
pub fn wrap_url_with_auth(url: &str) -> Result<String> {
let cred = match match_url(url)? {
Some(c) => c,
None => return Ok(url.to_string()),
};
let after_scheme = match url.split_once("://") {
Some(x) => x,
None => return Ok(url.to_string()),
};
let scheme = after_scheme.0;
let rest = after_scheme.1;
if rest.contains('@') {
return Ok(url.to_string());
}
let user = url_encode(&cred.username);
let pass = url_encode(&cred.password);
Ok(format!("{scheme}://{user}:{pass}@{rest}"))
}
fn url_encode(s: &str) -> String {
let mut out = String::with_capacity(s.len());
for b in s.bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'.' | b'_' | b'~' => out.push(b as char),
_ => out.push_str(&format!("%{:02X}", b)),
}
}
out
}