From b5bb27e1b20fcd1f6e834696ba01221d2af7a41c Mon Sep 17 00:00:00 2001 From: Danny Morabito Date: Thu, 28 Nov 2024 23:53:42 +0100 Subject: [PATCH] handle email mime types (fixes #5), plus fix email display in letter view page --- src/lib/letter.ts | 70 +++++++++++++++++++++++++++- src/routes/letters/[id]/+page.svelte | 4 +- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/lib/letter.ts b/src/lib/letter.ts index 05c9baa..8a512cc 100644 --- a/src/lib/letter.ts +++ b/src/lib/letter.ts @@ -2,6 +2,66 @@ import NDK, { NDKEvent, type NDKEventId, NDKKind, type NDKUser } from '@nostr-de import { isValidNip05 } from '$lib/utils.svelte'; import { TokenInfoWithMailSubscriptionDuration } from '@arx/utils'; +function parseTextPlainContent(mimeMessage: string) { + const boundary = mimeMessage.split('\n')[0]; + + // --7408fffb1a494ae39c2dde2c60878849 + // Content-Type: text/plain + // Content-Transfer-Encoding: 7bit + // + // test + // --7408fffb1a494ae39c2dde2c60878849 + // Content-Type: text/html + // Content-Transfer-Encoding: 7bit + + const boundaries = mimeMessage.split(boundary + '\n'); + boundaries.shift(); + + const bodies = []; + + for (const body of boundaries) { + const headers: Record = {}; + const lines = body.split('\r\n'); + let isLookingForHeaders = true; + let content = ''; + for (const line of lines) { + if (line.trim() === '') { + isLookingForHeaders = false; + content += line + '\n'; + } else { + if (!isLookingForHeaders) { + content += line + '\n'; + continue; + } + const [key, value] = line.split(': '); + headers[key.toLowerCase()] = value; + } + } + bodies.push({ headers, content: content.trim() }); + } + + if (bodies.length === 0) return mimeMessage; + + let chosenBody = bodies.find((body) => body.headers['Content-Type'] === 'text/plain'); + if (!chosenBody) chosenBody = bodies[0]; + + const encoding = chosenBody.headers['content-transfer-encoding']; + if (!encoding) return chosenBody.content; // assume plain text + + switch (encoding.toLowerCase()) { + case '7bit': + return chosenBody.content; + case 'base64': + return atob(chosenBody.content.replaceAll('\n', '')); + case 'quoted-printable': + return chosenBody.content + .replace(/=\r\n/g, '') + .replace(/=([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16))); + default: + return `Cannot decode ${encoding} encoded message. This is a bug, please report it.`; + } +} + export class Letter { id: NDKEventId; subject: string; @@ -28,13 +88,19 @@ export class Letter { ): Promise { if (decryptedMessage.kind !== NDKKind.Article) throw new Error('Not a letter'); + let rawContent = decryptedMessage.content; + let content = ''; + let email = decryptedMessage.tags.find((t) => t[0] === 'email:from')?.[1]; + if (rawContent.startsWith('--') && email) content = parseTextPlainContent(rawContent); + else content = rawContent; + const letter = new Letter(); letter.id = encryptedMessage.id; letter.subject = decryptedMessage.tags.find((t) => t[0] === 'subject')?.[1] ?? 'No subject'; letter.from = decryptedMessage.author; - letter.content = decryptedMessage.content; + letter.content = content; letter.date = new Date(decryptedMessage.created_at! * 1000); - letter.emailAddress = decryptedMessage.tags.find((t) => t[0] === 'email:from')?.[1]; + letter.emailAddress = email; let stampToken = decryptedMessage.tags.find((t) => t[0] === 'stamp')?.[1]; if (stampToken) { try { diff --git a/src/routes/letters/[id]/+page.svelte b/src/routes/letters/[id]/+page.svelte index b5abb1c..e5be463 100644 --- a/src/routes/letters/[id]/+page.svelte +++ b/src/routes/letters/[id]/+page.svelte @@ -70,13 +70,13 @@
From: - +
To:
{#each letter.recipients as recipient} - + {/each}