Add some type safety

This commit is contained in:
Gonne 2024-11-25 20:19:09 +01:00
parent eef3728818
commit bbd936cf88
5 changed files with 336 additions and 21 deletions

2
.gitignore vendored
View file

@ -22,11 +22,11 @@ target/
# Testdata with private mailadresses # Testdata with private mailadresses
testdata/mathebau.aliases testdata/mathebau.aliases
testdata/koma.aliases
# might contain private mailadresses as well # might contain private mailadresses as well
testdata/virt_aliases testdata/virt_aliases
# Added by cargo # Added by cargo
/target /target

310
Cargo.lock generated
View file

@ -5,3 +5,313 @@ version = 3
[[package]] [[package]]
name = "alias_to_sieve" name = "alias_to_sieve"
version = "0.1.0" version = "0.1.0"
dependencies = [
"email-address-parser",
"fqdn",
]
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]]
name = "bumpalo"
version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "console_error_panic_hook"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
dependencies = [
"cfg-if",
"wasm-bindgen",
]
[[package]]
name = "cpufeatures"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "email-address-parser"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe19a4967eca30062be4abaf813d929ba48b3bfb21830367f7e1baae37f213a"
dependencies = [
"console_error_panic_hook",
"pest",
"pest_derive",
"quick-xml",
"wasm-bindgen",
]
[[package]]
name = "fqdn"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d649848b92394c9e1357f6574e48ba9b073a20ceac342f7fbfd0ab5f12524d"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "libc"
version = "0.2.164"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "once_cell"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "pest"
version = "2.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442"
dependencies = [
"memchr",
"thiserror",
"ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd"
dependencies = [
"pest",
"pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e"
dependencies = [
"pest",
"pest_meta",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "pest_meta"
version = "2.7.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d"
dependencies = [
"once_cell",
"pest",
"sha2",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quick-xml"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cc440ee4802a86e357165021e3e255a9143724da31db1e2ea540214c96a0f82"
dependencies = [
"memchr",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]]
name = "syn"
version = "2.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "ucd-trie"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
dependencies = [
"cfg-if",
"once_cell",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"

View file

@ -4,3 +4,7 @@ version = "0.1.0"
edition = "2021" edition = "2021"
rust-version = "1.68.2" rust-version = "1.68.2"
[dependencies]
fqdn = {version = "0.4.2", features = ["domain-label-length-limited-to-63", "domain-name-without-special-chars"]}
email-address-parser = "2.0.0"

View file

@ -74,11 +74,11 @@
"nixpkgs": "nixpkgs_2" "nixpkgs": "nixpkgs_2"
}, },
"locked": { "locked": {
"lastModified": 1732242723, "lastModified": 1732328983,
"narHash": "sha256-NWI8csIK0ujFlFuEXKnoc+7hWoCiEtINK9r48LUUMeU=", "narHash": "sha256-RHt12f/slrzDpSL7SSkydh8wUE4Nr4r23HlpWywed9E=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "a229311fcb45b88a95fdfa5cecd8349c809a272a", "rev": "ed8aa5b64f7d36d9338eb1d0a3bb60cf52069a72",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -1,15 +1,19 @@
use std::collections::BTreeMap; use std::collections::HashMap;
use std::env; use std::env;
use std::fs::File; use std::fs::File;
use std::io::{self, BufRead}; use std::io::{self, BufRead};
use std::path::Path; use std::path::Path;
use std::str::FromStr;
use fqdn::*;
use email_address_parser::EmailAddress;
#[derive(Debug)] #[derive(Debug)]
struct AliasFile { struct AliasFile {
content: io::Lines<io::BufReader<File>>, content: io::Lines<io::BufReader<File>>,
default_domain: String, default_domain: FQDN,
} }
type AliasMap = HashMap<EmailAddress, Vec<EmailAddress>>;
fn main() { fn main() {
let args: Vec<String> = env::args().collect(); let args: Vec<String> = env::args().collect();
@ -20,7 +24,7 @@ fn main() {
let mut alias_files : Vec<AliasFile> = Vec::new(); let mut alias_files : Vec<AliasFile> = Vec::new();
for i in (1..args.len()).step_by(2) { for i in (1..args.len()).step_by(2) {
if let Ok(lines) = read_lines(&args[i]) { if let Ok(lines) = read_lines(&args[i]) {
alias_files.push(AliasFile{content: lines, default_domain: args[i+1].to_string()}) alias_files.push(AliasFile{content: lines, default_domain: FQDN::from_str(&args[i+1]).unwrap()})
} }
} }
@ -29,13 +33,12 @@ fn main() {
println!("{}", sieve_script); println!("{}", sieve_script);
} }
fn generate_sieve_script(redirects: BTreeMap<String, Vec<String>>) -> String { fn generate_sieve_script(redirects: AliasMap) -> String {
let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string(); let mut script : String = "require [\"envelope\", \"copy\"];\n\n".to_string();
for (redirect, mut destinations) in redirects { for (redirect, destinations) in redirects {
script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect, script += format!("if envelope :is \"to\" \"{}\" {{\n{}}}\n", redirect,
{ {
let mut subscript : String = "".to_string(); let mut subscript : String = "".to_string();
destinations.sort();
for destination in destinations.iter().rev().skip(1).rev() { for destination in destinations.iter().rev().skip(1).rev() {
subscript += format!(" redirect :copy \"{}\";\n", destination).as_str(); subscript += format!(" redirect :copy \"{}\";\n", destination).as_str();
} }
@ -46,10 +49,10 @@ fn generate_sieve_script(redirects: BTreeMap<String, Vec<String>>) -> String {
script script
} }
fn parse_alias_to_map(alias_files: Vec<AliasFile>) -> BTreeMap<String, Vec<String>> { fn parse_alias_to_map(alias_files: Vec<AliasFile>) -> AliasMap {
// File must exist in the current path // File must exist in the current path
let mut redirect_map : BTreeMap<String, Vec<String>> = BTreeMap::new(); let mut redirect_map : AliasMap = AliasMap::new();
let mut destinations : Vec<String> = Vec::new(); let mut destinations : Vec<EmailAddress> = Vec::new();
for alias_file in alias_files.into_iter() { for alias_file in alias_files.into_iter() {
for line in alias_file.content { for line in alias_file.content {
let line = line.unwrap(); let line = line.unwrap();
@ -58,7 +61,7 @@ fn parse_alias_to_map(alias_files: Vec<AliasFile>) -> BTreeMap<String, Vec<Strin
if destination.is_empty() { if destination.is_empty() {
continue; continue;
} }
let redirects: Vec<String> = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ") let redirects: Vec<EmailAddress> = line.split_at(line.find(char::is_whitespace).unwrap_or(0)).1.split(" ")
.filter(|address| address.trim().to_string().replace(",","") != "").map(|addr| to_mailaddress(addr, &alias_file.default_domain)).collect(); .filter(|address| address.trim().to_string().replace(",","") != "").map(|addr| to_mailaddress(addr, &alias_file.default_domain)).collect();
if redirects.is_empty() { if redirects.is_empty() {
continue; continue;
@ -70,7 +73,7 @@ fn parse_alias_to_map(alias_files: Vec<AliasFile>) -> BTreeMap<String, Vec<Strin
let mut changed = true; let mut changed = true;
while changed { while changed {
changed = false; changed = false;
let mut all_new_redirects : BTreeMap<String, Vec<String>> = BTreeMap::new(); let mut all_new_redirects : AliasMap = AliasMap::new();
for destination in destinations.iter() { for destination in destinations.iter() {
for forward_to in redirect_map.get(destination).unwrap().iter() { for forward_to in redirect_map.get(destination).unwrap().iter() {
if let Some(new_redirects) = redirect_map.get(forward_to) { if let Some(new_redirects) = redirect_map.get(forward_to) {
@ -88,16 +91,15 @@ fn parse_alias_to_map(alias_files: Vec<AliasFile>) -> BTreeMap<String, Vec<Strin
redirect_map redirect_map
} }
fn to_mailaddress(local_part: &str, default_domain : &String) -> String { fn to_mailaddress(local_part: &str, default_domain : &FQDN) -> EmailAddress {
let mut addr = local_part.trim().to_string(); let mut addr = local_part.trim().to_string();
addr = addr.replace(",", ""); addr = addr.replace(",", "");
if addr.contains("@") { if addr.contains("@") {
return addr; return EmailAddress::parse(&addr, None).unwrap();
} }
addr + "@" + default_domain EmailAddress::new(&addr, &default_domain.to_string(), None).unwrap()
} }
// The output is wrapped in a Result to allow matching on errors. // The output is wrapped in a Result to allow matching on errors.
// Returns an Iterator to the Reader of the lines of the file. // Returns an Iterator to the Reader of the lines of the file.
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>> fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
@ -106,7 +108,6 @@ where P: AsRef<Path>, {
Ok(io::BufReader::new(file).lines()) Ok(io::BufReader::new(file).lines())
} }
fn print_help(){ fn print_help(){
print!("Reads a virtual alias file and needs a default domain to append to local paths, e.g. print!("Reads a virtual alias file and needs a default domain to append to local paths, e.g.
./alias_to_sieve example.com.txt example.com"); ./alias_to_sieve example.com.txt example.com");