mail-parser

repository·main·Indexed 19 days ago

https://github.com/stalwartlabs/mail-parser

A high-performance, zero-copy Rust library for parsing e-mail messages according to RFC 5322 and MIME standards. It provides a flattened view of message content (text, HTML, and attachments) conforming to RFC 8621, Section 4.1.4, rather than a complex MIME tree. Features include Serde integration for JSON serialization, support for a wide range of character sets, and utilities for decoding Base64, quoted-printable, and hexadecimal sequences.

Tokens
7.2K
Snippets
27
Records
35
Agent score
66%

What's inside mail-parser

  1. How mail-parser represents message content

    main

    Unlike many parsers that return deeply nested MIME trees, mail-parser follows RFC 8621, Section 4.1.4 to provide a flattened, human-friendly view.

    Instead of navigating a complex tree of MIME parts, you can directly access:

    1. Text body parts: Via .body_text(index).
    2. HTML body parts: Via .body_html(index).
    3. Attachments: Via .attachment(index).

    If a message contains an alternative part but is missing one of the formats (e.g., it has HTML but no plain text), the library automatically performs the conversion for you so that .body_text() and .body_html() remain usable.

  2. Run tests, fuzzing, and benchmarks

    main

    To ensure the reliability of your integration or to contribute to the library, use the following commands:

    Standard Tests:

    $ cargo test --all-features

    MIRI Tests (for memory safety verification):

    $ cargo +nightly miri test --all-features

    Fuzzing (requires cargo-fuzz):

    $ cargo +nightly fuzz run mail_parser

    Benchmarks:

    $ cargo +nightly bench --all-features
  3. Parse e-mail messages with mail-parser

    main

    Use MessageParser::default().parse(input) to parse raw e-mail bytes. The library returns a Message object that provides a human-friendly representation of the content, conforming to RFC 8621, Section 4.1.4.

    Key features of the parsed output:

    • Zero-copy: Most strings are returned as Cow<str> references to the input.
    • Body Parts: Automatically provides access to text body parts, HTML body parts, and attachments. If an alternative version is missing, it automatically converts between HTML and plain text.
    • Address Parsing: Handles addresses, comments, lists, and groups.
    • Nested Messages: Supports parsing nested message/rfc822 parts as full Message objects.
    • Serde Integration: The Message type integrates with serde for JSON serialization.
    let input = br"From: Art Vandelay <art@vandelay.com> ...";
    let message = MessageParser::default().parse(input).unwrap();
    
    // Accessing headers
    let subject = message.subject().unwrap();
    
    // Accessing body parts (RFC 8621 style)
    let html_body = message.body_html(0).unwrap();
    let text_body = message.body_text(0).unwrap();
    
    // Accessing attachments
    let attachment = message.attachment(0).unwrap();
    let name = attachment.attachment_name().unwrap();
    
    // Handling nested messages
    let nested_message = attachment.message().unwrap();
  4. Supported character sets

    main

    The library supports a wide range of character sets for decoding message content.

    Built-in support:

    • All Unicode (UTF-*) encodings (UTF-8, UTF-16, UTF-16BE, UTF-16LE, UTF-7).
    • US-ASCII.
    • Various ISO-8859 series (1 through 16).
    • Windows Code Pages (CP1250 through CP1258).
    • KOI8-R, KOI8_U.
    • MACINTOSH, IBM850, TIS-620.

    Optional support via encoding_rs dependency: If you enable the encoding_rs feature, you gain support for legacy multi-byte encodings used in Chinese and Japanese:

    • SHIFT_JIS, BIG5, EUC-JP, EUC-KR, GB18030, GBK, ISO-2022-JP, WINDOWS-874, IBM-866.
  5. Retrieve specific headers with GetHeader trait

    main

    The GetHeader<'x> trait provides a way to look up specific headers by name.

    Methods:

    • header_value(&self, name: &HeaderName<'_>) -> Option<&HeaderValue<'x>>: Returns the parsed value of the specified header.
    • header(&self, name: impl Into<HeaderName<'x>>) -> Option<&Header<'x>>: Returns the full Header object, including offsets.
  6. Access Mbox Message metadata and contents

    main

    The Message struct represents a single parsed message from an Mbox file. You can access its metadata and raw content using the following methods:

    • internal_date(&self) -> u64: Returns the message creation date in UTC seconds since the UNIX epoch.
    • from(&self) -> &str: Returns the message sender address (the string following the From prefix in the Mbox header).
    • contents(&self) -> &[u8]: Returns a byte slice of the message body.
    • unwrap_contents(self) -> Vec<u8>: Consumes the message and returns the body as an owned Vec<u8>.
    // Assuming 'message' is a Message instance
    let sender = message.from();
    let timestamp = message.internal_date();
    let body = message.contents();
    
    // Or consume it to get an owned Vec
    let owned_body = message.unwrap_contents();
  7. Append HTML tokens with `add_html_token`

    main

    The add_html_token function allows for manual appending of HTML tokens (including entities) to a mutable string buffer.

    Parameters:

    • result: A mutable reference to the String where the decoded content will be appended.
    • token: A byte slice (&[u8]) representing the token to process.
    • add_space: A boolean indicating whether to prepend a space before the token.

    If the token is an HTML entity (e.g., &#123; or &amp;), it is decoded into its character representation. If decoding fails, the Unicode replacement character is used.

    let mut buffer = String::new();
    add_html_token(&mut buffer, b"Hello", false);
    add_html_token(&mut buffer, b"&amp;", true);
    // buffer will be "Hello &"
  8. Convert HTML to plain text with `html_to_text`

    main

    Use html_to_text to strip HTML tags and convert an HTML string into a plain text representation. This function handles common HTML elements by:

    • Stripping tags like <script>, <style>, <head>, and <template> content.
    • Converting <br> tags and closing </p> tags into newlines (\n).
    • Handling HTML entities (e.g., &#x...;, &#...;, or named entities) by decoding them into their corresponding characters.
    • Removing HTML comments (<!-- ... -->).
    • Preserving whitespace and managing tokenization to ensure readable text output.
    let html_input = "<html><body><h1>Hello</h1><p>This is <b>bold</b> text.</p></body></html>";
    let plain_text = html_to_text(html_input);
    // plain_text will contain the text content with appropriate spacing and newlines
  9. Access MIME headers with MimeHeaders trait

    main

    Implement or use the MimeHeaders<'x> trait to easily extract common MIME metadata from a part. This trait provides helper methods for:

    • content_description(): Returns the Content-Description field.
    • content_disposition(): Returns the Content-Disposition field.
    • content_id(): Returns the Content-ID field.
    • content_encoding(): Returns the Content-Transfer-Encoding field.
    • content_type(): Returns the Content-Type field.
    • content_language(): Returns the Content-Language header value.
    • content_location(): Returns the Content-Location field.
    • attachment_name(): Returns the filename from Content-Disposition or Content-Type attributes.
    • is_content_type(type, subtype): Checks if the part matches a specific MIME type and subtype (case-insensitive).
    // Example usage of MimeHeaders methods
    if part.is_content_type("text", "plain") {
        // handle plain text
    }
    
    if let Some(name) = part.attachment_name() {
        println!("Attachment name: {}", name);
    }
  10. Decode hexadecimal encoded sequences with `decode_hex`

    main

    The decode_hex function decodes percent-encoded hexadecimal sequences (e.g., %20) within a byte slice. It processes the input byte by byte and returns a tuple containing a boolean indicating if the entire sequence was validly decoded, and a Vec<u8> containing the resulting bytes.

    Behavior:

    • If a % character is encountered, it must be followed by two valid hexadecimal characters.
    • If the sequence is malformed (e.g., a % not followed by hex digits, or multiple consecutive % symbols), the function returns false for the success flag.
    • Non-encoded characters are passed through to the result unchanged.
    // Example usage of decode_hex
    let input = b"this%20is%20some%20text";
    let (success, result) = decode_hex(input);
    
    assert!(success);
    assert_eq!(result, b"this is some text");
  11. Decode quoted-printable bytes with `quoted_printable_decode`

    main

    Use quoted_printable_decode to decode a slice of bytes containing quoted-printable encoded data. It returns Some(Vec<u8>) containing the decoded bytes, or None if the input is malformed (e.g., invalid hex sequences).

    let decoded = quoted_printable_decode(b"=E2=80=94");
    // Returns Some(vec![0xE2, 0x80, 0x94])