/** * RFC 5322/MIME Compliant Email Parser * * Parses raw email strings and provides methods for parsing individual * email components (headers, addresses, Message-IDs, dates, content types). * * Wraps the `postal-mime` library for full email parsing and the * `email-addresses` library for RFC 5322 address grammar parsing. * * **Platform-specific code (Node.js `Buffer`):** * This file uses `Buffer.from()` in `decodeEncodedWord()`, `decodeQuotedPrintable()`, * and `decodeBase64()` for base64 decoding, quoted-printable decoding, and charset * conversion (e.g. ISO-8859-1 → UTF-8). These usages cannot easily be replaced with * pure `Uint8Array` operations because: * - `Buffer.from(str, 'base64')` and `Buffer.from(str, 'latin1')` provide charset-aware * decoding that has no single-call equivalent in the Web Crypto API or `TextDecoder` * for all supported charsets. * - The `postal-mime` library (used in `parse()`) already handles browser-compatible * parsing, so the Buffer-dependent methods are primarily used for the manual * header/body decoding path. * * If browser compatibility is needed for this parser, consider replacing the Buffer * calls with `TextDecoder` (for charset conversion) and `atob()` (for base64), or * moving this file to `brightchain-api-lib`. * * @platform Node.js — uses `Buffer` for base64/quoted-printable decoding and charset handling * @see RFC 5322 - Internet Message Format * @see RFC 2045 - MIME Part One: Format of Internet Message Bodies * @see RFC 2046 - MIME Part Two: Media Types * @see Requirement 18.4 — platform-specific code documented * * @remarks * Requirements: 2.1, 2.2, 2.3, 2.4, 3.4, 3.5, 3.6, 14.4 */ import { IAddress, IMailbox } from '../../interfaces/messaging/emailAddress'; import type { IEmailMetadata } from '../../interfaces/messaging/emailMetadata'; import { ContentTransferEncoding, IContentType, IMimePart } from '../../interfaces/messaging/mimePart'; /** * RFC 5322/MIME compliant email parser. * * Provides methods for parsing email headers, addresses, Message-IDs, * dates, and content types. The main `parse()` method (Task 4.6) will * use `postal-mime` for full email parsing. * * @see Design Document: EmailParser Class */ export declare class EmailParser { /** * Unfolds multi-line headers by removing CRLF followed by whitespace. * * Per RFC 5322 Section 2.2.3, long header fields can be split across * multiple lines by inserting a CRLF before any whitespace. Unfolding * reverses this by removing the CRLF (or LF) that precedes whitespace. * * Also handles LF followed by whitespace for robustness (Requirement 14.5). * * @param rawHeaders - The raw header string potentially containing folded lines * @returns The unfolded header string * * @see RFC 5322 Section 2.2.3 - Long Header Fields * @see Requirement 14.4 - Unfold multi-line headers * * @example * ```typescript * const parser = new EmailParser(); * parser.unfoldHeaders('Subject: This is\r\n a long subject'); * // => 'Subject: This is a long subject' * ``` */ unfoldHeaders(rawHeaders: string): string; /** * Parses a raw header section string into a Map of header names to values. * * Handles: * - Header unfolding (multi-line headers) * - Multiple values for the same header name * - Case-insensitive header name storage (lowercased keys) * * @param headerSection - The raw header section string (headers separated by line breaks) * @returns Map of lowercase header names to arrays of values * * @see RFC 5322 Section 2.2 - Header Fields * * @example * ```typescript * const parser = new EmailParser(); * const headers = parser.parseHeaders('From: alice@example.com\r\nTo: bob@example.com'); * headers.get('from'); // ['alice@example.com'] * headers.get('to'); // ['bob@example.com'] * ``` */ parseHeaders(headerSection: string): Map; /** * Parses a single mailbox address string into an IMailbox object. * * Uses the `email-addresses` library for RFC 5322 compliant parsing. * Supports both name-addr format (`"Display Name" `) * and addr-spec format (`local@domain`). * * @param mailboxString - The mailbox string to parse * @returns The parsed IMailbox object * @throws {EmailError} With INVALID_MAILBOX if parsing fails * * @see RFC 5322 Section 3.4 - Address Specification * @see Requirement 2.1 - Parse mailbox addresses * @see Requirement 2.2 - Parse addr-spec as local-part@domain * @see Requirement 2.3 - Support quoted-string local-parts * * @example * ```typescript * const parser = new EmailParser(); * const mailbox = parser.parseMailbox('John Doe '); * // { displayName: 'John Doe', localPart: 'john', domain: 'example.com' } * * const simple = parser.parseMailbox('user@example.com'); * // { localPart: 'user', domain: 'example.com' } * ``` */ parseMailbox(mailboxString: string): IMailbox; /** * Parses an address list string into an array of IAddress objects. * * Uses the `email-addresses` library for RFC 5322 compliant parsing. * Supports both individual mailboxes and group addresses. * * @param addressListString - The comma-separated address list string * @returns Array of parsed IAddress objects (IMailbox or IAddressGroup) * @throws {EmailError} With INVALID_MAILBOX if parsing fails * * @see RFC 5322 Section 3.4 - Address Specification * @see Requirement 2.1 - Parse mailbox addresses * @see Requirement 2.4 - Support group addresses * * @example * ```typescript * const parser = new EmailParser(); * const addresses = parser.parseAddressList('alice@example.com, Bob '); * // [IMailbox, IMailbox] * * const withGroup = parser.parseAddressList('Team: alice@example.com, bob@example.com;'); * // [IAddressGroup] * ``` */ parseAddressList(addressListString: string): IAddress[]; /** * Parses a Message-ID string, validating its format. * * Message-IDs must be enclosed in angle brackets and contain exactly * one "@" character per RFC 5322 Section 3.6.4. * * Also supports extracting multiple Message-IDs from In-Reply-To * or References headers when called via parseMessageIdList(). * * @param messageIdString - The Message-ID string to parse (e.g., "") * @returns The validated Message-ID string (with angle brackets) * @throws {EmailError} With INVALID_MESSAGE_ID if format is invalid * * @see RFC 5322 Section 3.6.4 - Identification Fields * @see Requirement 3.4 - Validate Message-IDs are enclosed in angle brackets * @see Requirement 3.5 - Validate Message-IDs contain exactly one "@" * * @example * ```typescript * const parser = new EmailParser(); * parser.parseMessageId(''); * // => '' * * parser.parseMessageId('no-brackets@example.com'); * // throws EmailError (INVALID_MESSAGE_ID) * ``` */ parseMessageId(messageIdString: string): string; /** * Extracts multiple Message-IDs from In-Reply-To or References header values. * * These headers can contain multiple Message-IDs separated by whitespace. * * @param headerValue - The header value containing one or more Message-IDs * @returns Array of validated Message-ID strings * * @see RFC 5322 Section 3.6.4 - Identification Fields * @see Requirement 3.6 - Extract multiple Message-IDs separated by whitespace * * @example * ```typescript * const parser = new EmailParser(); * parser.parseMessageIdList(' '); * // => ['', ''] * ``` */ parseMessageIdList(headerValue: string): string[]; /** * Parses an RFC 5322 date string into a Date object. * * Handles common RFC 5322 date formats including: * - Full format: "Thu, 13 Feb 2025 15:30:00 +0000" * - Without day-of-week: "13 Feb 2025 15:30:00 +0000" * - Various timezone formats * * @param dateString - The RFC 5322 date string to parse * @returns The parsed Date object * @throws {EmailError} With INVALID_DATE if parsing fails * * @see RFC 5322 Section 3.3 - Date and Time Specification * * @example * ```typescript * const parser = new EmailParser(); * parser.parseDate('Thu, 13 Feb 2025 15:30:00 +0000'); * // => Date object for 2025-02-13T15:30:00Z * ``` */ parseDate(dateString: string): Date; /** * Parses a Content-Type header string into an IContentType object. * * Extracts the type, subtype, and parameters from a Content-Type header value. * Handles quoted parameter values containing special characters. * * @param contentTypeString - The Content-Type header value to parse * @returns The parsed IContentType object * @throws {EmailError} With INVALID_CONTENT_TYPE if parsing fails * * @see RFC 2045 Section 5 - Content-Type Header Field * @see Requirement 5.1 - Content-Type with type/subtype and optional parameters * @see Requirement 5.2 - Parse Content-Type parameters * @see Requirement 5.3 - Handle quoted parameter values * * @example * ```typescript * const parser = new EmailParser(); * const ct = parser.parseContentType('text/plain; charset=utf-8'); * // { type: 'text', subtype: 'plain', parameters: Map { 'charset' => 'utf-8' } } * * const mp = parser.parseContentType('multipart/mixed; boundary="----=_Part_123"'); * // { type: 'multipart', subtype: 'mixed', parameters: Map { 'boundary' => '----=_Part_123' } } * ``` */ parseContentType(contentTypeString: string): IContentType; /** * Parses Content-Type parameters from a parameter string. * * Handles both quoted and unquoted parameter values. * * @param paramString - The parameter portion of the Content-Type header * @param parameters - Map to populate with parsed parameters */ private parseContentTypeParameters; /** * Decodes an RFC 2047 encoded-word back to a UTF-8 string. * * Encoded-words have the format: =?charset?encoding?encoded-text?= * where encoding is either "B" (base64) or "Q" (quoted-printable). * * If the input is not an encoded-word, it is returned as-is. * * @param encodedWord - The string potentially containing an encoded-word * @returns The decoded UTF-8 string * * @see RFC 2047 - MIME Part Three: Message Header Extensions for Non-ASCII Text * @see Requirement 4.5 - Decode RFC 2047 encoded-words in subject lines back to UTF-8 * * @example * ```typescript * const parser = new EmailParser(); * parser.decodeEncodedWord('=?UTF-8?B?SGVsbG8gV29ybGQ=?='); * // => 'Hello World' * * parser.decodeEncodedWord('=?UTF-8?Q?Hello_World?='); * // => 'Hello World' * * parser.decodeEncodedWord('plain text'); * // => 'plain text' * ``` */ decodeEncodedWord(encodedWord: string): string; /** * Decodes quoted-printable encoded data per RFC 2045 Section 6.7. * * Replaces =XX sequences with the corresponding byte value and * handles soft line breaks (=\r\n or =\n). * * @param data - The quoted-printable encoded data as Uint8Array * @returns The decoded bytes as Uint8Array * * @see RFC 2045 Section 6.7 - Quoted-Printable Content-Transfer-Encoding * @see Requirement 7.8 - Decode content based on Content-Transfer-Encoding * @see Requirement 14.9 - Decode body according to Content-Transfer-Encoding header * * @example * ```typescript * const parser = new EmailParser(); * const encoded = new TextEncoder().encode('Hello=20World'); * const decoded = parser.decodeQuotedPrintable(encoded); * // decoded contains bytes for 'Hello World' * ``` */ decodeQuotedPrintable(data: Uint8Array): Uint8Array; /** * Decodes base64 encoded data per RFC 2045 Section 6.8. * * Uses Node.js Buffer for decoding. Ignores whitespace in the input, * as line breaks in base64 encoded data are common per RFC 2045. * * @param data - The base64 encoded data as Uint8Array * @returns The decoded bytes as Uint8Array * * @see RFC 2045 Section 6.8 - Base64 Content-Transfer-Encoding * @see Requirement 7.8 - Decode content based on Content-Transfer-Encoding * @see Requirement 14.9 - Decode body according to Content-Transfer-Encoding header * * @example * ```typescript * const parser = new EmailParser(); * const encoded = new TextEncoder().encode('SGVsbG8gV29ybGQ='); * const decoded = parser.decodeBase64(encoded); * // decoded contains bytes for 'Hello World' * ``` */ decodeBase64(data: Uint8Array): Uint8Array; /** * Parses (decodes) the body of a MIME part based on its Content-Transfer-Encoding. * * Dispatches to the appropriate decoder: * - '7bit', '8bit', 'binary': returns body as-is (no decoding needed) * - 'quoted-printable': calls decodeQuotedPrintable() * - 'base64': calls decodeBase64() * * @param body - The raw body data as Uint8Array * @param contentType - The Content-Type of the body (for future use) * @param encoding - The Content-Transfer-Encoding to apply * @returns The decoded body as Uint8Array * * @see RFC 2045 Section 6 - Content-Transfer-Encoding Header Field * @see Requirement 7.8 - Decode content based on Content-Transfer-Encoding * @see Requirement 14.9 - Decode body according to Content-Transfer-Encoding header * * @example * ```typescript * const parser = new EmailParser(); * const ct = createContentType('text', 'plain'); * const body = new TextEncoder().encode('SGVsbG8='); * const decoded = parser.parseBody(body, ct, ContentTransferEncoding.Base64); * // decoded contains bytes for 'Hello' * ``` */ parseBody(body: Uint8Array, contentType: IContentType, encoding: ContentTransferEncoding): Uint8Array; /** * Parses a multipart MIME body into an array of IMimePart objects. * * Splits the body by the given boundary delimiter, parses each part's * headers and body, and recursively handles nested multipart structures. * Preamble text (before the first boundary) and epilogue text (after the * final boundary) are handled gracefully and ignored per RFC 2046. * * @param body - The raw multipart body as Uint8Array * @param boundary - The boundary string from the Content-Type header * @returns Array of parsed IMimePart objects * @throws {EmailError} With MALFORMED_MIME if the boundary is missing or body is malformed * * @see RFC 2046 Section 5.1 - Multipart Media Type * @see Requirement 6.7 - Parse nested multipart structures * @see Requirement 6.8 - Handle preamble and epilogue text * * @example * ```typescript * const parser = new EmailParser(); * const body = new TextEncoder().encode( * '--boundary\r\nContent-Type: text/plain\r\n\r\nHello\r\n--boundary--' * ); * const parts = parser.parseMultipart(body, 'boundary'); * // [{ contentType: { type: 'text', subtype: 'plain', ... }, body: ..., size: 5 }] * ``` */ parseMultipart(body: Uint8Array, boundary: string): IMimePart[]; /** * Parses a single MIME part (headers + body) into an IMimePart object. * * @param partContent - The raw content of a single MIME part (headers + blank line + body) * @returns The parsed IMimePart object */ private parseSinglePart; /** * Parses a Content-Disposition header value into an IContentDisposition object. * * @param dispositionString - The Content-Disposition header value * @returns The parsed IContentDisposition object */ private parseContentDisposition; /** * Escapes special regex characters in a string for use in a RegExp. * * @param str - The string to escape * @returns The escaped string safe for use in RegExp */ private escapeRegExp; /** * Parses a raw email (string or Uint8Array) into an IEmailMetadata object. * * Uses `PostalMime.parse()` as the core parsing engine, then converts * postal-mime's Email type to the IEmailMetadata interface. Handles both * CRLF and LF line endings (postal-mime handles this internally). * * @param rawEmail - The raw email as a string or Uint8Array * @returns The parsed IEmailMetadata object * @throws {EmailError} With PARSE_ERROR if parsing fails * * @see RFC 5322 - Internet Message Format * @see Requirement 14.5 - Handle both CRLF and LF line endings * * @example * ```typescript * const parser = new EmailParser(); * const metadata = await parser.parse(rawEmailString); * console.log(metadata.from.address); // "sender@example.com" * console.log(metadata.subject); // "Hello World" * ``` */ parse(rawEmail: string | Uint8Array): Promise; /** * Converts a postal-mime Email object to IEmailMetadata. * * @param email - The postal-mime parsed Email object * @returns The converted IEmailMetadata object */ private convertPostalEmailToMetadata; /** * Converts a postal-mime Address to an IMailbox (single mailbox). * Returns undefined if the address cannot be converted. */ private convertPostalAddressToMailbox; /** * Converts a postal-mime Address to an array of IMailbox objects. * Handles both individual mailboxes and group addresses. */ private convertPostalAddressToMailboxes; /** * Converts a postal-mime Mailbox to an IMailbox. */ private convertPostalMailboxToIMailbox; /** * Finds the first header value matching the given key (case-insensitive). */ private findHeader; /** * Finds all header values matching the given key (case-insensitive). */ private findAllHeaders; /** * Extracts custom/extension headers (X-* and other non-standard headers). * Standard RFC 5322 headers are excluded. */ private extractCustomHeaders; /** * Builds IMimePart[] from postal-mime's parsed email content. * Creates parts from text, html, and attachment content. */ private buildMimeParts; /** * Converts postal-mime attachments to IAttachmentMetadata[]. */ private convertAttachments; /** * Converts a single postal-mime Attachment to IAttachmentMetadata. */ private convertSingleAttachment; /** * Converts postal-mime attachment content to Uint8Array. */ private convertAttachmentContent; /** * Parses an attachment's MIME type string into an IContentType. */ private parseAttachmentContentType; } //# sourceMappingURL=emailParser.d.ts.map