From f0dbe1148aa6d4099c445e4c6bd3a625acf300e5 Mon Sep 17 00:00:00 2001 From: Stanimir Bozhilov Date: Tue, 20 Sep 2022 14:18:26 +0200 Subject: Add support for application/scim+json content type Signed-off-by: Stanimir Bozhilov --- lib/private/AppFramework/Http/Request.php | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'lib/private/AppFramework/Http') diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 496a845dd4a..9a7014b36c1 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -26,6 +26,7 @@ declare(strict_types=1); * @author Thomas Tanghus * @author Vincent Petry * @author Simon Leiner + * @author Stanimir Bozhilov * * @license AGPL-3.0 * @@ -404,6 +405,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { && $this->getHeader('Content-Length') !== '' && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false && strpos($this->getHeader('Content-Type'), 'application/json') === false + && strpos($this->getHeader('Content-Type'), 'application/scim+json') === false ) { if ($this->content === false) { throw new \LogicException( @@ -437,6 +439,15 @@ class Request implements \ArrayAccess, \Countable, IRequest { $this->items['post'] = $params; } } + // 'application/scim+json' must be decoded manually. + } elseif (strpos($this->getHeader('Content-Type'), 'application/scim+json') !== false) { + $params = json_decode(file_get_contents($this->inputStream), true); + if ($params !== null && \count($params) > 0) { + $this->items['params'] = $params; + if ($this->method === 'POST') { + $this->items['post'] = $params; + } + } // Handle application/x-www-form-urlencoded for methods other than GET // or post correctly -- cgit v1.2.3 From 0ace70488a02d7ee11cc3ae722c2e7f43f431d1e Mon Sep 17 00:00:00 2001 From: Stanimir Bozhilov Date: Wed, 21 Sep 2022 15:31:50 +0200 Subject: Treat application/json and application/scim+json in same if-block Signed-off-by: Stanimir Bozhilov --- lib/private/AppFramework/Http/Request.php | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) (limited to 'lib/private/AppFramework/Http') diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 9a7014b36c1..59ee3edd0fe 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -430,17 +430,9 @@ class Request implements \ArrayAccess, \Countable, IRequest { } $params = []; - // 'application/json' must be decoded manually. - if (strpos($this->getHeader('Content-Type'), 'application/json') !== false) { - $params = json_decode(file_get_contents($this->inputStream), true); - if ($params !== null && \count($params) > 0) { - $this->items['params'] = $params; - if ($this->method === 'POST') { - $this->items['post'] = $params; - } - } - // 'application/scim+json' must be decoded manually. - } elseif (strpos($this->getHeader('Content-Type'), 'application/scim+json') !== false) { + // 'application/json' and 'application/scim+json' must be decoded manually. + if (strpos($this->getHeader('Content-Type'), 'application/json') !== false + || strpos($this->getHeader('Content-Type'), 'application/scim+json') !== false) { $params = json_decode(file_get_contents($this->inputStream), true); if ($params !== null && \count($params) > 0) { $this->items['params'] = $params; -- cgit v1.2.3 From f286a9d6ac6423011eb5e513e761e61b47571bff Mon Sep 17 00:00:00 2001 From: Stanimir Bozhilov Date: Wed, 21 Sep 2022 16:36:01 +0200 Subject: Use regex for all JSON-related content types Signed-off-by: Stanimir Bozhilov --- lib/private/AppFramework/Http/Request.php | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'lib/private/AppFramework/Http') diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index 59ee3edd0fe..b0392f11e09 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -107,6 +107,8 @@ class Request implements \ArrayAccess, \Countable, IRequest { /** @var bool */ protected $contentDecoded = false; + protected $jsonContentTypeRegex = '/application\/(\w+\+)?json/'; + /** * @param array $vars An associative array with the following optional values: * - array 'urlParams' the parameters which were matched from the URL @@ -404,13 +406,13 @@ class Request implements \ArrayAccess, \Countable, IRequest { && $this->getHeader('Content-Length') !== '0' && $this->getHeader('Content-Length') !== '' && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false - && strpos($this->getHeader('Content-Type'), 'application/json') === false - && strpos($this->getHeader('Content-Type'), 'application/scim+json') === false + && preg_match($this->jsonContentTypeRegex, $this->getHeader('Content-Type')) === 0 ) { if ($this->content === false) { throw new \LogicException( '"put" can only be accessed once if not ' - . 'application/x-www-form-urlencoded or application/json.' + . 'application/x-www-form-urlencoded, application/json ' + . 'or other content type, related to JSON (like application/scim+json).' ); } $this->content = false; @@ -430,9 +432,8 @@ class Request implements \ArrayAccess, \Countable, IRequest { } $params = []; - // 'application/json' and 'application/scim+json' must be decoded manually. - if (strpos($this->getHeader('Content-Type'), 'application/json') !== false - || strpos($this->getHeader('Content-Type'), 'application/scim+json') !== false) { + // 'application/json' and other JSON-related content types must be decoded manually. + if (preg_match($this->jsonContentTypeRegex, $this->getHeader('Content-Type')) === 1) { $params = json_decode(file_get_contents($this->inputStream), true); if ($params !== null && \count($params) > 0) { $this->items['params'] = $params; -- cgit v1.2.3 From d80f8f6c823c531d2c54110c7d66b802a54a86da Mon Sep 17 00:00:00 2001 From: Stanimir Bozhilov Date: Thu, 22 Sep 2022 11:25:39 +0200 Subject: Type hint JSON content type regex and use preg_match less Signed-off-by: Stanimir Bozhilov --- lib/private/AppFramework/Http/Request.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'lib/private/AppFramework/Http') diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index b0392f11e09..ee569273a3f 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -107,7 +107,8 @@ class Request implements \ArrayAccess, \Countable, IRequest { /** @var bool */ protected $contentDecoded = false; - protected $jsonContentTypeRegex = '/application\/(\w+\+)?json/'; + /** @var string */ + protected string $jsonContentTypeRegex = '/application\/(\w+\+)?json/'; /** * @param array $vars An associative array with the following optional values: @@ -406,13 +407,12 @@ class Request implements \ArrayAccess, \Countable, IRequest { && $this->getHeader('Content-Length') !== '0' && $this->getHeader('Content-Length') !== '' && strpos($this->getHeader('Content-Type'), 'application/x-www-form-urlencoded') === false - && preg_match($this->jsonContentTypeRegex, $this->getHeader('Content-Type')) === 0 + && strpos($this->getHeader('Content-Type'), 'application/json') === false ) { if ($this->content === false) { throw new \LogicException( '"put" can only be accessed once if not ' - . 'application/x-www-form-urlencoded, application/json ' - . 'or other content type, related to JSON (like application/scim+json).' + . 'application/x-www-form-urlencoded or application/json.' ); } $this->content = false; -- cgit v1.2.3 From 46c10c77e1d5f1b46e665995f9030ccd74283af7 Mon Sep 17 00:00:00 2001 From: Stanimir Bozhilov Date: Mon, 26 Sep 2022 11:51:44 +0200 Subject: Fix the JSON content type regex to match all MIME types Signed-off-by: Stanimir Bozhilov --- lib/private/AppFramework/Http/Request.php | 2 +- tests/lib/AppFramework/Http/RequestTest.php | 91 +++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 1 deletion(-) (limited to 'lib/private/AppFramework/Http') diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index ee569273a3f..e4219cefd70 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -108,7 +108,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { protected $contentDecoded = false; /** @var string */ - protected string $jsonContentTypeRegex = '/application\/(\w+\+)?json/'; + protected string $jsonContentTypeRegex = '{^application/(?:[a-z0-9.-]+\+)?json\b}'; /** * @param array $vars An associative array with the following optional values: diff --git a/tests/lib/AppFramework/Http/RequestTest.php b/tests/lib/AppFramework/Http/RequestTest.php index bfee052e6a8..16875cfb0d8 100644 --- a/tests/lib/AppFramework/Http/RequestTest.php +++ b/tests/lib/AppFramework/Http/RequestTest.php @@ -232,6 +232,30 @@ class RequestTest extends \Test\TestCase { $this->assertSame('Example User', $request['displayName']); } + public function testCustomJsonPost() { + global $data; + $data = '{"propertyA":"sometestvalue", "propertyB":"someothertestvalue"}'; + + // Note: the content type used here is fictional and intended to check if the regex for JSON content types works fine + $vars = [ + 'method' => 'POST', + 'server' => ['CONTENT_TYPE' => 'application/custom-type+json; utf-8'] + ]; + + $request = new Request( + $vars, + $this->requestId, + $this->config, + $this->csrfTokenManager, + $this->stream + ); + + $this->assertSame('POST', $request->method); + $result = $request->post; + $this->assertSame('sometestvalue', $result['propertyA']); + $this->assertSame('someothertestvalue', $result['propertyB']); + } + public function testNotJsonPost() { global $data; $data = 'this is not valid json'; @@ -274,6 +298,27 @@ class RequestTest extends \Test\TestCase { // ensure there's no error attempting to decode the content } + public function testNotCustomJsonPost() { + global $data; + $data = 'this is not valid json'; + $vars = [ + 'method' => 'POST', + 'server' => ['CONTENT_TYPE' => 'application/custom-type+json; utf-8'] + ]; + + $request = new Request( + $vars, + $this->requestId, + $this->config, + $this->csrfTokenManager, + $this->stream + ); + + $this->assertEquals('POST', $request->method); + $result = $request->post; + // ensure there's no error attempting to decode the content + } + public function testPatch() { global $data; $data = http_build_query(['name' => 'John Q. Public', 'nickname' => 'Joey'], '', '&'); @@ -390,6 +435,52 @@ class RequestTest extends \Test\TestCase { $this->assertSame(null, $result['displayName']); } + public function testCustomJsonPatchAndPut() { + global $data; + + // PUT content + $data = '{"propertyA": "sometestvalue", "propertyB": "someothertestvalue"}'; + $vars = [ + 'method' => 'PUT', + 'server' => ['CONTENT_TYPE' => 'application/custom-type+json; utf-8'], + ]; + + $request = new Request( + $vars, + $this->requestId, + $this->config, + $this->csrfTokenManager, + $this->stream + ); + + $this->assertSame('PUT', $request->method); + $result = $request->put; + + $this->assertSame('sometestvalue', $result['propertyA']); + $this->assertSame('someothertestvalue', $result['propertyB']); + + // PATCH content + $data = '{"propertyA": "sometestvalue", "propertyB": null}'; + $vars = [ + 'method' => 'PATCH', + 'server' => ['CONTENT_TYPE' => 'application/custom-type+json; utf-8'], + ]; + + $request = new Request( + $vars, + $this->requestId, + $this->config, + $this->csrfTokenManager, + $this->stream + ); + + $this->assertSame('PATCH', $request->method); + $result = $request->patch; + + $this->assertSame('sometestvalue', $result['propertyA']); + $this->assertSame(null, $result['propertyB']); + } + public function testPutStream() { global $data; $data = file_get_contents(__DIR__ . '/../../../data/testimage.png'); -- cgit v1.2.3 From b44befa8812a38ab29171480572f8581f3b970ad Mon Sep 17 00:00:00 2001 From: Stanimir Bozhilov Date: Thu, 8 Dec 2022 15:11:23 +0100 Subject: Move JSON content type regex to IRequest and make it a const --- lib/private/AppFramework/Http/Request.php | 5 +---- lib/public/IRequest.php | 5 +++++ 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'lib/private/AppFramework/Http') diff --git a/lib/private/AppFramework/Http/Request.php b/lib/private/AppFramework/Http/Request.php index e4219cefd70..3764ce5c7f8 100644 --- a/lib/private/AppFramework/Http/Request.php +++ b/lib/private/AppFramework/Http/Request.php @@ -107,9 +107,6 @@ class Request implements \ArrayAccess, \Countable, IRequest { /** @var bool */ protected $contentDecoded = false; - /** @var string */ - protected string $jsonContentTypeRegex = '{^application/(?:[a-z0-9.-]+\+)?json\b}'; - /** * @param array $vars An associative array with the following optional values: * - array 'urlParams' the parameters which were matched from the URL @@ -433,7 +430,7 @@ class Request implements \ArrayAccess, \Countable, IRequest { $params = []; // 'application/json' and other JSON-related content types must be decoded manually. - if (preg_match($this->jsonContentTypeRegex, $this->getHeader('Content-Type')) === 1) { + if (preg_match(self::JSON_CONTENT_TYPE_REGEX, $this->getHeader('Content-Type')) === 1) { $params = json_decode(file_get_contents($this->inputStream), true); if ($params !== null && \count($params) > 0) { $this->items['params'] = $params; diff --git a/lib/public/IRequest.php b/lib/public/IRequest.php index 7696c3fa8c3..26d0179bc2f 100644 --- a/lib/public/IRequest.php +++ b/lib/public/IRequest.php @@ -97,6 +97,11 @@ interface IRequest { */ public const USER_AGENT_THUNDERBIRD_ADDON = '/^Mozilla\/5\.0 \([A-Za-z ]+\) Nextcloud\-Thunderbird v.*$/'; + /** + * @var string + */ + public const JSON_CONTENT_TYPE_REGEX = '{^application/(?:[a-z0-9.-]+\+)?json\b}'; + /** * @param string $name * -- cgit v1.2.3