From e2d7dd837d27f8b0a0875f7cca823eddb60bbb5b Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:12:03 +0100 Subject: [PATCH 1/2] fix: use cURL cookie engine instead of parse_str for RFC 6265 compliance parse_str() URL-decodes cookie values, causing the test client to behave differently from real clients (Dart, Swift) which store values verbatim per RFC 6265. This masked a production bug where base64 session values containing %3D%3D would fail to decode on real devices. Replaces the manual Set-Cookie header parsing with cURL's built-in cookie engine (CURLOPT_COOKIEFILE='') and reads cookies via CURLINFO_COOKIELIST, which stores and returns values verbatim without any decoding. Co-Authored-By: Claude Sonnet 4.6 --- tests/e2e/Client.php | 29 +++++++---------------------- 1 file changed, 7 insertions(+), 22 deletions(-) diff --git a/tests/e2e/Client.php b/tests/e2e/Client.php index 758133c4c0..d170d56fe4 100644 --- a/tests/e2e/Client.php +++ b/tests/e2e/Client.php @@ -219,7 +219,8 @@ class Client curl_setopt($ch, CURLOPT_HTTPHEADER, $formattedHeaders); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); curl_setopt($ch, CURLOPT_TIMEOUT, 120); - curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders, &$cookies) { + curl_setopt($ch, CURLOPT_COOKIEFILE, ''); // enable in-memory RFC 6265 cookie engine + curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) { $len = strlen($header); $header = explode(':', $header, 2); @@ -227,12 +228,6 @@ class Client return $len; } - if (strtolower(trim($header[0])) == 'set-cookie') { - $parsed = $this->parseCookie((string)trim($header[1])); - $name = array_key_first($parsed); - $cookies[$name] = $parsed[$name]; - } - $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]); return $len; @@ -259,6 +254,11 @@ class Client $responseType = $responseHeaders['content-type'] ?? ''; $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE); + foreach (curl_getinfo($ch, CURLINFO_COOKIELIST) as $line) { + $parts = explode("\t", $line); + $cookies[$parts[5]] = $parts[6] ?? ''; + } + if ($decode && $method !== self::METHOD_HEAD) { $strpos = strpos($responseType, ';'); $strpos = \is_bool($strpos) ? \strlen($responseType) : $strpos; @@ -309,21 +309,6 @@ class Client ]; } - /** - * Parse Cookie String - * - * @param string $cookie - * @return array - */ - public function parseCookie(string $cookie): array - { - $cookies = []; - - parse_str(strtr($cookie, ['&' => '%26', '+' => '%2B', ';' => '&']), $cookies); - - return $cookies; - } - /** * Flatten params array to PHP multiple format * From 84dc921d41acd89a17e843680cfdc3c0c7f2b065 Mon Sep 17 00:00:00 2001 From: loks0n <22452787+loks0n@users.noreply.github.com> Date: Wed, 8 Apr 2026 14:58:57 +0100 Subject: [PATCH 2/2] fix: replace utopia-php/framework with http, fix RFC 6265 cookie handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit utopia-php/framework was the old name for utopia-php/http. Replacing it with utopia-php/http 0.34.19 which fixes getCookie() to use Swoole's native cookie store (populated via php_raw_url_decode) instead of re-parsing the raw Cookie header without URL-decoding. This fixes a production auth bug where Swoole's setcookie() URL-encodes base64 session values (+ → %2B, / → %2F, = → %3D) in Set-Cookie headers. RFC 6265 clients (Dart, Swift) reflect these verbatim; the old getCookie() returned %2B/%2F/%3D to base64_decode() which produced corrupted output, rejecting valid sessions. Also updates the e2e test client to use cURL's built-in RFC 6265 cookie engine (CURLOPT_COOKIEFILE) instead of parse_str() which silently URL-decoded values, masking the bug in tests. Adds a cookie roundtrip assertion to testCreateAccountSession. Co-Authored-By: Claude Sonnet 4.6 --- composer.json | 2 +- composer.lock | 80 +++---------------- .../Account/AccountCustomClientTest.php | 10 +++ 3 files changed, 24 insertions(+), 68 deletions(-) diff --git a/composer.json b/composer.json index d3474361e2..4ad1ae6120 100644 --- a/composer.json +++ b/composer.json @@ -67,7 +67,7 @@ "utopia-php/emails": "0.6.*", "utopia-php/dns": "1.6.*", "utopia-php/dsn": "0.2.1", - "utopia-php/framework": "0.34.*", + "utopia-php/http": "0.34.*", "utopia-php/fetch": "0.5.*", "utopia-php/image": "0.8.*", "utopia-php/locale": "0.8.*", diff --git a/composer.lock b/composer.lock index 90e8a09ab2..164b3a036f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e9c38bbebc60849e70e3640aaa4422cd", + "content-hash": "4fb974e9843f6104e40396e7cad4a833", "packages": [ { "name": "adhocore/jwt", @@ -4269,72 +4269,18 @@ }, "time": "2025-12-18T16:25:10+00:00" }, - { - "name": "utopia-php/framework", - "version": "0.34.18", - "source": { - "type": "git", - "url": "https://github.com/utopia-php/http.git", - "reference": "c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc", - "reference": "c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc", - "shasum": "" - }, - "require": { - "ext-swoole": "*", - "php": ">=8.2", - "utopia-php/compression": "0.1.*", - "utopia-php/di": "0.3.*", - "utopia-php/servers": "0.3.*", - "utopia-php/telemetry": "0.2.*", - "utopia-php/validators": "0.2.*" - }, - "require-dev": { - "doctrine/instantiator": "^1.5", - "laravel/pint": "1.*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "1.*", - "phpunit/phpunit": "^9.5.25", - "swoole/ide-helper": "4.8.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Utopia\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A simple, light and advanced PHP HTTP framework", - "keywords": [ - "framework", - "http", - "php", - "upf" - ], - "support": { - "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.34.18" - }, - "time": "2026-04-07T08:06:39+00:00" - }, { "name": "utopia-php/http", - "version": "0.34.18", + "version": "0.34.19", "source": { "type": "git", "url": "https://github.com/utopia-php/http.git", - "reference": "c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc" + "reference": "995c119f31866cacd42d63b1f922bf86eabb396c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/http/zipball/c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc", - "reference": "c8e7e8fc9b9b68aa874e365c83010fefe8ae8ccc", + "url": "https://api.github.com/repos/utopia-php/http/zipball/995c119f31866cacd42d63b1f922bf86eabb396c", + "reference": "995c119f31866cacd42d63b1f922bf86eabb396c", "shasum": "" }, "require": { @@ -4373,9 +4319,9 @@ ], "support": { "issues": "https://github.com/utopia-php/http/issues", - "source": "https://github.com/utopia-php/http/tree/0.34.18" + "source": "https://github.com/utopia-php/http/tree/0.34.19" }, - "time": "2026-04-07T08:06:39+00:00" + "time": "2026-04-08T10:23:17+00:00" }, { "name": "utopia-php/image", @@ -5502,16 +5448,16 @@ "packages-dev": [ { "name": "appwrite/sdk-generator", - "version": "1.17.6", + "version": "1.17.7", "source": { "type": "git", "url": "https://github.com/appwrite/sdk-generator.git", - "reference": "8888a9fd11260d389874424268ecbe0d956eb550" + "reference": "291471d04c3f0e7b9fcc46668a6255a4c0f2947e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/8888a9fd11260d389874424268ecbe0d956eb550", - "reference": "8888a9fd11260d389874424268ecbe0d956eb550", + "url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/291471d04c3f0e7b9fcc46668a6255a4c0f2947e", + "reference": "291471d04c3f0e7b9fcc46668a6255a4c0f2947e", "shasum": "" }, "require": { @@ -5547,9 +5493,9 @@ "description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms", "support": { "issues": "https://github.com/appwrite/sdk-generator/issues", - "source": "https://github.com/appwrite/sdk-generator/tree/1.17.6" + "source": "https://github.com/appwrite/sdk-generator/tree/1.17.7" }, - "time": "2026-04-08T05:37:23+00:00" + "time": "2026-04-08T08:51:05+00:00" }, { "name": "brianium/paratest", diff --git a/tests/e2e/Services/Account/AccountCustomClientTest.php b/tests/e2e/Services/Account/AccountCustomClientTest.php index ee1bb31ede..951ab179b3 100644 --- a/tests/e2e/Services/Account/AccountCustomClientTest.php +++ b/tests/e2e/Services/Account/AccountCustomClientTest.php @@ -802,6 +802,16 @@ class AccountCustomClientTest extends Scope $sessionId = $response['body']['$id']; $session = $response['cookies']['a_session_' . $this->getProject()['$id']]; + $accountResponse = $this->client->call(Client::METHOD_GET, '/account', array_merge([ + 'origin' => 'http://localhost', + 'content-type' => 'application/json', + 'x-appwrite-project' => $this->getProject()['$id'], + 'cookie' => 'a_session_' . $this->getProject()['$id'] . '=' . $session, + ])); + + $this->assertEquals(200, $accountResponse['headers']['status-code']); + $this->assertEquals($email, $accountResponse['body']['email']); + // apiKey is only available in custom client test $apiKey = $this->getProject()['apiKey']; if (!empty($apiKey)) {