Skip to content

Commit 6b040d9

Browse files
committed
Response: setCookie() sends the Max-Age attribute
Adds Max-Age next to expires - Max-Age takes precedence over expires (RFC 6265) and, unlike expires, does not depend on the client clock; expires is kept for ancient clients. This is something setcookie()'s options array could not control. A non-positive number of seconds clamps Max-Age to 0 (immediate deletion), so deleteCookie() now performs a real deletion (a past time => Max-Age=0) instead of setting a session cookie with an empty value.
1 parent 7ca9279 commit 6b040d9

2 files changed

Lines changed: 13 additions & 5 deletions

File tree

src/Http/Response.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,10 @@ public function setCookie(
253253
}
254254

255255
$seconds = Helpers::expirationToSeconds($expire);
256-
// the value is raw-url-encoded the same way PHP reads it back from $_COOKIE
256+
// the value is raw-url-encoded the same way PHP reads it back from $_COOKIE;
257+
// Max-Age takes precedence over expires (RFC 6265), expires is sent too for ancient clients
257258
$cookie = $name . '=' . rawurlencode($value)
258-
. ($seconds === null ? '' : '; expires=' . Helpers::formatDate(time() + $seconds))
259+
. ($seconds === null ? '' : '; expires=' . Helpers::formatDate(time() + $seconds) . '; Max-Age=' . max(0, $seconds))
259260
. '; path=' . ($path ?? ($domain ? '/' : $this->cookiePath))
260261
. (($domain = $domain ?? ($path ? '' : $this->cookieDomain)) === '' ? '' : '; domain=' . $domain)
261262
. (($secure ?? $this->cookieSecure) ? '; secure' : '')
@@ -277,7 +278,7 @@ public function deleteCookie(
277278
?bool $secure = null,
278279
): void
279280
{
280-
$this->setCookie($name, '', null, $path, $domain, $secure);
281+
$this->setCookie($name, '', -1, $path, $domain, $secure); // a past time => Max-Age=0 => delete now
281282
}
282283

283284

tests/Http/Response.setCookie.phpt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,19 @@ $headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:']));
7070
Assert::same(['Set-Cookie: test=f; path=/; domain=example.org; HttpOnly; SameSite=Lax'], $headers);
7171

7272

73-
// a future expiration sets the expires attribute
73+
// a future expiration sets Max-Age (and expires for ancient clients)
7474
$response = new Http\Response;
7575
$old = headers_list();
7676
$response->setCookie('test', 'value', 3600);
7777
$headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:']));
78-
Assert::match('Set-Cookie: test=value; expires=%a%; path=/; HttpOnly; SameSite=Lax', $headers[0]);
78+
Assert::match('Set-Cookie: test=value; expires=%a%; Max-Age=3600; path=/; HttpOnly; SameSite=Lax', $headers[0]);
79+
80+
81+
// deleteCookie sends Max-Age=0 (immediate deletion)
82+
$old = headers_list();
83+
$response->deleteCookie('test');
84+
$headers = array_values(array_diff(headers_list(), $old, ['Set-Cookie:']));
85+
Assert::match('Set-Cookie: test=; expires=%a%; Max-Age=0; path=/; HttpOnly; SameSite=Lax', $headers[0]);
7986

8087

8188
// the value is percent-encoded, the name is kept verbatim (incl. [] for array cookies)

0 commit comments

Comments
 (0)