mail-parser 1.0.0

Fast and robust e-mail parsing library for Rust
Documentation
/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 */

use mail_parser::{MessageParser, MessagePart};
use std::{
    env, fs, io,
    path::{Path, PathBuf},
};

const MESSAGE: &[u8] = br#"From: Art Vandelay <art@vandelay.com> (Vandelay Industries)
To: "Colleagues": "James Smythe" <james@vandelay.com>; Friends:
    jane@example.com, =?UTF-8?Q?John_Sm=C3=AEth?= <john@example.com>;
Date: Sat, 20 Nov 2021 14:22:01 -0800
Subject: Why not both importing AND exporting? =?utf-8?b?4pi6?=
Content-Type: multipart/mixed; boundary="festivus";

--festivus
Content-Type: text/html; charset="us-ascii"
Content-Transfer-Encoding: base64

PGh0bWw+PHA+SSB3YXMgdGhpbmtpbmcgYWJvdXQgcXVpdHRpbmcgdGhlICZsZHF1bztle
HBvcnRpbmcmcmRxdW87IHRvIGZvY3VzIGp1c3Qgb24gdGhlICZsZHF1bztpbXBvcnRpbm
cmcmRxdW87LDwvcD48cD5idXQgdGhlbiBJIHRob3VnaHQsIHdoeSBub3QgZG8gYm90aD8
gJiN4MjYzQTs8L3A+PC9odG1sPg==
--festivus
Content-Type: message/rfc822; name="Exporting my book about coffee tables.eml"
Content-Disposition: inline; filename="Exporting my book about coffee tables.eml"
Content-Transfer-Encoding: 7bit

From: "Cosmo Kramer" <kramer@kramerica.com>
Subject: Exporting my book about coffee tables
Content-Type: multipart/mixed; boundary="giddyup";

--giddyup
Content-Type: text/plain; charset="utf-16"
Content-Transfer-Encoding: quoted-printable

=FF=FE=0C!5=D8"=DD5=D8)=DD5=D8-=DD =005=D8*=DD5=D8"=DD =005=D8"=
=DD5=D85=DD5=D8-=DD5=D8,=DD5=D8/=DD5=D81=DD =005=D8*=DD5=D86=DD =
=005=D8=1F=DD5=D8,=DD5=D8,=DD5=D8(=DD =005=D8-=DD5=D8)=DD5=D8"=
=DD5=D8=1E=DD5=D80=DD5=D8"=DD!=00
--giddyup
Content-Type: image/gif; name*1="about "; name*0="Book ";
              name*2*=utf-8''%e2%98%95 tables.gif
Content-Transfer-Encoding: Base64
Content-Disposition: attachment

R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
--giddyup--
--festivus--
"#;

fn main() -> io::Result<()> {
    let directory = env::args_os().nth(1).map_or_else(
        || env::temp_dir().join("mail-parser-attachments"),
        PathBuf::from,
    );
    fs::create_dir_all(&directory)?;

    let message = MessageParser::new()
        .parse(MESSAGE)
        .expect("the message parses");
    for part in message.parts().filter(|part| part.is_attachment()) {
        let path = directory.join(file_name(part));
        fs::write(&path, part.decoded())?;
        println!(
            "{} ({} bytes, attachment of message {})",
            path.display(),
            part.decoded_len(),
            part.message().id()
        );
    }
    Ok(())
}

fn file_name(part: MessagePart<'_>) -> PathBuf {
    let name = part
        .attachment_name()
        .map(str::to_string)
        .or_else(|| {
            part.nested()
                .and_then(|nested| nested.subject())
                .map(|subject| format!("{subject}.eml"))
        })
        .unwrap_or_default();
    Path::new(&name).file_name().map_or_else(
        || PathBuf::from(format!("part-{}", part.id())),
        PathBuf::from,
    )
}