Skip to content

Commit

Permalink
fix for when payload ends with terminating boundary
Browse files Browse the repository at this point in the history
fixes #48
  • Loading branch information
robrichard committed Oct 4, 2022
1 parent 77fdd82 commit b916743
Show file tree
Hide file tree
Showing 2 changed files with 130 additions and 17 deletions.
118 changes: 112 additions & 6 deletions src/__test__/PatchResolver.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,7 @@ global.TextDecoder = TextDecoder;
function getMultiPartResponse(data, boundary) {
const json = JSON.stringify(data);

return [
'Content-Type: application/json',
'',
json,
`--${boundary}\r\n`,
].join('\r\n');
return ['Content-Type: application/json', '', json, `--${boundary}\r\n`].join('\r\n');
}

describe('PathResolver', function () {
Expand Down Expand Up @@ -193,6 +188,117 @@ describe('PathResolver', function () {
resolver.handleChunk(chunk2b);
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);
});
it('should work when final chunk ends with terminating boundary', function () {
const onResponse = jest.fn();
const resolver = new PatchResolver({
onResponse,
boundary,
});

resolver.handleChunk(`\r\n--${boundary}\r\n`);

expect(onResponse).not.toHaveBeenCalled();

resolver.handleChunk(chunk1);
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);

onResponse.mockClear();
resolver.handleChunk(chunk2);
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);

onResponse.mockClear();
resolver.handleChunk(chunk3);
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);

onResponse.mockClear();
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
resolver.handleChunk(chunk4FinalBoundary);
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
});

it('should work with preamble', function () {
const onResponse = jest.fn();
const resolver = new PatchResolver({
onResponse,
boundary,
});

resolver.handleChunk(`This is some preamble data that should be ignored\r\n`);
resolver.handleChunk(`\r\n--${boundary}\r\n`);

expect(onResponse).not.toHaveBeenCalled();

resolver.handleChunk(chunk1);
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);

onResponse.mockClear();
resolver.handleChunk(chunk2);
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);

onResponse.mockClear();
resolver.handleChunk(chunk3);
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);

onResponse.mockClear();
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
resolver.handleChunk(chunk4FinalBoundary);
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
});
it('should work with epilogue', function () {
const onResponse = jest.fn();
const resolver = new PatchResolver({
onResponse,
boundary,
});

resolver.handleChunk(`\r\n--${boundary}\r\n`);

expect(onResponse).not.toHaveBeenCalled();

resolver.handleChunk(chunk1);
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);

onResponse.mockClear();
resolver.handleChunk(chunk2);
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);

onResponse.mockClear();
resolver.handleChunk(chunk3);
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);

onResponse.mockClear();
resolver.handleChunk(chunk4);
resolver.handleChunk(`This is some epilogue data that should be ignored\r\n`);
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
});
it('should work with epilogue after chunk with terminating boundary', function () {
const onResponse = jest.fn();
const resolver = new PatchResolver({
onResponse,
boundary,
});

resolver.handleChunk(`\r\n--${boundary}\r\n`);

expect(onResponse).not.toHaveBeenCalled();

resolver.handleChunk(chunk1);
expect(onResponse.mock.calls[0][0]).toEqual([chunk1Data]);

onResponse.mockClear();
resolver.handleChunk(chunk2);
expect(onResponse.mock.calls[0][0]).toEqual([chunk2Data]);

onResponse.mockClear();
resolver.handleChunk(chunk3);
expect(onResponse.mock.calls[0][0]).toEqual([chunk3Data]);

onResponse.mockClear();
const chunk4FinalBoundary = getMultiPartResponse(chunk4Data, `${boundary}--`);
resolver.handleChunk(chunk4FinalBoundary);
resolver.handleChunk(`This is some epilogue data that should be ignored\r\n`);
expect(onResponse.mock.calls[0][0]).toEqual([chunk4Data]);
});
});
}
});
29 changes: 18 additions & 11 deletions src/parseMultipartHttp.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ function getDelimiter(boundary) {
return `\r\n--${boundary}\r\n`;
}

function getClosingDelimiter(boundary) {
return `\r\n--${boundary}--\r\n`;
}

function splitWithRest(string, delim) {
const index = string.indexOf(delim);
if (index < 0) {
Expand All @@ -13,7 +17,7 @@ function splitWithRest(string, delim) {
export function parseMultipartHttp(buffer, boundary, previousParts = [], isPreamble = true) {
const delimiter = getDelimiter(boundary);

const [region, next] = splitWithRest(buffer, delimiter);
let [region, next] = splitWithRest(buffer, delimiter);

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

if (!(region && region.length)) {
// we need more things
return {
newBuffer: buffer,
parts: previousParts,
isPreamble
};
if (!region) {
const closingDelimiter = getClosingDelimiter(boundary);
[region, next] = splitWithRest(buffer, closingDelimiter);

if (!region) {
// we need more things
return {
newBuffer: buffer,
parts: previousParts,
isPreamble,
};
}
}

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

// remove trailing boundary things
body = body
.replace(delimiter + '\r\n', '')
.replace(delimiter + '--\r\n', '');
body = body.replace(delimiter + '\r\n', '').replace(delimiter + '--\r\n', '');

const payload = JSON.parse(body);
const parts = [...previousParts, payload];
Expand Down

0 comments on commit b916743

Please sign in to comment.