#header-parser #parser #mime #message-format

mail-parser

适用于Rust的快速且健壮的电子邮件解析库

26次发布

0.9.3 2024年3月28日
0.9.2 2023年11月27日
0.9.1 2023年9月22日
0.8.2 2023年2月22日
0.3.0 2021年11月8日

#34 in 解析器实现

Download history 3455/week @ 2024-04-28 3561/week @ 2024-05-05 3934/week @ 2024-05-12 4587/week @ 2024-05-19 3901/week @ 2024-05-26 4007/week @ 2024-06-02 3842/week @ 2024-06-09 4454/week @ 2024-06-16 4643/week @ 2024-06-23 4612/week @ 2024-06-30 3961/week @ 2024-07-07 4310/week @ 2024-07-14 4969/week @ 2024-07-21 5276/week @ 2024-07-28 5187/week @ 2024-08-04 5144/week @ 2024-08-11

20,758 每月下载量
35 个crate(24个直接) 中使用

Apache-2.0 OR MIT

555KB
12K SLoC

mail-parser

crates.io build docs.rs crates.io

mail-parser 是一个用Rust编写的 电子邮件解析库,完全符合互联网消息格式标准(《RFC 5322》),多用途互联网邮件扩展(MIME;《RFC 2045 - 2049》)以及许多其他互联网消息RFC

它还支持解码41种不同的字符集,包括过时的格式,如UTF-7。所有Unicode(UTF-*)和单字节字符集都由库内部处理,而支持中文和日语等语言的旧多字节编码,如BIG5或ISO-2022-JP,则由可选依赖项encoding_rs提供。

通常,这个库遵循Postel定律或鲁棒性原则,即实现必须在发送行为上保守,在接收行为上自由。这意味着只要这些行为不太偏离标准,mail-parser 会尽力解析不符合规范的电子邮件消息。

与其他电子邮件解析库不同,该库遵循RFC 8621,第4.1.4节,提供了一种更符合人类友好的消息内容表示,包括仅由文本主体部分、HTML主体部分和附件组成。此外,当缺少alternative版本时,会自动将HTML和纯文本内联主体部分进行转换。

性能和内存安全性是在设计mail-parser时考虑的两个重要因素

  • 零拷贝:此库返回的几乎所有字符串都是对输入原始消息的Cow<str>引用。
  • 基于Chromium解码器的高性能Base64解码
  • 使用完美哈希函数快速解析消息头字段、字符集名称和HTML实体。
  • 使用100%安全的Rust语言编写,无外部依赖。
  • 库中的每个函数都经过模糊测试和彻底的MIRI测试
  • 经过实战考验,使用自1995年至今数百万条真实电子邮件消息。
  • 由全球的Stalwart邮件服务器在生产环境中使用。

使用示例

    let input = br#"From: Art Vandelay <[email protected]> (Vandelay Industries)
To: "Colleagues": "James Smythe" <[email protected]>; Friends:
    [email protected], =?UTF-8?Q?John_Sm=C3=AEth?= <[email protected]>;
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

From: "Cosmo Kramer" <[email protected]>
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--
"#;

    let message = MessageParser::default().parse(input).unwrap();

    // Parses addresses (including comments), lists and groups
    assert_eq!(
        message.from().unwrap().first().unwrap(),
        &Addr::new(
            "Art Vandelay (Vandelay Industries)".into(),
            "[email protected]"
        )
    );

    assert_eq!(
        message.to().unwrap().as_group().unwrap(),
        &[
            Group::new(
                "Colleagues",
                vec![Addr::new("James Smythe".into(), "[email protected]")]
            ),
            Group::new(
                "Friends",
                vec![
                    Addr::new(None, "[email protected]"),
                    Addr::new("John Smîth".into(), "[email protected]"),
                ]
            )
        ]
    );

    assert_eq!(
        message.date().unwrap().to_rfc3339(),
        "2021-11-20T14:22:01-08:00"
    );

    // RFC2047 support for encoded text in message readers
    assert_eq!(
        message.subject().unwrap(),
        "Why not both importing AND exporting? ☺"
    );

    // HTML and text body parts are returned conforming to RFC8621, Section 4.1.4
    assert_eq!(
        message.body_html(0).unwrap(),
        concat!(
            "<html><p>I was thinking about quitting the &ldquo;exporting&rdquo; to ",
            "focus just on the &ldquo;importing&rdquo;,</p><p>but then I thought,",
            " why not do both? &#x263A;</p></html>"
        )
    );

    // HTML parts are converted to plain text (and viceversa) when missing
    assert_eq!(
        message.body_text(0).unwrap(),
        concat!(
            "I was thinking about quitting the “exporting” to focus just on the",
            " “importing”,\nbut then I thought, why not do both? ☺\n"
        )
    );

    // Supports nested messages as well as multipart/digest
    let nested_message = message
        .attachment(0)
        .unwrap()
        .message();
        .unwrap();

    assert_eq!(
        nested_message.subject().unwrap(),
        "Exporting my book about coffee tables"
    );

    // Handles UTF-* as well as many legacy encodings
    assert_eq!(
        nested_message.body_text(0).unwrap(),
        "ℌ𝔢𝔩𝔭 𝔪𝔢 𝔢𝔵𝔭𝔬𝔯𝔱 𝔪𝔶 𝔟𝔬𝔬𝔨 𝔭𝔩𝔢𝔞𝔰𝔢!"
    );
    assert_eq!(
        nested_message.body_html(0).unwrap(),
        "<html><body>ℌ𝔢𝔩𝔭 𝔪𝔢 𝔢𝔵𝔭𝔬𝔯𝔱 𝔪𝔶 𝔟𝔬𝔬𝔨 𝔭𝔩𝔢𝔞𝔰𝔢!</body></html>"
    );

    let nested_attachment = nested_message.attachment(0).unwrap();

    assert_eq!(nested_attachment.len(), 42);

    // Full RFC2231 support for continuations and character sets
    assert_eq!(
        nested_attachment.attachment_name().unwrap(),
        "Book about ☕ tables.gif"
    );

    // Integrates with Serde
    println!("{}", serde_json::to_string_pretty(&message).unwrap());

更多示例在示例目录下。请注意,此库不支持构建电子邮件消息,因为该功能由mail-buildercrate单独提供。

测试、模糊测试和基准测试

要运行测试套件

 $ cargo test --all-features

或者,要使用MIRI运行测试套件

 $ cargo +nightly miri test --all-features

使用cargo-fuzz模糊库

 $ cargo +nightly fuzz run mail_parser

并,运行基准测试

 $ cargo +nightly bench --all-features

符合RFC

支持的字符集

  • UTF-8
  • UTF-16, UTF-16BE, UTF-16LE
  • UTF-7
  • US-ASCII
  • ISO-8859-1
  • ISO-8859-2
  • ISO-8859-3
  • ISO-8859-4
  • ISO-8859-5
  • ISO-8859-6
  • ISO-8859-7
  • ISO-8859-8
  • ISO-8859-9
  • ISO-8859-10
  • ISO-8859-13
  • ISO-8859-14
  • ISO-8859-15
  • ISO-8859-16
  • CP1250
  • CP1251
  • CP1252
  • CP1253
  • CP1254
  • CP1255
  • CP1256
  • CP1257
  • CP1258
  • KOI8-R
  • KOI8_U
  • MACINTOSH
  • IBM850
  • TIS-620

通过可选依赖项encoding_rs支持字符集

  • SHIFT_JIS
  • BIG5
  • EUC-JP
  • EUC-KR
  • GB18030
  • GBK
  • ISO-2022-JP
  • WINDOWS-874
  • IBM-866

许可证

许可方式任选其一

由您选择。

版权(C)2020-2022,Stalwart Labs Ltd.

依赖关系

~0–1MB
~34K SLoC