diff --git a/src/Appwrite/Functions/Validator/Headers.php b/src/Appwrite/Functions/Validator/Headers.php index 99f2d74249..fcda90e6a3 100644 --- a/src/Appwrite/Functions/Validator/Headers.php +++ b/src/Appwrite/Functions/Validator/Headers.php @@ -47,21 +47,33 @@ class Headers extends Validator $value = \json_decode($value, true); } - if (\json_last_error() == JSON_ERROR_NONE) { + if (json_last_error() !== JSON_ERROR_NONE) { + return false; + } else { if (\is_array($value)) { foreach ($value as $key => $val) { - // Check for invalid characters in key and value - if (!preg_match('/^[a-zA-Z0-9_-]+$/', $key)) { + // Reject non-string keys + if (!\is_string($key) || \strlen($key) === 0) { + return false; + } + + // Check if the key is a single character and ensure it is an alphabetic character + if (\strlen($key) === 1 && !preg_match('/^[a-zA-Z]$/', $key)) { + return false; + } + + // Check for invalid characters in keys longer than one character + if (\strlen($key) > 1 && !preg_match('/^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$/', $key)) { return false; } // Check for x-appwrite- prefix - if (0 === strpos($key, 'x-appwrite-')) { + if (str_starts_with($key, 'x-appwrite-')) { return false; } } } + return true; } - return true; } /** diff --git a/src/Appwrite/Utopia/Response.php b/src/Appwrite/Utopia/Response.php index 6d8c671d89..a71e9a1e8d 100644 --- a/src/Appwrite/Utopia/Response.php +++ b/src/Appwrite/Utopia/Response.php @@ -743,7 +743,7 @@ class Response extends SwooleResponse public function json($data): void { if (!is_array($data) && !$data instanceof \stdClass) { - throw new \Exception('Invalid JSON input var'); + throw new \Exception('Response body is not a valid JSON object.'); } $this diff --git a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php index 1e07c3b8e0..e4f7ced2d2 100644 --- a/tests/e2e/Services/Functions/FunctionsCustomServerTest.php +++ b/tests/e2e/Services/Functions/FunctionsCustomServerTest.php @@ -1350,23 +1350,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - // Poll until deployment is built - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', @@ -1438,16 +1422,6 @@ class FunctionsCustomServerTest extends Scope $this->assertEquals(201, $function['headers']['status-code']); - $variable = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/variables', array_merge([ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - ], $this->getHeaders()), [ - 'key' => 'CUSTOM_VARIABLE', - 'value' => 'variable', - ]); - - $this->assertEquals(201, $variable['headers']['status-code']); - $deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([ 'content-type' => 'multipart/form-data', 'x-appwrite-project' => $this->getProject()['$id'], @@ -1460,22 +1434,7 @@ class FunctionsCustomServerTest extends Scope $deploymentId = $deployment['body']['$id'] ?? ''; $this->assertEquals(202, $deployment['headers']['status-code']); - while (true) { - $deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [ - 'content-type' => 'application/json', - 'x-appwrite-project' => $this->getProject()['$id'], - 'x-appwrite-key' => $this->getProject()['apiKey'], - ]); - - if ( - $deployment['headers']['status-code'] >= 400 - || \in_array($deployment['body']['status'], ['ready', 'failed']) - ) { - break; - } - - \sleep(1); - } + $this->awaitDeploymentIsBuilt($function['body']['$id'], $deploymentId, checkForSuccess: false); $deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([ 'content-type' => 'application/json', diff --git a/tests/unit/Functions/Validator/HeadersTest.php b/tests/unit/Functions/Validator/HeadersTest.php index b06a71cb37..57baa1949c 100644 --- a/tests/unit/Functions/Validator/HeadersTest.php +++ b/tests/unit/Functions/Validator/HeadersTest.php @@ -44,5 +44,56 @@ class HeadersTest extends TestCase 'header/////Key' => 'headerValue', ]; $this->assertEquals($this->object->isValid($headers), false); + + $headers = [ + 'Content-Type' => 'application/json', + 'X-Custom-Header' => 'value' + ]; + $this->assertEquals($this->object->isValid($headers), true); + + $headers = [ + 'X-Custom-Header_With-Hyphens_and_Underscores' => 'value' + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + 'X-Header-123' => 'value' + ]; + $this->assertTrue($this->object->isValid($headers)); + + $headers = [ + 'X-Header<>' => 'value' + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + 'X Header' => 'value' + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + '' => 'value' + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + null => 'value', + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + 'X-Header' => null, + ]; + $this->assertTrue($this->object->isValid($headers)); + + $headers = [ + true => 'value', + ]; + $this->assertFalse($this->object->isValid($headers)); + + $headers = [ + 'a' => 'b', + ]; + $this->assertTrue($this->object->isValid($headers)); } }