diff options
Diffstat (limited to 'services/docker/mail-server/aws-sendmail/mail.ts')
-rw-r--r-- | services/docker/mail-server/aws-sendmail/mail.ts | 82 |
1 files changed, 62 insertions, 20 deletions
diff --git a/services/docker/mail-server/aws-sendmail/mail.ts b/services/docker/mail-server/aws-sendmail/mail.ts index de7942f..82902d2 100644 --- a/services/docker/mail-server/aws-sendmail/mail.ts +++ b/services/docker/mail-server/aws-sendmail/mail.ts @@ -17,16 +17,39 @@ export type MailDeliverState = | MailDeliverStateDelivered | MailDeliverStateError; +class MailParseError extends Error { + constructor( + message: string, + public readonly mail: Mail, + options?: ErrorOptions, + ) { + super(message, options); + } +} + class MailDeliverError extends Error { constructor( message: string, - options: ErrorOptions, public readonly mail: Mail, + options?: ErrorOptions, ) { super(message, options); } } +const eolNames = new Map([ + ["\n", "\\n"], + ["\r", "\\r"], + ["\n\r", "\\n\\r"], +]); + +interface ParsedMail { + headerStr: string; + bodyStr: string; + sepStr: string; + eol: string; +} + export class Mail { messageId: string | null = null; awsMessageId: string | null = null; @@ -45,44 +68,63 @@ export class Mail { ): never { const error = new MailDeliverError( `Failed to deliver mail to ${deliverer.destination}: ${reason}`, - { cause }, this, + { cause }, ); this.deliverState = { "kind": "error", deliverer, error }; throw error; } - encodeUtf8(): Uint8Array { + toUtf8Bytes(): Uint8Array { const utf8Encoder = new TextEncoder(); - // TODO: A problem here is if mail is VERY long, this will block for a long time. - // Maybe some task queue can be used. return utf8Encoder.encode(this.raw); } - getRawBase64(): string { + toBase64(): string { return encodeBase64(this.raw); } - appendHeaders( - rawMail: string, - headers: [key: string, value: string][], - ): string { - const separatorMatch = rawMail.match(/(\r\n|\n)(\r\n|\n)/); - if (separatorMatch == null) { - throw new Error( + simpleParse(): ParsedMail { + const twoEolMatch = this.raw.match(/(\r\n|\n|\r)(\r\n|\n|\r)/); + // "\r\n" is a false positive. + if (twoEolMatch == null || twoEolMatch[0] === "\r\n") { + throw new MailParseError( "No header/body separator (2 successive EOLs) found. Cannot append headers.", + this, + ); + } + + const [eol, sepStr] = [twoEolMatch[1], twoEolMatch[2]]; + + if (eol !== sepStr) { + getLogger().warn( + `Different EOLs (${eolNames.get(eol)} \ + and ${eolNames.get(sepStr)}) found in mail.`, ); } - if (separatorMatch[1] !== separatorMatch[2]) { - getLogger().warn("Different EOLs (\\r\\n and \\n) found in mail!"); + return { + headerStr: this.raw.slice(0, twoEolMatch.index! + eol.length), + bodyStr: this.raw.slice(twoEolMatch.index! + eol.length + sepStr.length), + sepStr, + eol, + }; + } + + simpleParseHeaders(): [key: string, value: string][] { + const { headerStr } = this.simpleParse(); + const rawLines = headerStr.split(/\r\n|\n|\r/); + const headerLines = []; + for (const l of rawLines) { + } + } - const headerStr = headers.map(([k, v]) => `${k}: ${v}${separatorMatch[1]}`) - .join(""); - const endOfHeadersIndex = separatorMatch.index! + separatorMatch[1].length; - return rawMail.slice(0, endOfHeadersIndex) + headerStr + - rawMail.slice(endOfHeadersIndex); + appendHeaders(headers: [key: string, value: string][]) { + const { headerStr, bodyStr, sepStr, eol } = this.simpleParse(); + const newHeaderStr = headerStr + + headers.map(([k, v]) => `${k}: ${v}${eol}`).join(""); + this.raw = newHeaderStr + sepStr + bodyStr; } } |