Skip to content

Commit 3513412

Browse files
committed
[RFC] partitioned option for setcookie/setrawcookie and sessions
RFC: https://wiki.php.net/rfc/CHIPS Closes GH-12652.
1 parent 77dace7 commit 3513412

14 files changed

+198
-25
lines changed

NEWS

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,12 @@ PHP NEWS
66
. Implement #81724 (openssl_cms_encrypt only allows specific ciphers).
77
(Jakub Zelenka)
88

9+
- Session:
10+
. Added support for partitioned cookies. (nielsdos)
11+
12+
- Standard:
13+
. Added support for partitioned cookies. (nielsdos)
14+
915
14 Aug 2025, PHP 8.5.0beta1
1016

1117
- Core:

UPGRADING

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,11 @@ PHP 8.5 UPGRADE NOTES
249249
Pdo_Sqlite::EXPLAIN_MODE_PREPARED, Pdo_Sqlite::EXPLAIN_MODE_EXPLAIN,
250250
Pdo_Sqlite::EXPLAIN_MODE_EXPLAIN_QUERY_PLAN.
251251

252+
- Session:
253+
. session_set_cookie_params(), session_get_cookie_params(), and session_start()
254+
now support partitioned cookies via the "partitioned" key.
255+
RFC: https://wiki.php.net/rfc/CHIPS
256+
252257
- SOAP:
253258
. Enumeration cases are now dumped in __getTypes().
254259
. Implemented request #61105:
@@ -275,6 +280,8 @@ PHP 8.5 UPGRADE NOTES
275280
"width_unit" and "height_unit" to indicate in which units the dimensions
276281
are expressed. These units are px by default. They are not necessarily
277282
the same (just to give one example: one may be cm and the other may be px).
283+
. setcookie() and setrawcookie() now support the "partitioned" key.
284+
RFC: https://wiki.php.net/rfc/CHIPS
278285

279286
- XSL:
280287
. The $namespace argument of XSLTProcessor::getParameter(),

ext/session/php_session.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ typedef struct _php_ps_globals {
149149
zend_string *cookie_samesite;
150150
bool cookie_secure;
151151
bool cookie_httponly;
152+
bool cookie_partitioned;
152153
const ps_module *mod;
153154
const ps_module *default_mod;
154155
void *mod_data;

ext/session/session.c

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,7 @@ PHP_INI_BEGIN()
898898
STD_PHP_INI_ENTRY("session.cookie_path", "/", PHP_INI_ALL, OnUpdateSessionStr, cookie_path, php_ps_globals, ps_globals)
899899
STD_PHP_INI_ENTRY("session.cookie_domain", "", PHP_INI_ALL, OnUpdateSessionStr, cookie_domain, php_ps_globals, ps_globals)
900900
STD_PHP_INI_BOOLEAN("session.cookie_secure", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_secure, php_ps_globals, ps_globals)
901+
STD_PHP_INI_BOOLEAN("session.cookie_partitioned","0", PHP_INI_ALL, OnUpdateSessionBool, cookie_partitioned, php_ps_globals, ps_globals)
901902
STD_PHP_INI_BOOLEAN("session.cookie_httponly", "0", PHP_INI_ALL, OnUpdateSessionBool, cookie_httponly, php_ps_globals, ps_globals)
902903
STD_PHP_INI_ENTRY("session.cookie_samesite", "", PHP_INI_ALL, OnUpdateSessionStr, cookie_samesite, php_ps_globals, ps_globals)
903904
STD_PHP_INI_BOOLEAN("session.use_cookies", "1", PHP_INI_ALL, OnUpdateSessionBool, use_cookies, php_ps_globals, ps_globals)
@@ -1362,6 +1363,12 @@ static zend_result php_session_send_cookie(void)
13621363
return FAILURE;
13631364
}
13641365

1366+
/* Check for invalid settings combinations */
1367+
if (UNEXPECTED(PS(cookie_partitioned) && !PS(cookie_secure))) {
1368+
php_error_docref(NULL, E_WARNING, "Partitioned session cookie cannot be used without also configuring it as secure");
1369+
return FAILURE;
1370+
}
1371+
13651372
ZEND_ASSERT(strpbrk(ZSTR_VAL(PS(session_name)), SESSION_FORBIDDEN_CHARS) == NULL);
13661373

13671374
/* URL encode id because it might be user supplied */
@@ -1406,6 +1413,10 @@ static zend_result php_session_send_cookie(void)
14061413
smart_str_appends(&ncookie, COOKIE_SECURE);
14071414
}
14081415

1416+
if (PS(cookie_partitioned)) {
1417+
smart_str_appends(&ncookie, COOKIE_PARTITIONED);
1418+
}
1419+
14091420
if (PS(cookie_httponly)) {
14101421
smart_str_appends(&ncookie, COOKIE_HTTPONLY);
14111422
}
@@ -1699,6 +1710,7 @@ PHP_FUNCTION(session_set_cookie_params)
16991710
zend_string *lifetime = NULL, *path = NULL, *domain = NULL, *samesite = NULL;
17001711
bool secure = 0, secure_null = 1;
17011712
bool httponly = 0, httponly_null = 1;
1713+
bool partitioned = false, partitioned_null = true;
17021714
zend_string *ini_name;
17031715
zend_result result;
17041716
int found = 0;
@@ -1766,6 +1778,10 @@ PHP_FUNCTION(session_set_cookie_params)
17661778
secure = zval_is_true(value);
17671779
secure_null = 0;
17681780
found++;
1781+
} else if (zend_string_equals_literal_ci(key, "partitioned")) {
1782+
partitioned = zval_is_true(value);
1783+
partitioned_null = 0;
1784+
found++;
17691785
} else if (zend_string_equals_literal_ci(key, "httponly")) {
17701786
httponly = zval_is_true(value);
17711787
httponly_null = 0;
@@ -1830,6 +1846,15 @@ PHP_FUNCTION(session_set_cookie_params)
18301846
goto cleanup;
18311847
}
18321848
}
1849+
if (!partitioned_null) {
1850+
ini_name = ZSTR_INIT_LITERAL("session.cookie_partitioned", 0);
1851+
result = zend_alter_ini_entry_chars(ini_name, partitioned ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
1852+
zend_string_release_ex(ini_name, 0);
1853+
if (result == FAILURE) {
1854+
RETVAL_FALSE;
1855+
goto cleanup;
1856+
}
1857+
}
18331858
if (!httponly_null) {
18341859
ini_name = ZSTR_INIT_LITERAL("session.cookie_httponly", 0);
18351860
result = zend_alter_ini_entry_chars(ini_name, httponly ? "1" : "0", 1, PHP_INI_USER, PHP_INI_STAGE_RUNTIME);
@@ -1872,6 +1897,7 @@ PHP_FUNCTION(session_get_cookie_params)
18721897
add_assoc_str(return_value, "path", zend_string_dup(PS(cookie_path), false));
18731898
add_assoc_str(return_value, "domain", zend_string_dup(PS(cookie_domain), false));
18741899
add_assoc_bool(return_value, "secure", PS(cookie_secure));
1900+
add_assoc_bool(return_value, "partitioned", PS(cookie_partitioned));
18751901
add_assoc_bool(return_value, "httponly", PS(cookie_httponly));
18761902
add_assoc_str(return_value, "samesite", zend_string_dup(PS(cookie_samesite), false));
18771903
}

ext/session/tests/session_get_cookie_params_basic.phpt

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ session.cookie_lifetime=0
99
session.cookie_path="/"
1010
session.cookie_domain=""
1111
session.cookie_secure=0
12+
session.cookie_partitioned=0
1213
session.cookie_httponly=0
1314
session.cookie_samesite=""
1415
--FILE--
@@ -31,13 +32,17 @@ var_dump(session_set_cookie_params([
3132
"httponly" => FALSE,
3233
"samesite" => "please"]));
3334
var_dump(session_get_cookie_params());
35+
var_dump(session_set_cookie_params([
36+
"secure" => TRUE,
37+
"partitioned" => TRUE]));
38+
var_dump(session_get_cookie_params());
3439

3540
echo "Done";
3641
ob_end_flush();
3742
?>
3843
--EXPECTF--
3944
*** Testing session_get_cookie_params() : basic functionality ***
40-
array(6) {
45+
array(7) {
4146
["lifetime"]=>
4247
int(0)
4348
["path"]=>
@@ -46,13 +51,15 @@ array(6) {
4651
string(0) ""
4752
["secure"]=>
4853
bool(false)
54+
["partitioned"]=>
55+
bool(false)
4956
["httponly"]=>
5057
bool(false)
5158
["samesite"]=>
5259
string(0) ""
5360
}
5461
bool(true)
55-
array(6) {
62+
array(7) {
5663
["lifetime"]=>
5764
int(3600)
5865
["path"]=>
@@ -61,13 +68,15 @@ array(6) {
6168
string(4) "blah"
6269
["secure"]=>
6370
bool(false)
71+
["partitioned"]=>
72+
bool(false)
6473
["httponly"]=>
6574
bool(false)
6675
["samesite"]=>
6776
string(0) ""
6877
}
6978
bool(true)
70-
array(6) {
79+
array(7) {
7180
["lifetime"]=>
7281
int(%d)
7382
["path"]=>
@@ -76,13 +85,15 @@ array(6) {
7685
string(3) "foo"
7786
["secure"]=>
7887
bool(true)
88+
["partitioned"]=>
89+
bool(false)
7990
["httponly"]=>
8091
bool(true)
8192
["samesite"]=>
8293
string(0) ""
8394
}
8495
bool(true)
85-
array(6) {
96+
array(7) {
8697
["lifetime"]=>
8798
int(123)
8899
["path"]=>
@@ -91,6 +102,25 @@ array(6) {
91102
string(3) "baz"
92103
["secure"]=>
93104
bool(false)
105+
["partitioned"]=>
106+
bool(false)
107+
["httponly"]=>
108+
bool(false)
109+
["samesite"]=>
110+
string(6) "please"
111+
}
112+
bool(true)
113+
array(7) {
114+
["lifetime"]=>
115+
int(123)
116+
["path"]=>
117+
string(4) "/bar"
118+
["domain"]=>
119+
string(3) "baz"
120+
["secure"]=>
121+
bool(true)
122+
["partitioned"]=>
123+
bool(true)
94124
["httponly"]=>
95125
bool(false)
96126
["samesite"]=>

ext/session/tests/session_get_cookie_params_variation1.phpt

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ session.cookie_lifetime=0
99
session.cookie_path="/"
1010
session.cookie_domain=""
1111
session.cookie_secure=0
12+
session.cookie_partitioned=0
1213
session.cookie_httponly=0
1314
session.cookie_samesite=""
1415
--FILE--
@@ -31,13 +32,15 @@ ini_set("session.cookie_httponly", TRUE);
3132
var_dump(session_get_cookie_params());
3233
ini_set("session.cookie_samesite", "foo");
3334
var_dump(session_get_cookie_params());
35+
ini_set("session.cookie_partitioned", TRUE);
36+
var_dump(session_get_cookie_params());
3437

3538
echo "Done";
3639
ob_end_flush();
3740
?>
3841
--EXPECT--
3942
*** Testing session_get_cookie_params() : variation ***
40-
array(6) {
43+
array(7) {
4144
["lifetime"]=>
4245
int(0)
4346
["path"]=>
@@ -46,12 +49,14 @@ array(6) {
4649
string(0) ""
4750
["secure"]=>
4851
bool(false)
52+
["partitioned"]=>
53+
bool(false)
4954
["httponly"]=>
5055
bool(false)
5156
["samesite"]=>
5257
string(0) ""
5358
}
54-
array(6) {
59+
array(7) {
5560
["lifetime"]=>
5661
int(3600)
5762
["path"]=>
@@ -60,12 +65,14 @@ array(6) {
6065
string(0) ""
6166
["secure"]=>
6267
bool(false)
68+
["partitioned"]=>
69+
bool(false)
6370
["httponly"]=>
6471
bool(false)
6572
["samesite"]=>
6673
string(0) ""
6774
}
68-
array(6) {
75+
array(7) {
6976
["lifetime"]=>
7077
int(3600)
7178
["path"]=>
@@ -74,12 +81,14 @@ array(6) {
7481
string(0) ""
7582
["secure"]=>
7683
bool(false)
84+
["partitioned"]=>
85+
bool(false)
7786
["httponly"]=>
7887
bool(false)
7988
["samesite"]=>
8089
string(0) ""
8190
}
82-
array(6) {
91+
array(7) {
8392
["lifetime"]=>
8493
int(3600)
8594
["path"]=>
@@ -88,12 +97,14 @@ array(6) {
8897
string(3) "foo"
8998
["secure"]=>
9099
bool(false)
100+
["partitioned"]=>
101+
bool(false)
91102
["httponly"]=>
92103
bool(false)
93104
["samesite"]=>
94105
string(0) ""
95106
}
96-
array(6) {
107+
array(7) {
97108
["lifetime"]=>
98109
int(3600)
99110
["path"]=>
@@ -102,12 +113,14 @@ array(6) {
102113
string(3) "foo"
103114
["secure"]=>
104115
bool(true)
116+
["partitioned"]=>
117+
bool(false)
105118
["httponly"]=>
106119
bool(false)
107120
["samesite"]=>
108121
string(0) ""
109122
}
110-
array(6) {
123+
array(7) {
111124
["lifetime"]=>
112125
int(3600)
113126
["path"]=>
@@ -116,12 +129,30 @@ array(6) {
116129
string(3) "foo"
117130
["secure"]=>
118131
bool(true)
132+
["partitioned"]=>
133+
bool(false)
119134
["httponly"]=>
120135
bool(true)
121136
["samesite"]=>
122137
string(0) ""
123138
}
124-
array(6) {
139+
array(7) {
140+
["lifetime"]=>
141+
int(3600)
142+
["path"]=>
143+
string(5) "/path"
144+
["domain"]=>
145+
string(3) "foo"
146+
["secure"]=>
147+
bool(true)
148+
["partitioned"]=>
149+
bool(false)
150+
["httponly"]=>
151+
bool(true)
152+
["samesite"]=>
153+
string(3) "foo"
154+
}
155+
array(7) {
125156
["lifetime"]=>
126157
int(3600)
127158
["path"]=>
@@ -130,6 +161,8 @@ array(6) {
130161
string(3) "foo"
131162
["secure"]=>
132163
bool(true)
164+
["partitioned"]=>
165+
bool(true)
133166
["httponly"]=>
134167
bool(true)
135168
["samesite"]=>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
--TEST--
2+
session_start() with partitioned cookies
3+
--INI--
4+
session.use_strict_mode=0
5+
session.save_handler=files
6+
session.save_path=
7+
session.cookie_secure=0
8+
session.cookie_partitioned=0
9+
--EXTENSIONS--
10+
session
11+
--SKIPIF--
12+
<?php include('skipif.inc'); ?>
13+
--FILE--
14+
<?php
15+
16+
ob_start();
17+
18+
var_dump(session_start(['cookie_partitioned' => true]));
19+
var_dump(session_start(['cookie_partitioned' => true, 'cookie_secure' => false]));
20+
var_dump(session_start(['cookie_partitioned' => true, 'cookie_secure' => true]));
21+
22+
ob_end_flush();
23+
24+
?>
25+
--EXPECTF--
26+
Warning: session_start(): Partitioned session cookie cannot be used without also configuring it as secure in %s on line %d
27+
bool(false)
28+
29+
Warning: session_start(): Partitioned session cookie cannot be used without also configuring it as secure in %s on line %d
30+
bool(false)
31+
bool(true)

0 commit comments

Comments
 (0)