diff --git a/.github/workflows/ci2.yml b/.github/workflows/ci2.yml index 7d9c5c4..17a4389 100644 --- a/.github/workflows/ci2.yml +++ b/.github/workflows/ci2.yml @@ -14,6 +14,6 @@ jobs: with: deno-version: v1.x - run: docker run -d -p 1080:1080 -p 1025:1025 reachfive/fake-smtp-server - - run: deno test --unstable -A ./test/e2e/basic.test.ts + - run: deno test --unstable -A ./test/e2e/ - run: deno fmt --check - run: deno lint diff --git a/client/basic/client.ts b/client/basic/client.ts index b7d8773..68713b8 100644 --- a/client/basic/client.ts +++ b/client/basic/client.ts @@ -183,14 +183,15 @@ export class SMTPClient { }, ); - const dec = new TextDecoder(); + // const dec = new TextDecoder(); config.attachments.map((v) => { - if (v.encoding === "text") return v.content; + return v.content; + // if (v.encoding === "text") return v.content; - const arr = new Uint8Array(v.content); + // const arr = new Uint8Array(v.content); - return dec.decode(arr); + // return dec.decode(arr); }).join("\n").replace( new RegExp("--attachment([0-9]+)", "g"), (_, numb) => { @@ -260,27 +261,43 @@ export class SMTPClient { await this.#connection.writeCmd( "Content-Disposition: attachment; filename=" + attachment.filename, - "\r\n", ); - if (attachment.encoding === "binary") { - await this.#connection.writeCmd("Content-Transfer-Encoding: binary"); + if (attachment.encoding === "base64") { + await this.#connection.writeCmd( + "Content-Transfer-Encoding: base64", + "\r\n", + ); - if ( - attachment.content instanceof ArrayBuffer || - attachment.content instanceof SharedArrayBuffer + for ( + let line = 0; + line < Math.ceil(attachment.content.length / 75); + line++ ) { - await this.#connection.writeCmdBinary( - new Uint8Array(attachment.content), + const lineOfBase64 = attachment.content.slice( + line * 75, + (line + 1) * 75, ); - } else { - await this.#connection.writeCmdBinary(attachment.content); + + await this.#connection.writeCmd(lineOfBase64); } + // if ( + // attachment.content instanceof ArrayBuffer || + // attachment.content instanceof SharedArrayBuffer + // ) { + // await this.#connection.writeCmdBinary( + // new Uint8Array(attachment.content), + // ); + // } else { + // await this.#connection.writeCmdBinary(attachment.content); + // } + await this.#connection.writeCmd("\r\n"); } else if (attachment.encoding === "text") { await this.#connection.writeCmd( "Content-Transfer-Encoding: quoted-printable", + "\r\n", ); await this.#connection.writeCmd(attachment.content, "\r\n"); diff --git a/config/mail/attachments.ts b/config/mail/attachments.ts index 3fd43fa..9bbe69e 100644 --- a/config/mail/attachments.ts +++ b/config/mail/attachments.ts @@ -1,4 +1,4 @@ -import { base64Decode } from "./encoding.ts"; +import { base64Encode } from "./encoding.ts"; interface baseAttachment { contentType: string; @@ -16,7 +16,7 @@ export type Attachment = export type ResolvedAttachment = & ( | textAttachment - | arrayBufferLikeAttachment + | base64Attachment ) & baseAttachment; @@ -28,12 +28,12 @@ type arrayBufferLikeAttachment = { }; export function resolveAttachment(attachment: Attachment): ResolvedAttachment { - if (attachment.encoding === "base64") { + if (attachment.encoding === "binary") { return { filename: attachment.filename, contentType: attachment.contentType, - encoding: "binary", - content: base64Decode(attachment.content), + encoding: "base64", + content: base64Encode(attachment.content), }; } else { return attachment; diff --git a/config/mail/encoding.ts b/config/mail/encoding.ts index 240af8f..6883114 100644 --- a/config/mail/encoding.ts +++ b/config/mail/encoding.ts @@ -1,4 +1,4 @@ -export { base64Decode } from "../../deps.ts"; +export { base64Decode, base64Encode } from "../../deps.ts"; const encoder = new TextEncoder(); diff --git a/deps.ts b/deps.ts index f7665ee..a2e4db0 100644 --- a/deps.ts +++ b/deps.ts @@ -1,6 +1,9 @@ export { BufReader, BufWriter, -} from "https://deno.land/std@0.130.0/io/buffer.ts"; -export { TextProtoReader } from "https://deno.land/std@0.130.0/textproto/mod.ts"; -export { decode as base64Decode } from "https://deno.land/std@0.130.0/encoding/base64.ts"; +} from "https://deno.land/std@0.142.0/io/buffer.ts"; +export { TextProtoReader } from "https://deno.land/std@0.142.0/textproto/mod.ts"; +export { + decode as base64Decode, + encode as base64Encode, +} from "https://deno.land/std@0.142.0/encoding/base64.ts"; diff --git a/test/e2e/attachment.test.ts b/test/e2e/attachment.test.ts new file mode 100644 index 0000000..cfd7acf --- /dev/null +++ b/test/e2e/attachment.test.ts @@ -0,0 +1,135 @@ +import { clearEmails, getEmails } from "../fake-smtp.ts"; +import { assertEquals } from "https://deno.land/std@0.136.0/testing/asserts.ts"; +import { SMTPClient } from "../../mod.ts"; + +function wait(to = 10000) { + return new Promise((res) => setTimeout(res, to)); +} + +Deno.test("test text attachment", async () => { + await clearEmails(); + + const client = new SMTPClient({ + debug: { + allowUnsecure: true, + // log: true, + noStartTLS: true, + }, + connection: { + hostname: "localhost", + port: 1025, + tls: false, + }, + }); + + const content = "A\r\nHello\nWorld\rjmjkjj"; + + await client.send({ + from: "me@denomailer.example", + to: "you@denomailer.example", + subject: "testing", + content: "test", + attachments: [ + { + content, + filename: "text.txt", + encoding: "text", + contentType: "text/plain", + }, + ], + }); + + const mails = await getEmails(); + const data = new Uint8Array(mails[0].attachments[0].content.data); + assertEquals(new TextDecoder().decode(data).trim(), content.trim()); + await client.close(); +}); + +Deno.test("test binary attachment", async () => { + await clearEmails(); + + const client = new SMTPClient({ + debug: { + allowUnsecure: true, + // log: true, + noStartTLS: true, + }, + connection: { + hostname: "localhost", + port: 1025, + tls: false, + }, + }); + + const content = await Deno.readFile( + Deno.cwd() + "/test/e2e/attachments/image.png", + ); + + await client.send({ + from: "me@denomailer.example", + to: "you@denomailer.example", + subject: "testing", + content: "test", + attachments: [ + { + content, + filename: "text.txt", + encoding: "binary", + contentType: "image/png", + }, + ], + }); + + await wait(2000); + + const mails = await getEmails(); + const data = new Uint8Array(mails[0].attachments[0].content.data); + assertEquals(data, content); + await client.close(); +}); + +Deno.test("test more text attachment", async () => { + await clearEmails(); + + const client = new SMTPClient({ + debug: { + allowUnsecure: true, + // log: true, + noStartTLS: true, + }, + connection: { + hostname: "localhost", + port: 1025, + tls: false, + }, + }); + + const content = [ + "abc\ndef\nghi", + "abc\rdef\rghi", + "abc\ndef\rghi", + "abc\rdef\ndhi", + ]; + + for (let i = 0; i < content.length; i++) { + await client.send({ + from: "me@denomailer.example", + to: "you@denomailer.example", + subject: "testing", + content: "test", + attachments: [ + { + content: content[i], + filename: "text.txt", + encoding: "text", + contentType: "text/plain", + }, + ], + }); + + const mails = await getEmails(); + const data = new Uint8Array(mails[0].attachments[0].content.data); + assertEquals(new TextDecoder().decode(data).trim(), content[i].trim()); + } + await client.close(); +}); diff --git a/test/e2e/attachments/image.png b/test/e2e/attachments/image.png new file mode 100644 index 0000000..0508124 Binary files /dev/null and b/test/e2e/attachments/image.png differ diff --git a/test/e2e/attachments/text.txt b/test/e2e/attachments/text.txt new file mode 100644 index 0000000..984f82d --- /dev/null +++ b/test/e2e/attachments/text.txt @@ -0,0 +1,2 @@ +Hello World! +With LB! \ No newline at end of file