Skip to content

Commit a9d8d4d

Browse files
authored
Make the boundary param in Multipart Conten-Type quoted as needed per RFC 2616 (#20)
2 parents 733aff5 + 7b1cc9c commit a9d8d4d

File tree

2 files changed

+53
-2
lines changed

2 files changed

+53
-2
lines changed

src/Multipart.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ export class Multipart implements Part {
2424
* @internal
2525
*/
2626
public static readonly SP = 0x20;
27+
/**
28+
* Horizontal tab (`\t`) ASCII code
29+
* @internal
30+
*/
31+
public static readonly HT = 0x09;
2732
/**
2833
* Carriage return (`\r`) ASCII code
2934
* @internal
@@ -284,7 +289,7 @@ export class Multipart implements Part {
284289
const byte = data[currentEndOfBoundaryIndex];
285290
if (byte === Multipart.CR && data[currentEndOfBoundaryIndex + 1] === Multipart.LF)
286291
return [boundaryStartIndex, currentEndOfBoundaryIndex + 2];
287-
if (byte === Multipart.SP || byte === 0x09) {
292+
if (byte === Multipart.SP || byte === Multipart.HT) {
288293
currentEndOfBoundaryIndex++;
289294
continue;
290295
}
@@ -434,10 +439,32 @@ export class Multipart implements Part {
434439
return Multipart.combineArrays(result);
435440
}
436441

442+
private static boundaryShouldBeQuoted(boundary: Uint8Array): boolean {
443+
for (const byte of boundary) {
444+
if (
445+
byte === Multipart.HT
446+
|| byte === Multipart.SP
447+
|| byte === 0x22 // "
448+
|| byte === 0x28 // (
449+
|| byte === 0x29 // )
450+
|| byte === 0x2c // ,
451+
|| byte === 0x2f // /
452+
|| (byte >= Multipart.COLON && byte <= 0x40) // :;<=>@
453+
|| (byte >= 0x5b && byte <= 0x5d) // [\]
454+
|| byte === 0x7b // {
455+
|| byte === 0x7d // }
456+
) return true;
457+
}
458+
return false;
459+
}
460+
437461
/**
438462
* Set the `Content-Type` header of this multipart based on {@link mediaType} and {@link boundary}.
439463
*/
440464
private setHeaders() {
441-
this.headers.set("Content-Type", this.#mediaType + "; boundary=" + new TextDecoder().decode(this.#boundary));
465+
const shouldQuoteBoundary = Multipart.boundaryShouldBeQuoted(this.#boundary);
466+
const boundaryString = new TextDecoder().decode(this.#boundary);
467+
const boundary = shouldQuoteBoundary ? `"${boundaryString.replace(/"/g, '\\"')}"` : boundaryString;
468+
this.headers.set("Content-Type", this.#mediaType + "; boundary=" + boundary);
442469
}
443470
}

test/Multipart.test.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,4 +364,28 @@ describe("Multipart", function () {
364364
expect(() => new Multipart([], "foo?bar").bytes()).to.not.throw();
365365
});
366366
});
367+
368+
describe("#headers", function () {
369+
it("should have the Content-Type boundary parameters in quotes as per RFC 2616", function () {
370+
expect(new Multipart([], "foobar", "multipart/mixed").headers.get("content-type")).to.equal("multipart/mixed; boundary=foobar");
371+
expect(new Multipart([], "foo\tbar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo\tbar"');
372+
expect(new Multipart([], "foo bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo bar"');
373+
expect(new Multipart([], 'foo"bar', "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo\\"bar"');
374+
expect(new Multipart([], "foo(bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo(bar"');
375+
expect(new Multipart([], "foo)bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo)bar"');
376+
expect(new Multipart([], "foo,bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo,bar"');
377+
expect(new Multipart([], "foo:bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo:bar"');
378+
expect(new Multipart([], "foo;bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo;bar"');
379+
expect(new Multipart([], "foo<bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo<bar"');
380+
expect(new Multipart([], "foo=bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo=bar"');
381+
expect(new Multipart([], "foo>bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo>bar"');
382+
expect(new Multipart([], "foo?bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo?bar"');
383+
expect(new Multipart([], "foo@bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo@bar"');
384+
expect(new Multipart([], "foo[bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo[bar"');
385+
expect(new Multipart([], "foo\\bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo\\bar"');
386+
expect(new Multipart([], "foo]bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo]bar"');
387+
expect(new Multipart([], "foo{bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo{bar"');
388+
expect(new Multipart([], "foo}bar", "multipart/mixed").headers.get("content-type")).to.equal('multipart/mixed; boundary="foo}bar"');
389+
});
390+
});
367391
});

0 commit comments

Comments
 (0)