Skip to content

Commit 85fbc6e

Browse files
committed
Fix GH-10152: Custom properties of Date's child classes are not serialised
1 parent f732486 commit 85fbc6e

14 files changed

+288
-23
lines changed

ext/date/php_date.c

Lines changed: 146 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2254,6 +2254,19 @@ static void date_object_free_storage_period(zend_object *object) /* {{{ */
22542254
zend_object_std_dtor(&intern->std);
22552255
} /* }}} */
22562256

2257+
static void add_common_properties(HashTable *myht, zend_object *zobj)
2258+
{
2259+
HashTable *common;
2260+
zend_string *name;
2261+
zval *prop;
2262+
2263+
common = zend_std_get_properties(zobj);
2264+
2265+
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_IND(common, name, prop) {
2266+
zend_hash_add(myht, name, prop);
2267+
} ZEND_HASH_FOREACH_END();
2268+
}
2269+
22572270
/* Advanced Interface */
22582271
PHPAPI zval *php_date_instantiate(zend_class_entry *pce, zval *object) /* {{{ */
22592272
{
@@ -2733,6 +2746,8 @@ PHP_METHOD(DateTime, __serialize)
27332746
array_init(return_value);
27342747
myht = Z_ARRVAL_P(return_value);
27352748
date_object_to_hash(dateobj, myht);
2749+
2750+
add_common_properties(myht, &dateobj->std);
27362751
}
27372752
/* }}} */
27382753

@@ -2751,9 +2766,36 @@ PHP_METHOD(DateTimeImmutable, __serialize)
27512766
array_init(return_value);
27522767
myht = Z_ARRVAL_P(return_value);
27532768
date_object_to_hash(dateobj, myht);
2769+
2770+
add_common_properties(myht, &dateobj->std);
27542771
}
27552772
/* }}} */
27562773

2774+
static bool date_time_is_internal_property(zend_string *name)
2775+
{
2776+
if (
2777+
zend_string_equals_literal(name, "date") ||
2778+
zend_string_equals_literal(name, "timezone_type") ||
2779+
zend_string_equals_literal(name, "timezone")
2780+
) {
2781+
return 1;
2782+
}
2783+
return 0;
2784+
}
2785+
2786+
static void restore_custom_datetime_properties(zval *object, HashTable *myht)
2787+
{
2788+
zend_string *prop_name;
2789+
zval *prop_val;
2790+
2791+
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) {
2792+
if (date_time_is_internal_property(prop_name)) {
2793+
continue;
2794+
}
2795+
add_property_zval_ex(object, ZSTR_VAL(prop_name), ZSTR_LEN(prop_name), prop_val);
2796+
} ZEND_HASH_FOREACH_END();
2797+
}
2798+
27572799
/* {{{ */
27582800
PHP_METHOD(DateTime, __unserialize)
27592801
{
@@ -2772,6 +2814,8 @@ PHP_METHOD(DateTime, __unserialize)
27722814
if (!php_date_initialize_from_hash(&dateobj, myht)) {
27732815
zend_throw_error(NULL, "Invalid serialization data for DateTime object");
27742816
}
2817+
2818+
restore_custom_datetime_properties(object, myht);
27752819
}
27762820
/* }}} */
27772821

@@ -2793,6 +2837,8 @@ PHP_METHOD(DateTimeImmutable, __unserialize)
27932837
if (!php_date_initialize_from_hash(&dateobj, myht)) {
27942838
zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object");
27952839
}
2840+
2841+
restore_custom_datetime_properties(object, myht);
27962842
}
27972843
/* }}} */
27982844

@@ -3753,9 +3799,35 @@ PHP_METHOD(DateTimeZone, __serialize)
37533799
array_init(return_value);
37543800
myht = Z_ARRVAL_P(return_value);
37553801
date_timezone_object_to_hash(tzobj, myht);
3802+
3803+
add_common_properties(myht, &tzobj->std);
37563804
}
37573805
/* }}} */
37583806

3807+
static bool date_timezone_is_internal_property(zend_string *name)
3808+
{
3809+
if (
3810+
zend_string_equals_literal(name, "timezone_type") ||
3811+
zend_string_equals_literal(name, "timezone")
3812+
) {
3813+
return 1;
3814+
}
3815+
return 0;
3816+
}
3817+
3818+
static void restore_custom_datetimezone_properties(zval *object, HashTable *myht)
3819+
{
3820+
zend_string *prop_name;
3821+
zval *prop_val;
3822+
3823+
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) {
3824+
if (date_timezone_is_internal_property(prop_name)) {
3825+
continue;
3826+
}
3827+
add_property_zval_ex(object, ZSTR_VAL(prop_name), ZSTR_LEN(prop_name), prop_val);
3828+
} ZEND_HASH_FOREACH_END();
3829+
}
3830+
37593831
/* {{{ */
37603832
PHP_METHOD(DateTimeZone, __unserialize)
37613833
{
@@ -3774,6 +3846,8 @@ PHP_METHOD(DateTimeZone, __unserialize)
37743846
if (!php_date_timezone_initialize_from_hash(&object, &tzobj, myht)) {
37753847
zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object");
37763848
}
3849+
3850+
restore_custom_datetimezone_properties(object, myht);
37773851
}
37783852
/* }}} */
37793853

@@ -4344,9 +4418,44 @@ PHP_METHOD(DateInterval, __serialize)
43444418
array_init(return_value);
43454419
myht = Z_ARRVAL_P(return_value);
43464420
date_interval_object_to_hash(intervalobj, myht);
4421+
4422+
add_common_properties(myht, &intervalobj->std);
43474423
}
43484424
/* }}} */
43494425

4426+
static bool date_interval_is_internal_property(zend_string *name)
4427+
{
4428+
if (
4429+
zend_string_equals_literal(name, "date_string") ||
4430+
zend_string_equals_literal(name, "from_string") ||
4431+
zend_string_equals_literal(name, "y") ||
4432+
zend_string_equals_literal(name, "m") ||
4433+
zend_string_equals_literal(name, "d") ||
4434+
zend_string_equals_literal(name, "h") ||
4435+
zend_string_equals_literal(name, "i") ||
4436+
zend_string_equals_literal(name, "s") ||
4437+
zend_string_equals_literal(name, "f") ||
4438+
zend_string_equals_literal(name, "invert") ||
4439+
zend_string_equals_literal(name, "days")
4440+
) {
4441+
return 1;
4442+
}
4443+
return 0;
4444+
}
4445+
4446+
static void restore_custom_dateinterval_properties(zval *object, HashTable *myht)
4447+
{
4448+
zend_string *prop_name;
4449+
zval *prop_val;
4450+
4451+
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) {
4452+
if (date_interval_is_internal_property(prop_name)) {
4453+
continue;
4454+
}
4455+
add_property_zval_ex(object, ZSTR_VAL(prop_name), ZSTR_LEN(prop_name), prop_val);
4456+
} ZEND_HASH_FOREACH_END();
4457+
}
4458+
43504459

43514460
/* {{{ */
43524461
PHP_METHOD(DateInterval, __unserialize)
@@ -4364,6 +4473,7 @@ PHP_METHOD(DateInterval, __unserialize)
43644473
myht = Z_ARRVAL_P(array);
43654474

43664475
php_date_interval_initialize_from_hash(&object, &intervalobj, myht);
4476+
restore_custom_dateinterval_properties(object, myht);
43674477
}
43684478
/* }}} */
43694479

@@ -5269,9 +5379,44 @@ PHP_METHOD(DatePeriod, __serialize)
52695379
array_init(return_value);
52705380
myht = Z_ARRVAL_P(return_value);
52715381
date_period_object_to_hash(period_obj, myht);
5382+
5383+
add_common_properties(myht, &period_obj->std);
52725384
}
52735385
/* }}} */
52745386

5387+
/* {{{ date_period_is_internal_property
5388+
* Common for date_period_read_property(), date_period_write_property(), and
5389+
* restore_custom_dateperiod_properties functions
5390+
*/
5391+
static bool date_period_is_internal_property(zend_string *name)
5392+
{
5393+
if (
5394+
zend_string_equals_literal(name, "start") ||
5395+
zend_string_equals_literal(name, "current") ||
5396+
zend_string_equals_literal(name, "end") ||
5397+
zend_string_equals_literal(name, "interval") ||
5398+
zend_string_equals_literal(name, "recurrences") ||
5399+
zend_string_equals_literal(name, "include_start_date") ||
5400+
zend_string_equals_literal(name, "include_end_date")
5401+
) {
5402+
return 1;
5403+
}
5404+
return 0;
5405+
}
5406+
/* }}} */
5407+
5408+
static void restore_custom_dateperiod_properties(zval *object, HashTable *myht)
5409+
{
5410+
zend_string *prop_name;
5411+
zval *prop_val;
5412+
5413+
ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(myht, prop_name, prop_val) {
5414+
if (date_period_is_internal_property(prop_name)) {
5415+
continue;
5416+
}
5417+
add_property_zval_ex(object, ZSTR_VAL(prop_name), ZSTR_LEN(prop_name), prop_val);
5418+
} ZEND_HASH_FOREACH_END();
5419+
}
52755420

52765421
/* {{{ */
52775422
PHP_METHOD(DatePeriod, __unserialize)
@@ -5291,6 +5436,7 @@ PHP_METHOD(DatePeriod, __unserialize)
52915436
if (!php_date_period_initialize_from_hash(period_obj, myht)) {
52925437
zend_throw_error(NULL, "Invalid serialization data for DatePeriod object");
52935438
}
5439+
restore_custom_dateperiod_properties(object, myht);
52945440
}
52955441
/* }}} */
52965442

@@ -5313,25 +5459,6 @@ PHP_METHOD(DatePeriod, __wakeup)
53135459
}
53145460
/* }}} */
53155461

5316-
/* {{{ date_period_is_internal_property
5317-
* Common for date_period_read_property() and date_period_write_property() functions
5318-
*/
5319-
static bool date_period_is_internal_property(zend_string *name)
5320-
{
5321-
if (zend_string_equals_literal(name, "recurrences")
5322-
|| zend_string_equals_literal(name, "include_start_date")
5323-
|| zend_string_equals_literal(name, "include_end_date")
5324-
|| zend_string_equals_literal(name, "start")
5325-
|| zend_string_equals_literal(name, "current")
5326-
|| zend_string_equals_literal(name, "end")
5327-
|| zend_string_equals_literal(name, "interval")
5328-
) {
5329-
return 1;
5330-
}
5331-
return 0;
5332-
}
5333-
/* }}} */
5334-
53355462
/* {{{ date_period_read_property */
53365463
static zval *date_period_read_property(zend_object *object, zend_string *name, int type, void **cache_slot, zval *rv)
53375464
{
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Inherited DateTimeImmutable serialisation with custom properties
3+
--FILE--
4+
<?php
5+
date_default_timezone_set("Europe/London");
6+
7+
class MyDateTimeImmutable extends DateTimeImmutable
8+
{
9+
public function __construct(
10+
string $datetime = "now",
11+
?DateTimeZone $timezone = null,
12+
public ?bool $myProperty = null,
13+
) {
14+
parent::__construct($datetime, $timezone);
15+
}
16+
}
17+
18+
$d = new MyDateTimeImmutable("2023-01-25 16:32:55", myProperty: true);
19+
$e = unserialize(serialize($d));
20+
var_dump($e->myProperty);
21+
?>
22+
--EXPECTF--
23+
bool(true)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Inherited DateTimeInterval serialisation with custom properties
3+
--FILE--
4+
<?php
5+
date_default_timezone_set("Europe/London");
6+
7+
class MyDateInterval extends DateInterval
8+
{
9+
public function __construct(
10+
string $duration,
11+
public ?bool $myProperty = null,
12+
) {
13+
parent::__construct($duration);
14+
}
15+
}
16+
17+
$d = new MyDateInterval("P1W2D", myProperty: true);
18+
$e = unserialize(serialize($d));
19+
var_dump($e->myProperty);
20+
?>
21+
--EXPECTF--
22+
bool(true)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--TEST--
2+
Inherited DateTimePeriod serialisation with custom properties
3+
--FILE--
4+
<?php
5+
date_default_timezone_set("Europe/London");
6+
7+
class MyDatePeriod extends DatePeriod
8+
{
9+
public function __construct(
10+
DateTimeInterface $start,
11+
DateInterval $interval,
12+
int $recurrences,
13+
int $options = 0,
14+
public ?bool $myProperty = null,
15+
) {
16+
parent::__construct($start, $interval, $recurrences, $options);
17+
}
18+
}
19+
20+
$d = new MyDatePeriod(new DateTimeImmutable(), new DateInterval("PT5S"), 5, myProperty: true);
21+
$e = unserialize(serialize($d));
22+
var_dump($e->myProperty);
23+
?>
24+
--EXPECTF--
25+
bool(true)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
Inherited DateTimeZone serialisation with custom properties
3+
--FILE--
4+
<?php
5+
date_default_timezone_set("Europe/London");
6+
7+
class MyDateTimeZone extends DateTimeZone
8+
{
9+
public function __construct(
10+
string $timezone = "Europe/Kyiv",
11+
public ?bool $myProperty = null,
12+
) {
13+
parent::__construct($timezone);
14+
}
15+
}
16+
17+
$d = new MyDateTimeZone("Europe/London", myProperty: true);
18+
$e = unserialize(serialize($d));
19+
var_dump($e->myProperty);
20+
?>
21+
--EXPECTF--
22+
bool(true)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
Inherited DateTime serialisation with custom properties
3+
--FILE--
4+
<?php
5+
date_default_timezone_set("Europe/London");
6+
7+
class MyDateTime extends DateTime
8+
{
9+
public function __construct(
10+
string $datetime = "now",
11+
?DateTimeZone $timezone = null,
12+
public ?bool $myProperty = null,
13+
) {
14+
parent::__construct($datetime, $timezone);
15+
}
16+
}
17+
18+
$d = new MyDateTime("2023-01-25 16:32:55", myProperty: true);
19+
$e = unserialize(serialize($d));
20+
var_dump($e->myProperty);
21+
?>
22+
--EXPECTF--
23+
bool(true)

ext/date/tests/bug53437_var3.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Bug #53437 DateInterval unserialize bad data, 32 bit
44
<?php if (PHP_INT_SIZE != 4) { die('skip 32 bit only'); } ?>
55
--FILE--
66
<?php
7-
$s = 'O:12:"DateInterval":15:{s:1:"y";s:1:"2";s:1:"m";s:1:"0";s:1:"d";s:3:"bla";s:1:"h";s:1:"6";s:1:"i";s:1:"8";s:1:"s";s:1:"0";s:7:"weekday";i:10;s:16:"weekday_behavior";i:10;s:17:"first_last_day_of";i:0;s:6:"invert";i:0;s:4:"days";s:4:"aoeu";s:12:"special_type";i:0;s:14:"special_amount";s:21:"234523452345234532455";s:21:"have_weekday_relative";i:21474836489;s:21:"have_special_relative";s:3:"bla";}';
7+
$s = 'O:12:"DateInterval":8:{s:1:"y";s:1:"2";s:1:"m";s:1:"0";s:1:"d";s:3:"bla";s:1:"h";s:1:"6";s:1:"i";s:1:"8";s:1:"s";s:1:"0";s:6:"invert";i:0;s:4:"days";s:4:"aoeu";}';
88

99
$di = unserialize($s);
1010
var_dump($di);

ext/date/tests/bug53437_var5.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ Bug #53437 (DateInterval unserialize bad data, 64 bit)
44
<?php if (PHP_INT_SIZE != 8) { die('skip true 64 bit only'); } ?>
55
--FILE--
66
<?php
7-
$s = 'O:12:"DateInterval":15:{s:1:"y";s:1:"2";s:1:"m";s:1:"0";s:1:"d";s:3:"bla";s:1:"h";s:1:"6";s:1:"i";s:1:"8";s:1:"s";s:1:"0";s:7:"weekday";i:10;s:16:"weekday_behavior";i:10;s:17:"first_last_day_of";i:0;s:6:"invert";i:0;s:4:"days";s:4:"aoeu";s:12:"special_type";i:0;s:14:"special_amount";s:21:"234523452345234532455";s:21:"have_weekday_relative";i:21474836489;s:21:"have_special_relative";s:3:"bla";}';
7+
$s = 'O:12:"DateInterval":8:{s:1:"y";s:1:"2";s:1:"m";s:1:"0";s:1:"d";s:3:"bla";s:1:"h";s:1:"6";s:1:"i";s:1:"8";s:1:"s";s:1:"0";s:6:"invert";i:0;s:4:"days";s:4:"aoeu";}';
88

99
$di = unserialize($s);
1010
var_dump($di);

0 commit comments

Comments
 (0)