Index: .github/workflows/rust-clippy.yml ================================================================== --- .github/workflows/rust-clippy.yml +++ .github/workflows/rust-clippy.yml @@ -2,10 +2,13 @@ on: push: branches: [ master, release ] pull_request: +permissions: + contents: read + # sccache enable for rust/C builds env: SCCACHE_GHA_ENABLED: "true" RUSTC_WRAPPER: "sccache" @@ -12,12 +15,10 @@ jobs: rust-ci-run: name: Run rust-clippy analyzing and tests runs-on: ubuntu-latest if: github.event_name == 'push' || (github.head_ref != 'master' && github.head_ref != 'release') - permissions: - contents: read steps: # SETUP - uses: actions/checkout@v7 with: persist-credentials: false Index: Cargo.lock ================================================================== --- Cargo.lock +++ Cargo.lock @@ -227,23 +227,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "aws-lc-rs" -version = "1.18.2" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faac5829c2b74c28f830747e7818ccfb684261b5f48a1118b1e2a13d36dfab13" +checksum = "048006790404103c488e6c5616b3e7e702a71292aadda08e8f7698a9e0d2eb53" dependencies = [ "aws-lc-sys", "zeroize", ] [[package]] name = "aws-lc-sys" -version = "0.46.0" +version = "0.47.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1622d8446a2d4b2ce0c7eefc73dd43a99779028d5ee5c2dd8073a658ba8a2bc" +checksum = "8bfbe32ac36b3c428696ce315464181bc4029d689fd58e3f2a4e58c252242f52" dependencies = [ "cc", "cmake", "dunce", "fs_extra", @@ -1199,13 +1199,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "openssl-probe" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" +checksum = "7e036b411754132fd6b74e94b74e9a13c5cf606fa93d1885c35860d3060820b1" [[package]] name = "owo-colors" version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1481,13 +1481,13 @@ "semver", ] [[package]] name = "rustix" -version = "1.1.5" +version = "1.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891efababe418670775f199f0d233d84843c227a0949a883ce15b37c78d6629d" +checksum = "e5e76742e5294153d4dde1207dbec5bdd81eef6fabdc0d2118babadabfd858aa" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", Index: src/mail.rs ================================================================== --- src/mail.rs +++ src/mail.rs @@ -67,11 +67,12 @@ /// /// # Arguments /// * `settings` - Parsed application configuration. /// /// # Errors - /// Returns an error if required configuration values are missing or invalid. + /// Returns an error if required configuration values are missing or invalid, + /// Telegram API client creation fails, or the address regex cannot be built. pub fn new (settings: config::Config) -> Result { let api_key = settings.get_string("api_key") .context("[smtp2tg.toml] missing \"api_key\" parameter.\n")?; let mut recipients = HashMap::new(); for (name, value) in settings.get_table("recipients") @@ -129,11 +130,23 @@ Ok(self.tg.get(name).unwrap_or(&self.tg.default)) } else { bail!("Email address {name:?} is not from an allowed domain."); } } - /// Attempt to deliver one message + /// Forwards the buffered email to each distinct destination chat, using the + /// default chat for allowed addresses without a configured recipient. + /// + /// Inlines the first plain-text body if the escaped message is below 4096 + /// bytes. Sends remaining text parts and decoded attachments as documents; + /// HTML body parts are omitted. Leaves the buffered data and envelope intact. + /// + /// # Errors + /// Returns an error for a missing envelope, no recipients, a disallowed + /// address, mail parsing or part extraction failures, or rejected closing + /// `pre`/`code` tags in text passed to validation. Propagates media-group + /// construction and Telegram request errors, including diagnostic sends. + /// Delivery to earlier chats is not rolled back if a later send fails. async fn relay_mail (&self) -> Result<()> { if let Some(headers) = &self.headers { let mail = mail_parser::MessageParser::new().parse(&self.data) .context("Failed to parse mail.")?; Index: src/main.rs ================================================================== --- src/main.rs +++ src/main.rs @@ -42,10 +42,12 @@ /// the SMTP server runs until it stops or encounters an error. /// /// # Errors /// Returns an error if the configuration file is missing, inaccessible, /// insecure, malformed, or contains invalid required settings. +/// Also returns an error if the system hostname cannot be read or is not +/// valid UTF-8, or if mail server initialisation fails. /// /// # Panics /// Panics if configuring or serving the SMTP server fails. async fn main (ex: &LocalExecutor<'_>) -> Result<()> { let args = Args::parse();