From ee7e1313d565526ba9effc08e0b00d31ef43f0e0 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 7 Oct 2024 14:30:08 +0300 Subject: [PATCH 1/4] define htab (HT) const --- src/Multipart.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Multipart.ts b/src/Multipart.ts index def5eb1..6b7966f 100644 --- a/src/Multipart.ts +++ b/src/Multipart.ts @@ -24,6 +24,11 @@ export class Multipart implements Part { * @internal */ public static readonly SP = 0x20; + /** + * Horizontal tab (`\t`) ASCII code + * @internal + */ + public static readonly HT = 0x09; /** * Carriage return (`\r`) ASCII code * @internal @@ -284,7 +289,7 @@ export class Multipart implements Part { const byte = data[currentEndOfBoundaryIndex]; if (byte === Multipart.CR && data[currentEndOfBoundaryIndex + 1] === Multipart.LF) return [boundaryStartIndex, currentEndOfBoundaryIndex + 2]; - if (byte === Multipart.SP || byte === 0x09) { + if (byte === Multipart.SP || byte === Multipart.HT) { currentEndOfBoundaryIndex++; continue; } From ed094ce260c4967e79fac20a8c404a5e589a570b Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 7 Oct 2024 14:31:00 +0300 Subject: [PATCH 2/4] quote Content-Type boundary param if needed as per RFC 2616 --- src/Multipart.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Multipart.ts b/src/Multipart.ts index 6b7966f..1439ab9 100644 --- a/src/Multipart.ts +++ b/src/Multipart.ts @@ -439,10 +439,32 @@ export class Multipart implements Part { return Multipart.combineArrays(result); } + private static boundaryShouldBeQuoted(boundary: Uint8Array): boolean { + for (const byte of boundary) { + if ( + byte === Multipart.HT + || byte === Multipart.SP + || byte === 0x22 + || byte === 0x28 + || byte === 0x29 + || byte === 0x2c + || byte === 0x2f + || (byte >= Multipart.COLON && byte <= 0x40) + || (byte >= 0x5b && byte <= 0x5d) + || byte === 0x7b + || byte === 0x7d + ) return true; + } + return false; + } + /** * Set the `Content-Type` header of this multipart based on {@link mediaType} and {@link boundary}. */ private setHeaders() { - this.headers.set("Content-Type", this.#mediaType + "; boundary=" + new TextDecoder().decode(this.#boundary)); + const shouldQuoteBoundary = Multipart.boundaryShouldBeQuoted(this.#boundary); + const boundaryString = new TextDecoder().decode(this.#boundary); + const boundary = shouldQuoteBoundary ? `"${boundaryString.replace(/"/g, '\\"')}"` : boundaryString; + this.headers.set("Content-Type", this.#mediaType + "; boundary=" + boundary); } } From b8835f4e2555586283ae03b7d5b06b2d293741e2 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 7 Oct 2024 14:41:55 +0300 Subject: [PATCH 3/4] quoted boundary tests --- test/Multipart.test.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/Multipart.test.js b/test/Multipart.test.js index 4e1aeee..17ddb86 100644 --- a/test/Multipart.test.js +++ b/test/Multipart.test.js @@ -364,4 +364,28 @@ describe("Multipart", function () { expect(() => new Multipart([], "foo?bar").bytes()).to.not.throw(); }); }); + + describe("#headers", function () { + it("should have the Content-Type boundary parameters in quotes as per RFC 2616", function () { + expect(new Multipart([], "foobar", "multipart/mixed").headers.get("content-type")).to.equal("multipart/mixed; boundary=foobar"); + expect(new Multipart([], "foo\tbar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo\tbar"'); + expect(new Multipart([], "foo bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo bar"'); + expect(new Multipart([], 'foo"bar', "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo\\"bar"'); + expect(new Multipart([], "foo(bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo(bar"'); + expect(new Multipart([], "foo)bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo)bar"'); + expect(new Multipart([], "foo,bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo,bar"'); + expect(new Multipart([], "foo:bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo:bar"'); + expect(new Multipart([], "foo;bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo;bar"'); + expect(new Multipart([], "foobar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo>bar"'); + expect(new Multipart([], "foo?bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo?bar"'); + expect(new Multipart([], "foo@bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo@bar"'); + expect(new Multipart([], "foo[bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo[bar"'); + expect(new Multipart([], "foo\\bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo\\bar"'); + expect(new Multipart([], "foo]bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo]bar"'); + expect(new Multipart([], "foo{bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo{bar"'); + expect(new Multipart([], "foo}bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo}bar"'); + }); + }); }); From 7b1cc9ce8851776e4899a134d7cbc9988cb1d3f4 Mon Sep 17 00:00:00 2001 From: Zefir Kirilov Date: Mon, 7 Oct 2024 14:49:16 +0300 Subject: [PATCH 4/4] add comments to clarify the character for each byte tested --- src/Multipart.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Multipart.ts b/src/Multipart.ts index 1439ab9..018b6cf 100644 --- a/src/Multipart.ts +++ b/src/Multipart.ts @@ -444,15 +444,15 @@ export class Multipart implements Part { if ( byte === Multipart.HT || byte === Multipart.SP - || byte === 0x22 - || byte === 0x28 - || byte === 0x29 - || byte === 0x2c - || byte === 0x2f - || (byte >= Multipart.COLON && byte <= 0x40) - || (byte >= 0x5b && byte <= 0x5d) - || byte === 0x7b - || byte === 0x7d + || byte === 0x22 // " + || byte === 0x28 // ( + || byte === 0x29 // ) + || byte === 0x2c // , + || byte === 0x2f // / + || (byte >= Multipart.COLON && byte <= 0x40) // :;<=>@ + || (byte >= 0x5b && byte <= 0x5d) // [\] + || byte === 0x7b // { + || byte === 0x7d // } ) return true; } return false;