Skip to content

Commit

Permalink
fix binary attachments (#29)
Browse files Browse the repository at this point in the history
* add test for attachments

* fix tests

* compare text

* upgrade deps

* fix encoding LB

* add better test case

* trim all strings

* fix lb

* add binary attachment test

* fix file path

* fix fmt

* ff

* add more tests

* encode all non text attachment to base64
  • Loading branch information
mathe42 authored Jun 9, 2022
1 parent 7da737e commit afc1652
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
45 changes: 31 additions & 14 deletions client/basic/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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");
Expand Down
10 changes: 5 additions & 5 deletions config/mail/attachments.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { base64Decode } from "./encoding.ts";
import { base64Encode } from "./encoding.ts";

interface baseAttachment {
contentType: string;
Expand All @@ -16,7 +16,7 @@ export type Attachment =
export type ResolvedAttachment =
& (
| textAttachment
| arrayBufferLikeAttachment
| base64Attachment
)
& baseAttachment;

Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion config/mail/encoding.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export { base64Decode } from "../../deps.ts";
export { base64Decode, base64Encode } from "../../deps.ts";

const encoder = new TextEncoder();

Expand Down
9 changes: 6 additions & 3 deletions deps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
export {
BufReader,
BufWriter,
} from "https://deno.land/[email protected]/io/buffer.ts";
export { TextProtoReader } from "https://deno.land/[email protected]/textproto/mod.ts";
export { decode as base64Decode } from "https://deno.land/[email protected]/encoding/base64.ts";
} from "https://deno.land/[email protected]/io/buffer.ts";
export { TextProtoReader } from "https://deno.land/[email protected]/textproto/mod.ts";
export {
decode as base64Decode,
encode as base64Encode,
} from "https://deno.land/[email protected]/encoding/base64.ts";
135 changes: 135 additions & 0 deletions test/e2e/attachment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { clearEmails, getEmails } from "../fake-smtp.ts";
import { assertEquals } from "https://deno.land/[email protected]/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: "[email protected]",
to: "[email protected]",
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: "[email protected]",
to: "[email protected]",
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: "[email protected]",
to: "[email protected]",
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();
});
Binary file added test/e2e/attachments/image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions test/e2e/attachments/text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hello World!
With LB!

0 comments on commit afc1652

Please sign in to comment.