diff --git a/app/controllers/api/avatars.php b/app/controllers/api/avatars.php index 12f3f3d4d6..45ebce195d 100644 --- a/app/controllers/api/avatars.php +++ b/app/controllers/api/avatars.php @@ -23,7 +23,6 @@ use Utopia\Fetch\Client; use Utopia\Image\Image; use Utopia\Logger\Logger; use Utopia\System\System; -use Utopia\Validator\AnyOf; use Utopia\Validator\Assoc; use Utopia\Validator\Boolean; use Utopia\Validator\HexColor; @@ -699,16 +698,11 @@ App::get('/v1/avatars/screenshot') $client->setTimeout(30); $client->addHeader('content-type', Client::CONTENT_TYPE_APPLICATION_JSON); - // Ensure headers is always an associative array (object) - if (!is_array($headers)) { + // Convert indexed array to empty array (should not happen due to Assoc validator) + if (is_array($headers) && count($headers) > 0 && array_keys($headers) === range(0, count($headers) - 1)) { $headers = []; } - - // Convert to associative array if it's a regular array - if (is_array($headers) && array_keys($headers) === range(0, count($headers) - 1)) { - $headers = []; - } - + // Create a new object to ensure proper JSON serialization $headersObject = new \stdClass(); foreach ($headers as $key => $value) { @@ -725,12 +719,12 @@ App::get('/v1/avatars/screenshot') 'scale' => $scale, 'headers' => $headersObject ]; - + // Ensure the entire config is properly serialized as JSON // This is a workaround to ensure headers are sent as an object $configJson = json_encode($config, JSON_FORCE_OBJECT); $configObject = json_decode($configJson, false); // false to keep objects as objects - + // Convert back to array for the fetch method, but ensure headers remains an object $config = [ 'url' => $configObject->url, diff --git a/tests/e2e/Services/Avatars/AvatarsBase.php b/tests/e2e/Services/Avatars/AvatarsBase.php index 83f70b8978..c94bf6bcb9 100644 --- a/tests/e2e/Services/Avatars/AvatarsBase.php +++ b/tests/e2e/Services/Avatars/AvatarsBase.php @@ -558,4 +558,291 @@ trait AvatarsBase $this->assertEquals('PNG', $image->getImageFormat()); $this->assertEquals(strlen(\file_get_contents(__DIR__ . '/../../../resources/initials.png')), strlen($response['body'])); } + + public function testGetScreenshot(): array + { + /** + * Test for SUCCESS + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('image/png', $response['headers']['content-type']); + $this->assertNotEmpty($response['body']); + + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => [ + 'User-Agent' => 'Mozilla/5.0 (compatible; AppwriteBot/1.0)', + 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' + ], + ]); + + $this->assertEquals(200, $response['headers']['status-code']); + $this->assertEquals('image/png', $response['headers']['content-type']); + $this->assertNotEmpty($response['body']); + + /** + * Test for FAILURE - Invalid headers parameter types + */ + + // Test with string headers (should fail) + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => 'invalid-headers-string', + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + // Test with numeric headers (should fail) + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => 123, + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + // Test with boolean headers (should fail) + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => true, + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + // Test with null headers - framework converts null to empty array, so this passes + // Skipping this test as null is converted to [] by the framework before validation + + // Test with regular array (indexed array) - should fail + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => ['value1', 'value2', 'value3'], // Indexed array + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + // Test with mixed array (some numeric keys) - Assoc validator allows this + // Mixed arrays are considered associative by the Assoc validator + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => ['User-Agent' => 'MyApp', 'value2', 'Accept' => 'text/html'], // Mixed array + ]); + $this->assertEquals(200, $response['headers']['status-code']); + + // Test with empty array (should pass - empty associative array) + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => [], // Empty associative array should pass + ]); + $this->assertEquals(200, $response['headers']['status-code']); + + // Test with valid headers object (should pass) + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => [ + 'User-Agent' => 'MyApp/1.0', + 'Accept' => 'text/html,application/xhtml+xml', + 'Accept-Language' => 'en-US,en;q=0.9' + ], + ]); + $this->assertEquals(200, $response['headers']['status-code']); + + // Test with headers containing special characters (should pass) + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'headers' => [ + 'X-Custom-Header' => 'custom-value', + 'Authorization' => 'Bearer token123', + 'Content-Type' => 'application/json' + ], + ]); + $this->assertEquals(200, $response['headers']['status-code']); + + /** + * Test for FAILURE - Invalid URL parameter + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'invalid-url', + 'width' => 800, + 'height' => 600, + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'ftp://example.com', // Non-HTTP/HTTPS URL + 'width' => 800, + 'height' => 600, + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + /** + * Test for FAILURE - Invalid viewport parameter + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'viewport' => 'invalid-viewport', + 'width' => 800, + 'height' => 600, + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'viewport' => '2000x1000', // Too large + 'width' => 800, + 'height' => 600, + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + /** + * Test for FAILURE - Invalid width/height parameters + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 0, // Invalid width + 'height' => 600, + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 3000, // Invalid height + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + /** + * Test for FAILURE - Invalid scale parameter + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'scale' => 0.5, // Too small + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'scale' => 10, // Too large + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + /** + * Test for FAILURE - Invalid sleep parameter + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'sleep' => -1, // Negative sleep + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'sleep' => 15, // Too large + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + /** + * Test for FAILURE - Invalid quality parameter + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'quality' => -2, // Too small + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'quality' => 150, // Too large + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + /** + * Test for FAILURE - Invalid output parameter + */ + $response = $this->client->call(Client::METHOD_GET, '/avatars/screenshot', [ + 'x-appwrite-project' => $this->getProject()['$id'], + ], [ + 'url' => 'https://appwrite.io', + 'width' => 800, + 'height' => 600, + 'output' => 'invalid-format', + ]); + $this->assertEquals(400, $response['headers']['status-code']); + + return []; + } }