diff --git a/system/HTTP/Exceptions/HTTPException.php b/system/HTTP/Exceptions/HTTPException.php index d22c48bcb9f3..32f9ceabd662 100644 --- a/system/HTTP/Exceptions/HTTPException.php +++ b/system/HTTP/Exceptions/HTTPException.php @@ -72,6 +72,19 @@ public static function forInvalidNegotiationType(string $type) return new static(lang('HTTP.invalidNegotiationType', [$type])); } + /** + * Thrown in IncomingRequest when the json_decode() produces + * an error code other than JSON_ERROR_NONE. + * + * @param string $error The error message + * + * @return static + */ + public static function forInvalidJSON(?string $error = null) + { + return new static(lang('HTTP.invalidJSON', [$error])); + } + /** * For Message * diff --git a/system/HTTP/IncomingRequest.php b/system/HTTP/IncomingRequest.php index 307a78d38b4a..48b9f3f61be4 100755 --- a/system/HTTP/IncomingRequest.php +++ b/system/HTTP/IncomingRequest.php @@ -573,10 +573,22 @@ public function getVar($index = null, $filter = null, $flags = null) * @see http://php.net/manual/en/function.json-decode.php * * @return array|bool|float|int|stdClass|null + * + * @throws HTTPException When the body is invalid as JSON. */ public function getJSON(bool $assoc = false, int $depth = 512, int $options = 0) { - return json_decode($this->body ?? '', $assoc, $depth, $options); + if ($this->body === null) { + return null; + } + + $result = json_decode($this->body, $assoc, $depth, $options); + + if (json_last_error() !== JSON_ERROR_NONE) { + throw HTTPException::forInvalidJSON(json_last_error_msg()); + } + + return $result; } /** diff --git a/system/Language/en/HTTP.php b/system/Language/en/HTTP.php index a7861c7188d1..e9082f85d185 100644 --- a/system/Language/en/HTTP.php +++ b/system/Language/en/HTTP.php @@ -19,6 +19,7 @@ // IncomingRequest 'invalidNegotiationType' => '"{0}" is not a valid negotiation type. Must be one of: media, charset, encoding, language.', + 'invalidJSON' => 'Failed to parse JSON string. Error: {0}', // Message 'invalidHTTPProtocol' => 'Invalid HTTP Protocol Version: {0}', diff --git a/tests/system/HTTP/IncomingRequestTest.php b/tests/system/HTTP/IncomingRequestTest.php index 9f6114f4875e..9c323b8e5639 100644 --- a/tests/system/HTTP/IncomingRequestTest.php +++ b/tests/system/HTTP/IncomingRequestTest.php @@ -18,6 +18,7 @@ use CodeIgniter\Test\CIUnitTestCase; use Config\App; use InvalidArgumentException; +use JsonException; use TypeError; /** @@ -528,6 +529,32 @@ public function testGetJSONReturnsNullFromNullBody(): void $this->assertNull($request->getJSON()); } + public function testGetJSONWithInvalidJSONString(): void + { + $this->expectException(HTTPException::class); + $this->expectExceptionMessage('Failed to parse JSON string. Error: Syntax error'); + + $config = new App(); + $config->baseURL = 'http://example.com/'; + $json = 'Invalid JSON string'; + $request = $this->createRequest($config, $json); + + $request->getJSON(); + } + + public function testGetJSONWithJsonThrowOnErrorAndInvalidJSONString(): void + { + $this->expectException(JsonException::class); + $this->expectExceptionMessage('Syntax error'); + + $config = new App(); + $config->baseURL = 'http://example.com/'; + $json = 'Invalid JSON string'; + $request = $this->createRequest($config, $json); + + $request->getJSON(false, 512, JSON_THROW_ON_ERROR); + } + public function testCanGrabGetRawInput(): void { $rawstring = 'username=admin001&role=administrator&usepass=0'; diff --git a/tests/system/Validation/ValidationTest.php b/tests/system/Validation/ValidationTest.php index 7cc6f99c40d5..b2db31ad00b0 100644 --- a/tests/system/Validation/ValidationTest.php +++ b/tests/system/Validation/ValidationTest.php @@ -11,6 +11,7 @@ namespace CodeIgniter\Validation; +use CodeIgniter\HTTP\Exceptions\HTTPException; use CodeIgniter\HTTP\IncomingRequest; use CodeIgniter\HTTP\URI; use CodeIgniter\HTTP\UserAgent; @@ -789,6 +790,25 @@ public function testJsonInput(): void unset($_SERVER['CONTENT_TYPE']); } + public function testJsonInputInvalid(): void + { + $this->expectException(HTTPException::class); + $this->expectExceptionMessage('Failed to parse JSON string. Error: Syntax error'); + + $config = new App(); + $json = 'invalid'; + $request = new IncomingRequest($config, new URI(), $json, new UserAgent()); + $request->setHeader('Content-Type', 'application/json'); + + $rules = [ + 'role' => 'if_exist|max_length[5]', + ]; + $this->validation + ->withRequest($request->withMethod('POST')) + ->setRules($rules) + ->run(); + } + /** * @see https://github.com/codeigniter4/CodeIgniter4/issues/6466 */ diff --git a/user_guide_src/source/changelogs/v4.4.4.rst b/user_guide_src/source/changelogs/v4.4.4.rst index bb4250f1e63f..dfc862d62b62 100644 --- a/user_guide_src/source/changelogs/v4.4.4.rst +++ b/user_guide_src/source/changelogs/v4.4.4.rst @@ -31,6 +31,8 @@ and Traditional rules validate data of non-string types. Message Changes *************** +- Added ``HTTP.invalidJSON`` error message. + ******* Changes *******