From 8b2ee5388c31e0b77cd8f7e39290eb7be50ac7dd Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Thu, 14 Apr 2022 16:34:51 +0100 Subject: [PATCH 1/3] Add DateTime/DateTimeImmutable's __serialize and __unserialize methods --- ext/date/php_date.c | 124 +++++++++++++++--- ext/date/php_date.stub.php | 12 ++ ext/date/php_date_arginfo.h | 24 ++++ .../DateTimeImmutable_serialisation.phpt | 118 +++++++++++++++++ ext/date/tests/DateTime_serialisation.phpt | 118 +++++++++++++++++ ext/date/tests/bug55397.phpt | 2 +- ext/date/tests/bug62852.phpt | 2 +- ext/date/tests/bug62852_var2.phpt | 7 +- ext/date/tests/bug62852_var3.phpt | 7 +- ext/date/tests/bug66721.phpt | 4 +- ext/date/tests/bug68942_2.phpt | 2 +- 11 files changed, 387 insertions(+), 33 deletions(-) create mode 100644 ext/date/tests/DateTimeImmutable_serialisation.phpt create mode 100644 ext/date/tests/DateTime_serialisation.phpt diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 0eb94cae85ae6..02fbdd9c49e64 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -1811,28 +1811,9 @@ static HashTable *date_object_get_gc_timezone(zend_object *object, zval **table, return zend_std_get_properties(object); } /* }}} */ -static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_purpose purpose) /* {{{ */ +static void date_object_to_hash(php_date_obj *dateobj, HashTable *props) { - HashTable *props; zval zv; - php_date_obj *dateobj; - - switch (purpose) { - case ZEND_PROP_PURPOSE_DEBUG: - case ZEND_PROP_PURPOSE_SERIALIZE: - case ZEND_PROP_PURPOSE_VAR_EXPORT: - case ZEND_PROP_PURPOSE_JSON: - case ZEND_PROP_PURPOSE_ARRAY_CAST: - break; - default: - return zend_std_get_properties_for(object, purpose); - } - - dateobj = php_date_obj_from_obj(object); - props = zend_array_dup(zend_std_get_properties(object)); - if (!dateobj->time) { - return props; - } /* first we add the date and time in ISO format */ ZVAL_STR(&zv, date_format("Y-m-d H:i:s.u", sizeof("Y-m-d H:i:s.u")-1, dateobj->time, 1)); @@ -1865,6 +1846,31 @@ static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_ } zend_hash_str_update(props, "timezone", sizeof("timezone")-1, &zv); } +} + +static HashTable *date_object_get_properties_for(zend_object *object, zend_prop_purpose purpose) /* {{{ */ +{ + HashTable *props; + php_date_obj *dateobj; + + switch (purpose) { + case ZEND_PROP_PURPOSE_DEBUG: + case ZEND_PROP_PURPOSE_SERIALIZE: + case ZEND_PROP_PURPOSE_VAR_EXPORT: + case ZEND_PROP_PURPOSE_JSON: + case ZEND_PROP_PURPOSE_ARRAY_CAST: + break; + default: + return zend_std_get_properties_for(object, purpose); + } + + dateobj = php_date_obj_from_obj(object); + props = zend_array_dup(zend_std_get_properties(object)); + if (!dateobj->time) { + return props; + } + + date_object_to_hash(dateobj, props); return props; } /* }}} */ @@ -2623,6 +2629,84 @@ PHP_METHOD(DateTimeImmutable, __set_state) } /* }}} */ +/* {{{ */ +PHP_METHOD(DateTime, __serialize) +{ + zval *object = ZEND_THIS; + php_date_obj *dateobj; + HashTable *myht; + + ZEND_PARSE_PARAMETERS_NONE(); + + dateobj = Z_PHPDATE_P(object); + DATE_CHECK_INITIALIZED(dateobj->time, DateTime); + + array_init(return_value); + myht = Z_ARRVAL_P(return_value); + date_object_to_hash(dateobj, myht); +} +/* }}} */ + +/* {{{ */ +PHP_METHOD(DateTimeImmutable, __serialize) +{ + zval *object = ZEND_THIS; + php_date_obj *dateobj; + HashTable *myht; + + ZEND_PARSE_PARAMETERS_NONE(); + + dateobj = Z_PHPDATE_P(object); + DATE_CHECK_INITIALIZED(dateobj->time, DateTimeImmutable); + + array_init(return_value); + myht = Z_ARRVAL_P(return_value); + date_object_to_hash(dateobj, myht); +} +/* }}} */ + +/* {{{ */ +PHP_METHOD(DateTime, __unserialize) +{ + zval *object = ZEND_THIS; + php_date_obj *dateobj; + zval *array; + HashTable *myht; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(array) + ZEND_PARSE_PARAMETERS_END(); + + dateobj = Z_PHPDATE_P(object); + myht = Z_ARRVAL_P(array); + + if (!php_date_initialize_from_hash(&dateobj, myht)) { + zend_throw_error(NULL, "Invalid serialization data for DateTime object"); + } +} +/* }}} */ + +/* {{{ */ +PHP_METHOD(DateTimeImmutable, __unserialize) +{ + zval *object = ZEND_THIS; + php_date_obj *dateobj; + zval *array; + HashTable *myht; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(array) + ZEND_PARSE_PARAMETERS_END(); + + dateobj = Z_PHPDATE_P(object); + myht = Z_ARRVAL_P(array); + + if (!php_date_initialize_from_hash(&dateobj, myht)) { + zend_throw_error(NULL, "Invalid serialization data for DateTimeImmutable object"); + } +} +/* }}} */ + /* {{{ */ PHP_METHOD(DateTime, __wakeup) { diff --git a/ext/date/php_date.stub.php b/ext/date/php_date.stub.php index 5720ac3f573c1..55d17bd168ab0 100644 --- a/ext/date/php_date.stub.php +++ b/ext/date/php_date.stub.php @@ -205,12 +205,20 @@ public function diff(DateTimeInterface $targetObject, bool $absolute = false): D /** @tentative-return-type */ public function __wakeup(): void; + + public function __serialize(): array; + + public function __unserialize(array $data): void; } class DateTime implements DateTimeInterface { public function __construct(string $datetime = "now", ?DateTimeZone $timezone = null) {} + public function __serialize(): array {} + + public function __unserialize(array $data): void {} + /** @tentative-return-type */ public function __wakeup(): void {} @@ -318,6 +326,10 @@ class DateTimeImmutable implements DateTimeInterface { public function __construct(string $datetime = "now", ?DateTimeZone $timezone = null) {} + public function __serialize(): array {} + + public function __unserialize(array $data): void {} + /** @tentative-return-type */ public function __wakeup(): void {} diff --git a/ext/date/php_date_arginfo.h b/ext/date/php_date_arginfo.h index 3b8f1578f949a..6cc1b75997f3d 100644 --- a/ext/date/php_date_arginfo.h +++ b/ext/date/php_date_arginfo.h @@ -245,11 +245,21 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DateTimeInterface___wakeup, 0, 0, IS_VOID, 0) ZEND_END_ARG_INFO() +#define arginfo_class_DateTimeInterface___serialize arginfo_timezone_abbreviations_list + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DateTimeInterface___unserialize, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, data, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DateTime___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, datetime, IS_STRING, 0, "\"now\"") ZEND_ARG_OBJ_INFO_WITH_DEFAULT_VALUE(0, timezone, DateTimeZone, 1, "null") ZEND_END_ARG_INFO() +#define arginfo_class_DateTime___serialize arginfo_timezone_abbreviations_list + +#define arginfo_class_DateTime___unserialize arginfo_class_DateTimeInterface___unserialize + #define arginfo_class_DateTime___wakeup arginfo_class_DateTimeInterface___wakeup ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_DateTime___set_state, 0, 1, DateTime, 0) @@ -322,6 +332,10 @@ ZEND_END_ARG_INFO() #define arginfo_class_DateTimeImmutable___construct arginfo_class_DateTime___construct +#define arginfo_class_DateTimeImmutable___serialize arginfo_timezone_abbreviations_list + +#define arginfo_class_DateTimeImmutable___unserialize arginfo_class_DateTimeInterface___unserialize + #define arginfo_class_DateTimeImmutable___wakeup arginfo_class_DateTimeInterface___wakeup ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_DateTimeImmutable___set_state, 0, 1, DateTimeImmutable, 0) @@ -517,11 +531,15 @@ ZEND_FUNCTION(date_sunrise); ZEND_FUNCTION(date_sunset); ZEND_FUNCTION(date_sun_info); ZEND_METHOD(DateTime, __construct); +ZEND_METHOD(DateTime, __serialize); +ZEND_METHOD(DateTime, __unserialize); ZEND_METHOD(DateTime, __wakeup); ZEND_METHOD(DateTime, __set_state); ZEND_METHOD(DateTime, createFromImmutable); ZEND_METHOD(DateTime, createFromInterface); ZEND_METHOD(DateTimeImmutable, __construct); +ZEND_METHOD(DateTimeImmutable, __serialize); +ZEND_METHOD(DateTimeImmutable, __unserialize); ZEND_METHOD(DateTimeImmutable, __wakeup); ZEND_METHOD(DateTimeImmutable, __set_state); ZEND_METHOD(DateTimeImmutable, modify); @@ -610,12 +628,16 @@ static const zend_function_entry class_DateTimeInterface_methods[] = { ZEND_ABSTRACT_ME_WITH_FLAGS(DateTimeInterface, getTimestamp, arginfo_class_DateTimeInterface_getTimestamp, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DateTimeInterface, diff, arginfo_class_DateTimeInterface_diff, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_ABSTRACT_ME_WITH_FLAGS(DateTimeInterface, __wakeup, arginfo_class_DateTimeInterface___wakeup, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DateTimeInterface, __serialize, arginfo_class_DateTimeInterface___serialize, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) + ZEND_ABSTRACT_ME_WITH_FLAGS(DateTimeInterface, __unserialize, arginfo_class_DateTimeInterface___unserialize, ZEND_ACC_PUBLIC|ZEND_ACC_ABSTRACT) ZEND_FE_END }; static const zend_function_entry class_DateTime_methods[] = { ZEND_ME(DateTime, __construct, arginfo_class_DateTime___construct, ZEND_ACC_PUBLIC) + ZEND_ME(DateTime, __serialize, arginfo_class_DateTime___serialize, ZEND_ACC_PUBLIC) + ZEND_ME(DateTime, __unserialize, arginfo_class_DateTime___unserialize, ZEND_ACC_PUBLIC) ZEND_ME(DateTime, __wakeup, arginfo_class_DateTime___wakeup, ZEND_ACC_PUBLIC) ZEND_ME(DateTime, __set_state, arginfo_class_DateTime___set_state, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(DateTime, createFromImmutable, arginfo_class_DateTime_createFromImmutable, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) @@ -641,6 +663,8 @@ static const zend_function_entry class_DateTime_methods[] = { static const zend_function_entry class_DateTimeImmutable_methods[] = { ZEND_ME(DateTimeImmutable, __construct, arginfo_class_DateTimeImmutable___construct, ZEND_ACC_PUBLIC) + ZEND_ME(DateTimeImmutable, __serialize, arginfo_class_DateTimeImmutable___serialize, ZEND_ACC_PUBLIC) + ZEND_ME(DateTimeImmutable, __unserialize, arginfo_class_DateTimeImmutable___unserialize, ZEND_ACC_PUBLIC) ZEND_ME(DateTimeImmutable, __wakeup, arginfo_class_DateTimeImmutable___wakeup, ZEND_ACC_PUBLIC) ZEND_ME(DateTimeImmutable, __set_state, arginfo_class_DateTimeImmutable___set_state, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME_MAPPING(createFromFormat, date_create_immutable_from_format, arginfo_class_DateTimeImmutable_createFromFormat, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) diff --git a/ext/date/tests/DateTimeImmutable_serialisation.phpt b/ext/date/tests/DateTimeImmutable_serialisation.phpt new file mode 100644 index 0000000000000..9c4c5bacf1a08 --- /dev/null +++ b/ext/date/tests/DateTimeImmutable_serialisation.phpt @@ -0,0 +1,118 @@ +--TEST-- +Test DateTimeImmutable::__serialize and DateTimeImmutable::__unserialize +--FILE-- +__serialize()); + +echo "\n\nCalling __unserialize manually:\n"; +$d = new DateTimeImmutable; +$d->__unserialize( + [ + 'date' => '2022-04-14 11:27:42.541106', + 'timezone_type' => 3, + 'timezone' => 'UTC', + ] +); +var_dump($d); + +echo "\n\nCalling __unserialize a few more times, with abbreviations:\n"; +$d->__unserialize( + [ + 'date' => '2022-04-14 11:27:42.541106', + 'timezone_type' => 2, + 'timezone' => 'CEST', + ] +); +var_dump($d); +$d->__unserialize( + [ + 'date' => '2022-04-14 11:27:42.541106', + 'timezone_type' => 1, + 'timezone' => '+0130', + ] +); +var_dump($d); + +?> +--EXPECTF-- +Original object: +object(DateTimeImmutable)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(13) "Europe/London" +} + + +Serialised object: +string(135) "O:17:"DateTimeImmutable":3:{s:4:"date";s:26:"2022-04-14 11:27:42.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:13:"Europe/London";}" + + +Unserialised object: +object(DateTimeImmutable)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(13) "Europe/London" +} + + +Calling __serialize manually: +array(3) { + ["date"]=> + string(26) "2022-04-14 11:27:42.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(13) "Europe/London" +} + + +Calling __unserialize manually: +object(DateTimeImmutable)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.541106" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} + + +Calling __unserialize a few more times, with abbreviations: +object(DateTimeImmutable)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.541106" + ["timezone_type"]=> + int(2) + ["timezone"]=> + string(4) "CEST" +} +object(DateTimeImmutable)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.541106" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+01:30" +} diff --git a/ext/date/tests/DateTime_serialisation.phpt b/ext/date/tests/DateTime_serialisation.phpt new file mode 100644 index 0000000000000..f7a34bd7f558b --- /dev/null +++ b/ext/date/tests/DateTime_serialisation.phpt @@ -0,0 +1,118 @@ +--TEST-- +Test DateTime::__serialize and DateTime::__unserialize +--FILE-- +__serialize()); + +echo "\n\nCalling __unserialize manually:\n"; +$d = new DateTime; +$d->__unserialize( + [ + 'date' => '2022-04-14 11:27:42.541106', + 'timezone_type' => 3, + 'timezone' => 'UTC', + ] +); +var_dump($d); + +echo "\n\nCalling __unserialize a few more times, with abbreviations:\n"; +$d->__unserialize( + [ + 'date' => '2022-04-14 11:27:42.541106', + 'timezone_type' => 2, + 'timezone' => 'CEST', + ] +); +var_dump($d); +$d->__unserialize( + [ + 'date' => '2022-04-14 11:27:42.541106', + 'timezone_type' => 1, + 'timezone' => '+0130', + ] +); +var_dump($d); + +?> +--EXPECTF-- +Original object: +object(DateTime)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(13) "Europe/London" +} + + +Serialised object: +string(125) "O:8:"DateTime":3:{s:4:"date";s:26:"2022-04-14 11:27:42.000000";s:13:"timezone_type";i:3;s:8:"timezone";s:13:"Europe/London";}" + + +Unserialised object: +object(DateTime)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(13) "Europe/London" +} + + +Calling __serialize manually: +array(3) { + ["date"]=> + string(26) "2022-04-14 11:27:42.000000" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(13) "Europe/London" +} + + +Calling __unserialize manually: +object(DateTime)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.541106" + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(3) "UTC" +} + + +Calling __unserialize a few more times, with abbreviations: +object(DateTime)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.541106" + ["timezone_type"]=> + int(2) + ["timezone"]=> + string(4) "CEST" +} +object(DateTime)#%d (%d) { + ["date"]=> + string(26) "2022-04-14 11:27:42.541106" + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+01:30" +} diff --git a/ext/date/tests/bug55397.phpt b/ext/date/tests/bug55397.phpt index 98f29a25b5356..559490dfa3bfa 100644 --- a/ext/date/tests/bug55397.phpt +++ b/ext/date/tests/bug55397.phpt @@ -8,7 +8,7 @@ var_dump(unserialize('O:8:"DateTime":0:{}') == new DateTime); --EXPECTF-- Fatal error: Uncaught Error: Invalid serialization data for DateTime object in %sbug55397.php:%d Stack trace: -#0 [internal function]: DateTime->__wakeup() +#0 [internal function]: DateTime->__unserialize(Array) #1 %sbug55397.php(%d): unserialize('O:8:"DateTime":...') #2 {main} thrown in %sbug55397.php on line %d diff --git a/ext/date/tests/bug62852.phpt b/ext/date/tests/bug62852.phpt index 4ec9a0eab1ca6..120c08faf2393 100644 --- a/ext/date/tests/bug62852.phpt +++ b/ext/date/tests/bug62852.phpt @@ -13,7 +13,7 @@ try { --EXPECTF-- Fatal error: Uncaught Error: Invalid serialization data for DateTime object in %sbug62852.php:%d Stack trace: -#0 [internal function]: DateTime->__wakeup() +#0 [internal function]: DateTime->__unserialize(Array) #1 %sbug62852.php(%d): unserialize('O:8:"DateTime":...') #2 {main} thrown in %sbug62852.php on line %d diff --git a/ext/date/tests/bug62852_var2.phpt b/ext/date/tests/bug62852_var2.phpt index 57fbd6e94e955..4749d2a9bb21f 100644 --- a/ext/date/tests/bug62852_var2.phpt +++ b/ext/date/tests/bug62852_var2.phpt @@ -24,8 +24,7 @@ var_dump( $foo ); --EXPECTF-- Fatal error: Uncaught Error: Invalid serialization data for DateTime object in %sbug62852_var2.php:%d Stack trace: -#0 %sbug62852_var2.php(%d): DateTime->__wakeup() -#1 [internal function]: Foo->__wakeup() -#2 %sbug62852_var2.php(%d): unserialize('O:3:"Foo":3:{s:...') -#3 {main} +#0 [internal function]: DateTime->__unserialize(Array) +#1 %sbug62852_var2.php(%d): unserialize('O:3:"Foo":3:{s:...') +#2 {main} thrown in %sbug62852_var2.php on line %d diff --git a/ext/date/tests/bug62852_var3.phpt b/ext/date/tests/bug62852_var3.phpt index df3669893f257..7049efcebd84f 100644 --- a/ext/date/tests/bug62852_var3.phpt +++ b/ext/date/tests/bug62852_var3.phpt @@ -24,8 +24,7 @@ var_dump( $foo ); --EXPECTF-- Fatal error: Uncaught Error: Invalid serialization data for DateTime object in %sbug62852_var3.php:%d Stack trace: -#0 %sbug62852_var3.php(%d): DateTime->__wakeup() -#1 [internal function]: Foo->__wakeup() -#2 %sbug62852_var3.php(%d): unserialize('O:3:"Foo":3:{s:...') -#3 {main} +#0 [internal function]: DateTime->__unserialize(Array) +#1 %sbug62852_var3.php(%d): unserialize('O:3:"Foo":3:{s:...') +#2 {main} thrown in %sbug62852_var3.php on line %d diff --git a/ext/date/tests/bug66721.phpt b/ext/date/tests/bug66721.phpt index 9effb7ca5622e..5636aa6036b9f 100644 --- a/ext/date/tests/bug66721.phpt +++ b/ext/date/tests/bug66721.phpt @@ -1,5 +1,5 @@ --TEST-- -Test for bug #66721: __wakeup of DateTime segfaults when invalid object data is supplied +Test for bug #66721: __unserialize of DateTime segfaults when invalid object data is supplied --CREDITS-- Boro Sitnikovski --FILE-- @@ -10,7 +10,7 @@ var_dump(unserialize($y)); --EXPECTF-- Fatal error: Uncaught Error: Invalid serialization data for DateTime object in %sbug66721.php:%d Stack trace: -#0 [internal function]: DateTime->__wakeup() +#0 [internal function]: DateTime->__unserialize(Array) #1 %sbug66721.php(%d): unserialize('O:8:"DateTime":...') #2 {main} thrown in %sbug66721.php on line %d diff --git a/ext/date/tests/bug68942_2.phpt b/ext/date/tests/bug68942_2.phpt index 9870bbce5c997..5718db92ce37b 100644 --- a/ext/date/tests/bug68942_2.phpt +++ b/ext/date/tests/bug68942_2.phpt @@ -8,7 +8,7 @@ var_dump($data); --EXPECTF-- Fatal error: Uncaught Error: Invalid serialization data for DateTime object in %sbug68942_2.php:%d Stack trace: -#0 [internal function]: DateTime->__wakeup() +#0 [internal function]: DateTime->__unserialize(Array) #1 %sbug68942_2.php(%d): unserialize('a:2:{i:0;O:8:"D...') #2 {main} thrown in %sbug68942_2.php on line %d From 181623f9c00f971b9b41bee2b8fe7a9982d4a1d0 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Thu, 14 Apr 2022 16:52:22 +0100 Subject: [PATCH 2/3] Add DateTimeZone's __serialize and __unserialize methods --- ext/date/php_date.c | 81 ++++++++++++++----- ext/date/php_date.stub.php | 4 + ext/date/php_date_arginfo.h | 10 ++- .../tests/DateTimeZone_serialisation.phpt | 58 +++++++++++++ .../tests/DateTimeZone_serialize_errors.phpt | 15 ++++ ext/date/tests/bug68942.phpt | 4 +- 6 files changed, 151 insertions(+), 21 deletions(-) create mode 100644 ext/date/tests/DateTimeZone_serialisation.phpt create mode 100644 ext/date/tests/DateTimeZone_serialize_errors.phpt diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 02fbdd9c49e64..1a37a6a3cf8a6 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -1969,10 +1969,20 @@ static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv) } } +void date_timezone_object_to_hash(php_timezone_obj *tzobj, HashTable *props) +{ + zval zv; + + ZVAL_LONG(&zv, tzobj->type); + zend_hash_str_update(props, "timezone_type", strlen("timezone_type"), &zv); + + php_timezone_to_string(tzobj, &zv); + zend_hash_str_update(props, "timezone", strlen("timezone"), &zv); +} + static HashTable *date_object_get_properties_for_timezone(zend_object *object, zend_prop_purpose purpose) /* {{{ */ { HashTable *props; - zval zv; php_timezone_obj *tzobj; switch (purpose) { @@ -1992,11 +2002,7 @@ static HashTable *date_object_get_properties_for_timezone(zend_object *object, z return props; } - ZVAL_LONG(&zv, tzobj->type); - zend_hash_str_update(props, "timezone_type", sizeof("timezone_type")-1, &zv); - - php_timezone_to_string(tzobj, &zv); - zend_hash_str_update(props, "timezone", sizeof("timezone")-1, &zv); + date_timezone_object_to_hash(tzobj, props); return props; } /* }}} */ @@ -3500,7 +3506,7 @@ PHP_FUNCTION(date_diff) } /* }}} */ -static zend_result timezone_initialize(php_timezone_obj *tzobj, const char *tz, size_t tz_len) /* {{{ */ +static bool timezone_initialize(php_timezone_obj *tzobj, const char *tz, size_t tz_len) /* {{{ */ { timelib_time *dummy_t = ecalloc(1, sizeof(timelib_time)); int dst, not_found; @@ -3509,19 +3515,19 @@ static zend_result timezone_initialize(php_timezone_obj *tzobj, const char *tz, if (strlen(tz) != tz_len) { php_error_docref(NULL, E_WARNING, "Timezone must not contain null bytes"); efree(dummy_t); - return FAILURE; + return false; } dummy_t->z = timelib_parse_zone(&tz, &dst, dummy_t, ¬_found, DATE_TIMEZONEDB, php_date_parse_tzfile_wrapper); if (not_found) { php_error_docref(NULL, E_WARNING, "Unknown or bad timezone (%s)", orig_tz); efree(dummy_t); - return FAILURE; + return false; } else { set_timezone_from_timelib_time(tzobj, dummy_t); timelib_free(dummy_t->tz_abbr); efree(dummy_t); - return SUCCESS; + return true; } } /* }}} */ @@ -3536,7 +3542,7 @@ PHP_FUNCTION(timezone_open) ZEND_PARSE_PARAMETERS_END(); tzobj = Z_PHPTIMEZONE_P(php_date_instantiate(date_ce_timezone, return_value)); - if (FAILURE == timezone_initialize(tzobj, ZSTR_VAL(tz), ZSTR_LEN(tz))) { + if (!timezone_initialize(tzobj, ZSTR_VAL(tz), ZSTR_LEN(tz))) { zval_ptr_dtor(return_value); RETURN_FALSE; } @@ -3561,25 +3567,25 @@ PHP_METHOD(DateTimeZone, __construct) } /* }}} */ -static zend_result php_date_timezone_initialize_from_hash(zval **return_value, php_timezone_obj **tzobj, HashTable *myht) /* {{{ */ +static bool php_date_timezone_initialize_from_hash(zval **return_value, php_timezone_obj **tzobj, HashTable *myht) /* {{{ */ { zval *z_timezone_type; if ((z_timezone_type = zend_hash_str_find(myht, "timezone_type", sizeof("timezone_type") - 1)) == NULL) { - return FAILURE; + return false; } zval *z_timezone; if ((z_timezone = zend_hash_str_find(myht, "timezone", sizeof("timezone") - 1)) == NULL) { - return FAILURE; + return false; } if (Z_TYPE_P(z_timezone_type) != IS_LONG) { - return FAILURE; + return false; } if (Z_TYPE_P(z_timezone) != IS_STRING) { - return FAILURE; + return false; } return timezone_initialize(*tzobj, Z_STRVAL_P(z_timezone), Z_STRLEN_P(z_timezone)); } /* }}} */ @@ -3599,7 +3605,7 @@ PHP_METHOD(DateTimeZone, __set_state) php_date_instantiate(date_ce_timezone, return_value); tzobj = Z_PHPTIMEZONE_P(return_value); - if (php_date_timezone_initialize_from_hash(&return_value, &tzobj, myht) == FAILURE) { + if (!php_date_timezone_initialize_from_hash(&return_value, &tzobj, myht)) { zend_throw_error(NULL, "Timezone initialization failed"); zval_ptr_dtor(return_value); } @@ -3619,12 +3625,51 @@ PHP_METHOD(DateTimeZone, __wakeup) myht = Z_OBJPROP_P(object); - if (php_date_timezone_initialize_from_hash(&return_value, &tzobj, myht) == FAILURE) { + if (!php_date_timezone_initialize_from_hash(&return_value, &tzobj, myht)) { zend_throw_error(NULL, "Timezone initialization failed"); } } /* }}} */ +/* {{{ */ +PHP_METHOD(DateTimeZone, __serialize) +{ + zval *object = ZEND_THIS; + php_timezone_obj *tzobj; + HashTable *myht; + + ZEND_PARSE_PARAMETERS_NONE(); + + tzobj = Z_PHPTIMEZONE_P(object); + DATE_CHECK_INITIALIZED(tzobj->initialized, DateTimeZone); + + array_init(return_value); + myht = Z_ARRVAL_P(return_value); + date_timezone_object_to_hash(tzobj, myht); +} +/* }}} */ + +/* {{{ */ +PHP_METHOD(DateTimeZone, __unserialize) +{ + zval *object = ZEND_THIS; + php_timezone_obj *tzobj; + zval *array; + HashTable *myht; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ARRAY(array) + ZEND_PARSE_PARAMETERS_END(); + + tzobj = Z_PHPTIMEZONE_P(object); + myht = Z_ARRVAL_P(array); + + if (!php_date_timezone_initialize_from_hash(&object, &tzobj, myht)) { + zend_throw_error(NULL, "Invalid serialization data for DateTimeZone object"); + } +} +/* }}} */ + /* {{{ Returns the name of the timezone. */ PHP_FUNCTION(timezone_name_get) { diff --git a/ext/date/php_date.stub.php b/ext/date/php_date.stub.php index 55d17bd168ab0..ea3aba7c7b361 100644 --- a/ext/date/php_date.stub.php +++ b/ext/date/php_date.stub.php @@ -453,6 +453,10 @@ public static function listAbbreviations(): array {} */ public static function listIdentifiers(int $timezoneGroup = DateTimeZone::ALL, ?string $countryCode = null): array {} + public function __serialize(): array {} + + public function __unserialize(array $data): void {} + /** @tentative-return-type */ public function __wakeup(): void {} diff --git a/ext/date/php_date_arginfo.h b/ext/date/php_date_arginfo.h index 6cc1b75997f3d..367674f5e4f43 100644 --- a/ext/date/php_date_arginfo.h +++ b/ext/date/php_date_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: ea354510fbf64c42ee1cdd6fd786ab937516226c */ + * Stub hash: a157de6bca4bcf5a9ddace9e81ef700f132b4dda */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_strtotime, 0, 1, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, datetime, IS_STRING, 0) @@ -431,6 +431,10 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DateTimeZone_lis ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, countryCode, IS_STRING, 1, "null") ZEND_END_ARG_INFO() +#define arginfo_class_DateTimeZone___serialize arginfo_timezone_abbreviations_list + +#define arginfo_class_DateTimeZone___unserialize arginfo_class_DateTimeInterface___unserialize + #define arginfo_class_DateTimeZone___wakeup arginfo_class_DateTimeInterface___wakeup ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX(arginfo_class_DateTimeZone___set_state, 0, 1, DateTimeZone, 0) @@ -553,6 +557,8 @@ ZEND_METHOD(DateTimeImmutable, setTimestamp); ZEND_METHOD(DateTimeImmutable, createFromMutable); ZEND_METHOD(DateTimeImmutable, createFromInterface); ZEND_METHOD(DateTimeZone, __construct); +ZEND_METHOD(DateTimeZone, __serialize); +ZEND_METHOD(DateTimeZone, __unserialize); ZEND_METHOD(DateTimeZone, __wakeup); ZEND_METHOD(DateTimeZone, __set_state); ZEND_METHOD(DateInterval, __construct); @@ -696,6 +702,8 @@ static const zend_function_entry class_DateTimeZone_methods[] = { ZEND_ME_MAPPING(getLocation, timezone_location_get, arginfo_class_DateTimeZone_getLocation, ZEND_ACC_PUBLIC) ZEND_ME_MAPPING(listAbbreviations, timezone_abbreviations_list, arginfo_class_DateTimeZone_listAbbreviations, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME_MAPPING(listIdentifiers, timezone_identifiers_list, arginfo_class_DateTimeZone_listIdentifiers, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + ZEND_ME(DateTimeZone, __serialize, arginfo_class_DateTimeZone___serialize, ZEND_ACC_PUBLIC) + ZEND_ME(DateTimeZone, __unserialize, arginfo_class_DateTimeZone___unserialize, ZEND_ACC_PUBLIC) ZEND_ME(DateTimeZone, __wakeup, arginfo_class_DateTimeZone___wakeup, ZEND_ACC_PUBLIC) ZEND_ME(DateTimeZone, __set_state, arginfo_class_DateTimeZone___set_state, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_FE_END diff --git a/ext/date/tests/DateTimeZone_serialisation.phpt b/ext/date/tests/DateTimeZone_serialisation.phpt new file mode 100644 index 0000000000000..60f6f4c59f5be --- /dev/null +++ b/ext/date/tests/DateTimeZone_serialisation.phpt @@ -0,0 +1,58 @@ +--TEST-- +Test DateTimeZone::__serialize and DateTime::__unserialize +--FILE-- +__serialize()); + +$tz = new DateTimeZone("UTC"); +$tz->__unserialize( + [ + 'timezone_type' => 3, + 'timezone' => 'Europe/London', + ] +); +var_dump($tz); + +$tz->__unserialize( + [ + 'timezone_type' => 2, + 'timezone' => 'CEST', + ] +); +var_dump($tz); + +$tz->__unserialize( + [ + 'timezone_type' => 1, + 'timezone' => '+0130', + ] +); +var_dump($tz); + +?> +--EXPECTF-- +array(2) { + ["timezone_type"]=> + int(2) + ["timezone"]=> + string(4) "CEST" +} +object(DateTimeZone)#%d (%d) { + ["timezone_type"]=> + int(3) + ["timezone"]=> + string(13) "Europe/London" +} +object(DateTimeZone)#%d (%d) { + ["timezone_type"]=> + int(2) + ["timezone"]=> + string(4) "CEST" +} +object(DateTimeZone)#%d (%d) { + ["timezone_type"]=> + int(1) + ["timezone"]=> + string(6) "+01:30" +} diff --git a/ext/date/tests/DateTimeZone_serialize_errors.phpt b/ext/date/tests/DateTimeZone_serialize_errors.phpt new file mode 100644 index 0000000000000..f89e3bfafcbf4 --- /dev/null +++ b/ext/date/tests/DateTimeZone_serialize_errors.phpt @@ -0,0 +1,15 @@ +--TEST-- +Test unserialization of DateTimeZone with null byte +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECTF-- +Warning: DateTimeZone::__unserialize(): Timezone must not contain null bytes in %s on line %d +Invalid serialization data for DateTimeZone object diff --git a/ext/date/tests/bug68942.phpt b/ext/date/tests/bug68942.phpt index 9a9a5cfb888f0..91df60b007b62 100644 --- a/ext/date/tests/bug68942.phpt +++ b/ext/date/tests/bug68942.phpt @@ -6,9 +6,9 @@ $data = unserialize('a:2:{i:0;O:12:"DateTimeZone":2:{s:13:"timezone_type";a:2:{i var_dump($data); ?> --EXPECTF-- -Fatal error: Uncaught Error: Timezone initialization failed in %s:%d +Fatal error: Uncaught Error: Invalid serialization data for DateTimeZone object in %s:%d Stack trace: -#0 [internal function]: DateTimeZone->__wakeup() +#0 [internal function]: DateTimeZone->__unserialize(Array) #1 %s(%d): unserialize('a:2:{i:0;O:12:"...') #2 {main} thrown in %s on line %d From 6f2c5017f997c595733f51882cc6d09ceb936c68 Mon Sep 17 00:00:00 2001 From: Derick Rethans Date: Thu, 14 Apr 2022 16:52:45 +0100 Subject: [PATCH 3/3] Fixed memory leak with unserialize was called multiple times and an abbreviation was stored --- ext/date/php_date.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 1a37a6a3cf8a6..0a02a70f6266a 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -3130,6 +3130,12 @@ PHP_METHOD(DateTimeImmutable, sub) static void set_timezone_from_timelib_time(php_timezone_obj *tzobj, timelib_time *t) { + /* Free abbreviation if already set */ + if (tzobj->initialized && tzobj->type == TIMELIB_ZONETYPE_ABBR) { + timelib_free(tzobj->tzi.z.abbr); + } + + /* Set new values */ tzobj->initialized = 1; tzobj->type = t->zone_type;