Skip to content

Commit e60c907

Browse files
committed
more relaxed boundary validation
only throw error when building multipart body bytes show warning when parsing
1 parent 6e19299 commit e60c907

File tree

2 files changed

+36
-33
lines changed

2 files changed

+36
-33
lines changed

src/Multipart.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,14 +59,9 @@ export class Multipart implements Part {
5959
* @param parts The parts to include in the multipart
6060
* @param [boundary] The multipart boundary used to separate the parts. Randomly generated if not provided
6161
* @param [mediaType] The media type of the multipart. Defaults to "multipart/mixed"
62-
*
63-
* @throws {RangeError} If the boundary is invalid. A valid boundary is 1 to 70 characters long,
64-
* does not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space
6562
*/
6663
public constructor(public readonly parts: Part[], boundary: Uint8Array | string = crypto.randomUUID(), mediaType: string = "multipart/mixed") {
6764
this.#boundary = typeof boundary === "string" ? new TextEncoder().encode(boundary) : boundary;
68-
if (!Multipart.isValidBoundary(this.#boundary))
69-
throw new RangeError("Invalid boundary: must be 1 to 70 characters long, not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space");
7065
this.#mediaType = mediaType;
7166
this.setHeaders();
7267
}
@@ -121,13 +116,9 @@ export class Multipart implements Part {
121116

122117
/**
123118
* Set the boundary bytes used to separate the parts
124-
* @throws {RangeError} If the boundary is invalid. A valid boundary is 1 to 70 characters long,
125-
* does not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space
126119
*/
127120
public set boundary(boundary: Uint8Array | string) {
128121
this.#boundary = typeof boundary === "string" ? new TextEncoder().encode(boundary) : boundary;
129-
if (!Multipart.isValidBoundary(this.#boundary))
130-
throw new RangeError("Invalid boundary: must be 1 to 70 characters long, not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space");
131122
this.setHeaders();
132123
}
133124

@@ -149,8 +140,14 @@ export class Multipart implements Part {
149140
/**
150141
* Get the bytes of the body of this multipart. Includes all parts separated by the boundary.
151142
* Does not include the headers.
143+
*
144+
* @throws {RangeError} If the multipart boundary is invalid. A valid boundary is 1 to 70 characters long,
145+
* does not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space
152146
*/
153147
public get body(): Uint8Array {
148+
if (!Multipart.isValidBoundary(this.#boundary))
149+
throw new RangeError("Invalid boundary: must be 1 to 70 characters long, not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space");
150+
154151
const result: ArrayLike<number>[] = [];
155152
for (const part of this.parts) result.push(Multipart.DOUBLE_DASH, this.boundary, Multipart.CRLF, part.bytes(), Multipart.CRLF);
156153
result.push(Multipart.DOUBLE_DASH, this.boundary, Multipart.DOUBLE_DASH, Multipart.CRLF);
@@ -180,6 +177,9 @@ export class Multipart implements Part {
180177
* @param [mediaType] Multipart media type to pass to the constructor
181178
*/
182179
public static parseBody(data: Uint8Array, boundary: Uint8Array, mediaType?: string): Multipart {
180+
if (!Multipart.isValidBoundary(boundary))
181+
console.warn("Invalid boundary:", new TextDecoder().decode(boundary), "\nMust be 1 to 70 characters long, not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space");
182+
183183
const parts: Uint8Array[] = [];
184184
const fullBoundarySequence = new Uint8Array(Multipart.combineArrays([Multipart.DOUBLE_DASH, boundary, Multipart.CRLF]));
185185
const endBoundarySequence = new Uint8Array(Multipart.combineArrays([Multipart.DOUBLE_DASH, boundary, Multipart.DOUBLE_DASH, Multipart.CRLF]));
@@ -393,6 +393,9 @@ export class Multipart implements Part {
393393

394394
/**
395395
* Get the bytes of the headers and {@link body} of this multipart.
396+
*
397+
* @throws {RangeError} If the multipart boundary is invalid. A valid boundary is 1 to 70 characters long,
398+
* does not end with space, and may only contain: A-Z a-z 0-9 '()+_,-./:=? and space
396399
*/
397400
public bytes(): Uint8Array {
398401
const result: ArrayLike<number>[] = [];

test/Multipart.test.js

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,30 +27,6 @@ describe("Multipart", function () {
2727
expect(new TextDecoder().decode(multipart.boundary)).to.equal("empty-boundary");
2828
expect(multipart.mediaType).to.equal("multipart/mixed");
2929
});
30-
31-
it("should accept only valid boundaries", function () {
32-
expect(() => new Multipart([], "")).to.throw(RangeError, "Invalid boundary");
33-
expect(() => new Multipart([], " ")).to.throw(RangeError, "Invalid boundary");
34-
expect(() => new Multipart([], "a ")).to.throw(RangeError, "Invalid boundary");
35-
expect(() => new Multipart([], "0123456789".repeat(7) + "0")).to.throw(RangeError, "Invalid boundary");
36-
expect(() => new Multipart([], "foo!bar")).to.throw(RangeError, "Invalid boundary");
37-
38-
expect(() => new Multipart([], "a")).to.not.throw();
39-
expect(() => new Multipart([], "0123456789".repeat(7))).to.not.throw();
40-
expect(() => new Multipart([], "foo bar")).to.not.throw();
41-
expect(() => new Multipart([], "foo'bar")).to.not.throw();
42-
expect(() => new Multipart([], "foo(bar")).to.not.throw();
43-
expect(() => new Multipart([], "foo)bar")).to.not.throw();
44-
expect(() => new Multipart([], "foo+bar")).to.not.throw();
45-
expect(() => new Multipart([], "foo_bar")).to.not.throw();
46-
expect(() => new Multipart([], "foo,bar")).to.not.throw();
47-
expect(() => new Multipart([], "foo-bar")).to.not.throw();
48-
expect(() => new Multipart([], "foo.bar")).to.not.throw();
49-
expect(() => new Multipart([], "foo/bar")).to.not.throw();
50-
expect(() => new Multipart([], "foo:bar")).to.not.throw();
51-
expect(() => new Multipart([], "foo=bar")).to.not.throw();
52-
expect(() => new Multipart([], "foo?bar")).to.not.throw();
53-
});
5430
});
5531

5632
describe("parse", function () {
@@ -276,5 +252,29 @@ describe("Multipart", function () {
276252

277253
expect(new TextDecoder().decode(bytes)).to.equal(new TextDecoder().decode(expectedBytes));
278254
});
255+
256+
it("should accept only valid boundaries", function () {
257+
expect(() => new Multipart([], "").bytes()).to.throw(RangeError, "Invalid boundary");
258+
expect(() => new Multipart([], " ").bytes()).to.throw(RangeError, "Invalid boundary");
259+
expect(() => new Multipart([], "a ").bytes()).to.throw(RangeError, "Invalid boundary");
260+
expect(() => new Multipart([], "0123456789".repeat(7) + "0").bytes()).to.throw(RangeError, "Invalid boundary");
261+
expect(() => new Multipart([], "foo!bar").bytes()).to.throw(RangeError, "Invalid boundary");
262+
263+
expect(() => new Multipart([], "a").bytes()).to.not.throw();
264+
expect(() => new Multipart([], "0123456789".repeat(7)).bytes()).to.not.throw();
265+
expect(() => new Multipart([], "foo bar").bytes()).to.not.throw();
266+
expect(() => new Multipart([], "foo'bar").bytes()).to.not.throw();
267+
expect(() => new Multipart([], "foo(bar").bytes()).to.not.throw();
268+
expect(() => new Multipart([], "foo)bar").bytes()).to.not.throw();
269+
expect(() => new Multipart([], "foo+bar").bytes()).to.not.throw();
270+
expect(() => new Multipart([], "foo_bar").bytes()).to.not.throw();
271+
expect(() => new Multipart([], "foo,bar").bytes()).to.not.throw();
272+
expect(() => new Multipart([], "foo-bar").bytes()).to.not.throw();
273+
expect(() => new Multipart([], "foo.bar").bytes()).to.not.throw();
274+
expect(() => new Multipart([], "foo/bar").bytes()).to.not.throw();
275+
expect(() => new Multipart([], "foo:bar").bytes()).to.not.throw();
276+
expect(() => new Multipart([], "foo=bar").bytes()).to.not.throw();
277+
expect(() => new Multipart([], "foo?bar").bytes()).to.not.throw();
278+
});
279279
});
280280
});

0 commit comments

Comments
 (0)