Skip to content

Commit b916743

Browse files
committed
fix for when payload ends with terminating boundary
fixes #48
1 parent 77fdd82 commit b916743

File tree

2 files changed

+130
-17
lines changed

2 files changed

+130
-17
lines changed

src/__test__/PatchResolver.spec.js

Lines changed: 112 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,7 @@ global.TextDecoder = TextDecoder;
77
function getMultiPartResponse(data, boundary) {
88
const json = JSON.stringify(data);
99

10-
return [
11-
'Content-Type: application/json',
12-
'',
13-
json,
14-
`--${boundary}\r\n`,
15-
].join('\r\n');
10+
return ['Content-Type: application/json', '', json, `--${boundary}\r\n`].join('\r\n');
1611
}
1712

1813
describe('PathResolver', function () {
@@ -193,6 +188,117 @@ describe('PathResolver', function () {
193188
resolver.handleChunk(chunk2b);
194189
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
195190
});
191+
it('should work when final chunk ends with terminating boundary', function () {
192+
const onResponse = jest.fn();
193+
const resolver = new PatchResolver({
194+
onResponse,
195+
boundary,
196+
});
197+
198+
resolver.handleChunk(`\r\n--${boundary}\r\n`);
199+
200+
expect(onResponse).not.toHaveBeenCalled();
201+
202+
resolver.handleChunk(chunk1);
203+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
204+
205+
onResponse.mockClear();
206+
resolver.handleChunk(chunk2);
207+
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
208+
209+
onResponse.mockClear();
210+
resolver.handleChunk(chunk3);
211+
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
212+
213+
onResponse.mockClear();
214+
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
215+
resolver.handleChunk(chunk4FinalBoundary);
216+
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
217+
});
218+
219+
it('should work with preamble', function () {
220+
const onResponse = jest.fn();
221+
const resolver = new PatchResolver({
222+
onResponse,
223+
boundary,
224+
});
225+
226+
resolver.handleChunk(`This is some preamble data that should be ignored\r\n`);
227+
resolver.handleChunk(`\r\n--${boundary}\r\n`);
228+
229+
expect(onResponse).not.toHaveBeenCalled();
230+
231+
resolver.handleChunk(chunk1);
232+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
233+
234+
onResponse.mockClear();
235+
resolver.handleChunk(chunk2);
236+
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
237+
238+
onResponse.mockClear();
239+
resolver.handleChunk(chunk3);
240+
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
241+
242+
onResponse.mockClear();
243+
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
244+
resolver.handleChunk(chunk4FinalBoundary);
245+
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
246+
});
247+
it('should work with epilogue', function () {
248+
const onResponse = jest.fn();
249+
const resolver = new PatchResolver({
250+
onResponse,
251+
boundary,
252+
});
253+
254+
resolver.handleChunk(`\r\n--${boundary}\r\n`);
255+
256+
expect(onResponse).not.toHaveBeenCalled();
257+
258+
resolver.handleChunk(chunk1);
259+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
260+
261+
onResponse.mockClear();
262+
resolver.handleChunk(chunk2);
263+
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
264+
265+
onResponse.mockClear();
266+
resolver.handleChunk(chunk3);
267+
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
268+
269+
onResponse.mockClear();
270+
resolver.handleChunk(chunk4);
271+
resolver.handleChunk(`This is some epilogue data that should be ignored\r\n`);
272+
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
273+
});
274+
it('should work with epilogue after chunk with terminating boundary', function () {
275+
const onResponse = jest.fn();
276+
const resolver = new PatchResolver({
277+
onResponse,
278+
boundary,
279+
});
280+
281+
resolver.handleChunk(`\r\n--${boundary}\r\n`);
282+
283+
expect(onResponse).not.toHaveBeenCalled();
284+
285+
resolver.handleChunk(chunk1);
286+
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);
287+
288+
onResponse.mockClear();
289+
resolver.handleChunk(chunk2);
290+
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
291+
292+
onResponse.mockClear();
293+
resolver.handleChunk(chunk3);
294+
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);
295+
296+
onResponse.mockClear();
297+
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
298+
resolver.handleChunk(chunk4FinalBoundary);
299+
resolver.handleChunk(`This is some epilogue data that should be ignored\r\n`);
300+
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
301+
});
196302
});
197303
}
198304
});

src/parseMultipartHttp.js

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ function getDelimiter(boundary) {
22
return `\r\n--${boundary}\r\n`;
33
}
44

5+
function getClosingDelimiter(boundary) {
6+
return `\r\n--${boundary}--\r\n`;
7+
}
8+
59
function splitWithRest(string, delim) {
610
const index = string.indexOf(delim);
711
if (index < 0) {
@@ -13,7 +17,7 @@ function splitWithRest(string, delim) {
1317
export function parseMultipartHttp(buffer, boundary, previousParts = [], isPreamble = true) {
1418
const delimiter = getDelimiter(boundary);
1519

16-
const [region, next] = splitWithRest(buffer, delimiter);
20+
let [region, next] = splitWithRest(buffer, delimiter);
1721

1822
if (region !== undefined && (region.length || region.trim() === '') && isPreamble) {
1923
if (next && next.length) {
@@ -24,21 +28,24 @@ export function parseMultipartHttp(buffer, boundary, previousParts = [], isPream
2428
}
2529
}
2630

27-
if (!(region && region.length)) {
28-
// we need more things
29-
return {
30-
newBuffer: buffer,
31-
parts: previousParts,
32-
isPreamble
33-
};
31+
if (!region) {
32+
const closingDelimiter = getClosingDelimiter(boundary);
33+
[region, next] = splitWithRest(buffer, closingDelimiter);
34+
35+
if (!region) {
36+
// we need more things
37+
return {
38+
newBuffer: buffer,
39+
parts: previousParts,
40+
isPreamble,
41+
};
42+
}
3443
}
3544

3645
let [_headers, body] = splitWithRest(region, '\r\n\r\n');
3746

3847
// remove trailing boundary things
39-
body = body
40-
.replace(delimiter + '\r\n', '')
41-
.replace(delimiter + '--\r\n', '');
48+
body = body.replace(delimiter + '\r\n', '').replace(delimiter + '--\r\n', '');
4249

4350
const payload = JSON.parse(body);
4451
const parts = [...previousParts, payload];

0 commit comments

Comments
 (0)