Skip to content

Commit a596c74

Browse files
Frits van Campenspawnia
Frits van Campen
andauthored
Always throw RequestError with useful message when clients provide an invalid JSON body (#916)
Co-authored-by: Benedikt Franke <[email protected]>
1 parent e8358b6 commit a596c74

File tree

3 files changed

+50
-44
lines changed

3 files changed

+50
-44
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ You can find and compare releases at the [GitHub release page](https://github.co
1818
- Throw `SerializationError` over client safe `Error` when failing to serialize leaf types
1919
- Move debug entries in errors under `extensions` key
2020
- Use native PHP types for `Schema` and `SchemaConfig`
21+
- Always throw `RequestError` with useful message when clients provide an invalid JSON body
2122

2223
### Added
2324

src/Server/Helper.php

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -90,18 +90,9 @@ public function parseHttpRequest(?callable $readRawBodyFn = null)
9090
$rawBody = $readRawBodyFn === null
9191
? $this->readRawBody()
9292
: $readRawBodyFn();
93-
$bodyParams = json_decode($rawBody, true);
93+
$bodyParams = $this->decodeJson($rawBody);
9494

95-
if (json_last_error() !== JSON_ERROR_NONE) {
96-
throw new RequestError('Could not parse JSON: ' . json_last_error_msg());
97-
}
98-
99-
if (! is_array($bodyParams)) {
100-
throw new RequestError(
101-
'GraphQL Server expects JSON object or array, but got ' .
102-
Utils::printSafeJson($bodyParams)
103-
);
104-
}
95+
$this->assertJsonObjectOrArray($bodyParams);
10596
} elseif (stripos($contentType, 'application/x-www-form-urlencoded') !== false) {
10697
$bodyParams = $_POST;
10798
} elseif (stripos($contentType, 'multipart/form-data') !== false) {
@@ -530,22 +521,9 @@ public function parsePsrRequest(RequestInterface $request)
530521
} elseif (stripos($contentType[0], 'application/json') !== false) {
531522
$bodyParams = $request instanceof ServerRequestInterface
532523
? $request->getParsedBody()
533-
: json_decode((string) $request->getBody(), true);
534-
535-
if ($bodyParams === null) {
536-
throw new InvariantViolation(
537-
$request instanceof ServerRequestInterface
538-
? 'Expected to receive a parsed body for "application/json" PSR-7 request but got null'
539-
: 'Expected to receive a JSON array in body for "application/json" PSR-7 request'
540-
);
541-
}
524+
: $this->decodeJson((string) $request->getBody());
542525

543-
if (! is_array($bodyParams)) {
544-
throw new RequestError(
545-
'GraphQL Server expects JSON object or array, but got ' .
546-
Utils::printSafeJson($bodyParams)
547-
);
548-
}
526+
$this->assertJsonObjectOrArray($bodyParams);
549527
} else {
550528
parse_str((string) $request->getBody(), $bodyParams);
551529

@@ -564,6 +542,36 @@ public function parsePsrRequest(RequestInterface $request)
564542
);
565543
}
566544

545+
/**
546+
* @return mixed
547+
*
548+
* @throws RequestError
549+
*/
550+
protected function decodeJson(string $rawBody)
551+
{
552+
$bodyParams = json_decode($rawBody, true);
553+
554+
if (json_last_error() !== JSON_ERROR_NONE) {
555+
throw new RequestError('Expected JSON object or array for "application/json" request, but failed to parse because: ' . json_last_error_msg());
556+
}
557+
558+
return $bodyParams;
559+
}
560+
561+
/**
562+
* @param mixed $bodyParams
563+
*
564+
* @throws RequestError
565+
*/
566+
protected function assertJsonObjectOrArray($bodyParams): void
567+
{
568+
if (! is_array($bodyParams)) {
569+
throw new RequestError(
570+
'Expected JSON object or array for "application/json" request, got: ' . Utils::printSafeJson($bodyParams)
571+
);
572+
}
573+
}
574+
567575
/**
568576
* Converts query execution result to PSR-7 response.
569577
*

tests/Server/RequestParsingTest.php

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -401,42 +401,39 @@ public function testFailsParsingInvalidRawJsonRequestRaw(): void
401401
$body = 'not really{} a json';
402402

403403
$this->expectException(RequestError::class);
404-
$this->expectExceptionMessage('Could not parse JSON: Syntax error');
404+
$this->expectExceptionMessage('Expected JSON object or array for "application/json" request, but failed to parse because: Syntax error');
405405
$this->parseRawRequest('application/json', $body);
406406
}
407407

408408
public function testFailsParsingInvalidRawJsonRequestPsr(): void
409409
{
410410
$body = 'not really{} a json';
411411

412-
$this->expectException(InvariantViolation::class);
413-
$this->expectExceptionMessage('Expected to receive a JSON array in body for "application/json" PSR-7 request');
412+
$this->expectException(RequestError::class);
413+
$this->expectExceptionMessage('Expected JSON object or array for "application/json" request, but failed to parse because: Syntax error');
414414
$this->parsePsrRequest('application/json', $body);
415415
}
416416

417417
public function testFailsParsingNonPreParsedPsrRequest(): void
418418
{
419-
try {
420-
$this->parsePsrRequest('application/json', json_encode(null));
421-
self::fail('Expected exception not thrown');
422-
} catch (InvariantViolation $e) {
423-
// Expecting parsing exception to be thrown somewhere else:
424-
self::assertSame(
425-
'Expected to receive a JSON array in body for "application/json" PSR-7 request',
426-
$e->getMessage()
427-
);
428-
}
419+
$this->expectException(RequestError::class);
420+
$this->expectExceptionMessage('Expected JSON object or array for "application/json" request, got: null');
421+
$this->parsePsrRequest('application/json', json_encode(null));
422+
}
423+
424+
public function testFailsParsingInvalidEmptyJsonRequestPsr(): void
425+
{
426+
$this->expectException(RequestError::class);
427+
$this->expectExceptionMessage('Expected JSON object or array for "application/json" request, but failed to parse because: Syntax error');
428+
$this->parsePsrRequest('application/json', '');
429429
}
430430

431-
/**
432-
* There is no equivalent for psr request, because it should throw
433-
*/
434431
public function testFailsParsingNonArrayOrObjectJsonRequestRaw(): void
435432
{
436433
$body = '"str"';
437434

438435
$this->expectException(RequestError::class);
439-
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
436+
$this->expectExceptionMessage('Expected JSON object or array for "application/json" request, got: "str"');
440437
$this->parseRawRequest('application/json', $body);
441438
}
442439

@@ -445,7 +442,7 @@ public function testFailsParsingNonArrayOrObjectJsonRequestPsr(): void
445442
$body = '"str"';
446443

447444
$this->expectException(RequestError::class);
448-
$this->expectExceptionMessage('GraphQL Server expects JSON object or array, but got "str"');
445+
$this->expectExceptionMessage('Expected JSON object or array for "application/json" request, got: "str"');
449446
$this->parsePsrRequest('application/json', $body);
450447
}
451448

0 commit comments

Comments
 (0)