aboutsummaryrefslogtreecommitdiff
path: root/deno/mail-relay/mail-parsing.ts
diff options
context:
space:
mode:
authorYuqian Yang <crupest@crupest.life>2025-06-30 14:25:02 +0800
committerYuqian Yang <crupest@crupest.life>2025-06-30 14:25:02 +0800
commite18d101cae1dfcef29abd102d2908d429f4688d5 (patch)
tree49c0b1d1c237c674fe603db23d2e174acdea6979 /deno/mail-relay/mail-parsing.ts
parent66e2d76b75ed04ae8a43baefdb970f4cb89c5925 (diff)
downloadcrupest-e18d101cae1dfcef29abd102d2908d429f4688d5.tar.gz
crupest-e18d101cae1dfcef29abd102d2908d429f4688d5.tar.bz2
crupest-e18d101cae1dfcef29abd102d2908d429f4688d5.zip
mail: revert removing.
Diffstat (limited to 'deno/mail-relay/mail-parsing.ts')
-rw-r--r--deno/mail-relay/mail-parsing.ts144
1 files changed, 0 insertions, 144 deletions
diff --git a/deno/mail-relay/mail-parsing.ts b/deno/mail-relay/mail-parsing.ts
deleted file mode 100644
index 8e9697d..0000000
--- a/deno/mail-relay/mail-parsing.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import emailAddresses from "email-addresses";
-
-class MailParsingError extends Error {}
-
-function parseHeaderSection(section: string) {
- const headers = [] as [key: string, value: string][];
-
- let field: string | null = null;
- let lineNumber = 1;
-
- const handleField = () => {
- if (field == null) return;
- const sepPos = field.indexOf(":");
- if (sepPos === -1) {
- throw new MailParsingError(
- `Expect ':' in the header field line: ${field}`,
- );
- }
- headers.push([field.slice(0, sepPos).trim(), field.slice(sepPos + 1)]);
- field = null;
- };
-
- for (const line of section.trimEnd().split(/\r?\n|\r/)) {
- if (line.match(/^\s/)) {
- if (field == null) {
- throw new MailParsingError("Header section starts with a space.");
- }
- field += line;
- } else {
- handleField();
- field = line;
- }
- lineNumber += 1;
- }
-
- handleField();
-
- return headers;
-}
-
-function findFirst(fields: readonly [string, string][], key: string) {
- for (const [k, v] of fields) {
- if (key.toLowerCase() === k.toLowerCase()) return v;
- }
- return undefined;
-}
-
-function findMessageId(fields: readonly [string, string][]) {
- const messageIdField = findFirst(fields, "message-id");
- if (messageIdField == null) return undefined;
-
- const match = messageIdField.match(/\<(.*?)\>/);
- if (match != null) {
- return match[1];
- } else {
- console.warn(`Invalid syntax in header 'message-id': ${messageIdField}`);
- return undefined;
- }
-}
-
-function findDate(fields: readonly [string, string][]) {
- const dateField = findFirst(fields, "date");
- if (dateField == null) return undefined;
-
- const date = new Date(dateField);
- if (isNaN(date.getTime())) {
- console.warn(`Invalid date string in header 'date': ${dateField}`);
- return undefined;
- }
- return date;
-}
-
-function findFrom(fields: readonly [string, string][]) {
- const fromField = findFirst(fields, "from");
- if (fromField == null) return undefined;
-
- const addr = emailAddresses.parseOneAddress(fromField);
- return addr?.type === "mailbox" ? addr.address : undefined;
-}
-
-function findRecipients(fields: readonly [string, string][]) {
- const headers = ["to", "cc", "bcc", "x-original-to"];
- const recipients = new Set<string>();
- for (const [key, value] of fields) {
- if (headers.includes(key.toLowerCase())) {
- emailAddresses
- .parseAddressList(value)
- ?.flatMap((a) => (a.type === "mailbox" ? a : a.addresses))
- ?.forEach(({ address }) => recipients.add(address));
- }
- }
- return recipients;
-}
-
-function parseSections(raw: string) {
- const twoEolMatch = raw.match(/(\r?\n)(\r?\n)/);
- if (twoEolMatch == null) {
- throw new MailParsingError(
- "No header/body section separator (2 successive EOLs) found.",
- );
- }
-
- const [eol, sep] = [twoEolMatch[1], twoEolMatch[2]];
-
- if (eol !== sep) {
- console.warn("Different EOLs (\\r\\n, \\n) found.");
- }
-
- return {
- header: raw.slice(0, twoEolMatch.index!),
- body: raw.slice(twoEolMatch.index! + eol.length + sep.length),
- eol,
- sep,
- };
-}
-
-export type ParsedMail = Readonly<{
- header: string;
- body: string;
- sep: string;
- eol: string;
- headers: readonly [string, string][];
- messageId: string | undefined;
- date: Date | undefined;
- from: string | undefined;
- recipients: readonly string[];
-}>;
-
-export function simpleParseMail(raw: string): ParsedMail {
- const sections = Object.freeze(parseSections(raw));
- const headers = Object.freeze(parseHeaderSection(sections.header));
- const messageId = findMessageId(headers);
- const date = findDate(headers);
- const from = findFrom(headers);
- const recipients = Object.freeze([...findRecipients(headers)]);
- return Object.freeze({
- ...sections,
- headers,
- messageId,
- date,
- from,
- recipients,
- });
-}