Skip to content

Commit

Permalink
Make the boundary param in Multipart Conten-Type quoted as needed…
Browse files Browse the repository at this point in the history
… per RFC 2616 (#20)
  • Loading branch information
zefir-git authored Oct 7, 2024
2 parents 733aff5 + 7b1cc9c commit a9d8d4d
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 2 deletions.
31 changes: 29 additions & 2 deletions src/Multipart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -434,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);
}
}
24 changes: 24 additions & 0 deletions test/Multipart.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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([], "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([], "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"');
});
});
});

0 comments on commit a9d8d4d

Please sign in to comment.