Skip to content

Commit

Permalink
Fix @defer with payload containing "---" (#10891)
Browse files Browse the repository at this point in the history
* Fix `@defer` with payload containing "---"

When using `@defer` if the response payload contains "---" then the
query will fail with an error message such as:

		ApolloError: Unterminated string in JSON at position 15378

* chore(tests): updates HttpLink test to validate that boundaries
are not parsed within response data, only read at the beginning
of each chunk.

* chore: update changeset

---------

Co-authored-by: alessia <alessia@apollographql.com>
  • Loading branch information
laverdet and alessbell authored May 18, 2023
1 parent 5ab0563 commit ab42a5c
Show file tree
Hide file tree
Showing 3 changed files with 30 additions and 19 deletions.
5 changes: 5 additions & 0 deletions .changeset/angry-weeks-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@apollo/client": patch
---

Fixes a bug in how multipart responses are read when using `@defer`. When reading a multipart body, `HttpLink` no longer attempts to parse the boundary (e.g. `"---"` or other boundary string) within the response data itself, only when reading the beginning of each mulitpart chunked message.
9 changes: 6 additions & 3 deletions src/link/http/__tests__/HttpLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1360,7 +1360,10 @@ describe('HttpLink', () => {
'Content-Type: application/json; charset=utf-8',
'Content-Length: 58',
'',
'{"hasNext":false, "incremental": [{"data":{"name":"stubby"},"path":["stub"],"extensions":{"timestamp":1633038919}}]}',
// Intentionally using the boundary value `---` within the “name” to
// validate that boundary delimiters are not parsed within the response
// data itself, only read at the beginning of each chunk.
'{"hasNext":false, "incremental": [{"data":{"name":"stubby---"},"path":["stub"],"extensions":{"timestamp":1633038919}}]}',
'-----',
].join("\r\n");

Expand Down Expand Up @@ -1418,7 +1421,7 @@ describe('HttpLink', () => {
expect(result).toEqual({
incremental: [{
data: {
name: 'stubby',
name: 'stubby---',
},
extensions: {
timestamp: 1633038919,
Expand Down Expand Up @@ -1544,7 +1547,7 @@ describe('HttpLink', () => {
expect(result).toEqual({
incremental: [{
data: {
name: 'stubby',
name: 'stubby---',
},
extensions: {
timestamp: 1633038919,
Expand Down
35 changes: 19 additions & 16 deletions src/link/http/parseAndCheckHttpResponse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,40 +39,43 @@ export async function readMultipartBody<
.trim()
: "-";

let boundary = `--${boundaryVal}`;
const boundary = `\r\n--${boundaryVal}`;
let buffer = "";
const iterator = responseIterator(response);
let running = true;

while (running) {
const { value, done } = await iterator.next();
const chunk = typeof value === "string" ? value : decoder.decode(value);
const searchFrom = buffer.length - boundary.length + 1;
running = !done;
buffer += chunk;
let bi = buffer.indexOf(boundary);
let bi = buffer.indexOf(boundary, searchFrom);

while (bi > -1) {
let message: string;
[message, buffer] = [
buffer.slice(0, bi),
buffer.slice(bi + boundary.length),
];
if (message.trim()) {
const i = message.indexOf("\r\n\r\n");
const headers = parseHeaders(message.slice(0, i));
const contentType = headers["content-type"];
if (
contentType &&
contentType.toLowerCase().indexOf("application/json") === -1
) {
throw new Error(
"Unsupported patch content type: application/json is required."
);
}
const body = message.slice(i);
const i = message.indexOf("\r\n\r\n");
const headers = parseHeaders(message.slice(0, i));
const contentType = headers["content-type"];
if (
contentType &&
contentType.toLowerCase().indexOf("application/json") === -1
) {
throw new Error(
"Unsupported patch content type: application/json is required."
);
}
// nb: Technically you'd want to slice off the beginning "\r\n" but since
// this is going to be `JSON.parse`d there is no need.
const body = message.slice(i);

if (body) {
try {
const result = parseJsonBody<T>(response, body.replace("\r\n", ""));
const result = parseJsonBody<T>(response, body);
if (
Object.keys(result).length > 1 ||
"data" in result ||
Expand Down

0 comments on commit ab42a5c

Please sign in to comment.