Skip to content

Commit d25e565

Browse files
committed
PHPC-726: Allow cross-platform serialization of Timestamp and UTCDateTime
1 parent 560dc81 commit d25e565

20 files changed

+188
-121
lines changed

src/BSON/Timestamp.c

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,48 @@ static bool php_phongo_timestamp_init(php_phongo_timestamp_t *intern, phongo_lon
6767
return true;
6868
}
6969

70+
/* Initialize the object from numeric strings and return whether it was
71+
* successful. An exception will be thrown on error. */
72+
static bool php_phongo_timestamp_init_from_string(php_phongo_timestamp_t *intern, const char *s_increment, phongo_zpp_char_len s_increment_len, const char *s_timestamp, phongo_zpp_char_len s_timestamp_len TSRMLS_DC)
73+
{
74+
int64_t increment, timestamp;
75+
char *endptr = NULL;
76+
77+
errno = 0;
78+
79+
/* errno will set errno if conversion fails; however, we do not need to
80+
* specify the type of error.
81+
*
82+
* Note: bson_ascii_strtoll() does not properly detect out-of-range values
83+
* (see: CDRIVER-1377). strtoll() would be preferable, but it is not
84+
* available on all platforms (e.g. HP-UX), and atoll() provides no error
85+
* reporting at all. */
86+
87+
#if defined(PHP_WIN32)
88+
increment = _atoi64(s_increment);
89+
#else
90+
increment = bson_ascii_strtoll(s_increment, &endptr, 10);
91+
#endif
92+
93+
if (errno || (endptr && endptr != ((const char *)s_increment + s_increment_len))) {
94+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error parsing \"%s\" as 64-bit integer increment for %s initialization", s_increment, ZSTR_VAL(php_phongo_timestamp_ce->name));
95+
return false;
96+
}
97+
98+
#if defined(PHP_WIN32)
99+
timestamp = _atoi64(s_timestamp);
100+
#else
101+
timestamp = bson_ascii_strtoll(s_timestamp, &endptr, 10);
102+
#endif
103+
104+
if (errno || (endptr && endptr != ((const char *)s_timestamp + s_timestamp_len))) {
105+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "Error parsing \"%s\" as 64-bit integer timestamp for %s initialization", s_timestamp, ZSTR_VAL(php_phongo_timestamp_ce->name));
106+
return false;
107+
}
108+
109+
return php_phongo_timestamp_init(intern, increment, timestamp TSRMLS_CC);
110+
}
111+
70112
/* Initialize the object from a HashTable and return whether it was successful.
71113
* An exception will be thrown on error. */
72114
static bool php_phongo_timestamp_init_from_hash(php_phongo_timestamp_t *intern, HashTable *props TSRMLS_DC)
@@ -78,20 +120,28 @@ static bool php_phongo_timestamp_init_from_hash(php_phongo_timestamp_t *intern,
78120
(timestamp = zend_hash_str_find(props, "timestamp", sizeof("timestamp")-1)) && Z_TYPE_P(timestamp) == IS_LONG) {
79121
return php_phongo_timestamp_init(intern, Z_LVAL_P(increment), Z_LVAL_P(timestamp) TSRMLS_CC);
80122
}
123+
if ((increment = zend_hash_str_find(props, "increment", sizeof("increment")-1)) && Z_TYPE_P(increment) == IS_STRING &&
124+
(timestamp = zend_hash_str_find(props, "timestamp", sizeof("timestamp")-1)) && Z_TYPE_P(timestamp) == IS_STRING) {
125+
return php_phongo_timestamp_init_from_string(intern, Z_STRVAL_P(increment), Z_STRLEN_P(increment), Z_STRVAL_P(timestamp), Z_STRLEN_P(timestamp) TSRMLS_CC);
126+
}
81127
#else
82128
zval **increment, **timestamp;
83129

84130
if (zend_hash_find(props, "increment", sizeof("increment"), (void**) &increment) == SUCCESS && Z_TYPE_PP(increment) == IS_LONG &&
85131
zend_hash_find(props, "timestamp", sizeof("timestamp"), (void**) &timestamp) == SUCCESS && Z_TYPE_PP(timestamp) == IS_LONG) {
86132
return php_phongo_timestamp_init(intern, Z_LVAL_PP(increment), Z_LVAL_PP(timestamp) TSRMLS_CC);
87133
}
134+
if (zend_hash_find(props, "increment", sizeof("increment"), (void**) &increment) == SUCCESS && Z_TYPE_PP(increment) == IS_STRING &&
135+
zend_hash_find(props, "timestamp", sizeof("timestamp"), (void**) &timestamp) == SUCCESS && Z_TYPE_PP(timestamp) == IS_STRING) {
136+
return php_phongo_timestamp_init_from_string(intern, Z_STRVAL_PP(increment), Z_STRLEN_PP(increment), Z_STRVAL_PP(timestamp), Z_STRLEN_PP(timestamp) TSRMLS_CC);
137+
}
88138
#endif
89139

90-
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "%s initialization requires \"increment\" and \"timestamp\" integer fields", ZSTR_VAL(php_phongo_timestamp_ce->name));
140+
phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT TSRMLS_CC, "%s initialization requires \"increment\" and \"timestamp\" integer or numeric string fields", ZSTR_VAL(php_phongo_timestamp_ce->name));
91141
return false;
92142
}
93143

94-
/* {{{ proto void Timestamp::__construct(integer $increment, int $timestamp)
144+
/* {{{ proto void Timestamp::__construct(integer $increment, integer $timestamp)
95145
Construct a new BSON timestamp type, which consists of a 4-byte increment and
96146
4-byte timestamp. */
97147
PHP_METHOD(Timestamp, __construct)
@@ -261,6 +311,10 @@ HashTable *php_phongo_timestamp_get_properties(zval *object TSRMLS_DC) /* {{{ */
261311
{
262312
php_phongo_timestamp_t *intern;
263313
HashTable *props;
314+
char s_increment[24];
315+
char s_timestamp[24];
316+
int s_increment_len;
317+
int s_timestamp_len;
264318

265319
intern = Z_TIMESTAMP_OBJ_P(object);
266320
props = zend_std_get_properties(object TSRMLS_CC);
@@ -269,26 +323,29 @@ HashTable *php_phongo_timestamp_get_properties(zval *object TSRMLS_DC) /* {{{ */
269323
return props;
270324
}
271325

326+
s_increment_len = snprintf(s_increment, sizeof(s_increment), "%" PRId64, intern->increment);
327+
s_timestamp_len = snprintf(s_timestamp, sizeof(s_timestamp), "%" PRId64, intern->timestamp);
328+
272329
#if PHP_VERSION_ID >= 70000
273330
{
274331
zval increment, timestamp;
275332

276-
ZVAL_LONG(&increment, intern->increment);
333+
ZVAL_STRINGL(&increment, s_increment, s_increment_len);
277334
zend_hash_str_update(props, "increment", sizeof("increment")-1, &increment);
278335

279-
ZVAL_LONG(&timestamp, intern->timestamp);
336+
ZVAL_STRINGL(&timestamp, s_timestamp, s_timestamp_len);
280337
zend_hash_str_update(props, "timestamp", sizeof("timestamp")-1, &timestamp);
281338
}
282339
#else
283340
{
284341
zval *increment, *timestamp;
285342

286343
MAKE_STD_ZVAL(increment);
287-
ZVAL_LONG(increment, intern->increment);
344+
ZVAL_STRINGL(increment, s_increment, s_increment_len, 1);
288345
zend_hash_update(props, "increment", sizeof("increment"), &increment, sizeof(increment), NULL);
289346

290347
MAKE_STD_ZVAL(timestamp);
291-
ZVAL_LONG(timestamp, intern->timestamp);
348+
ZVAL_STRINGL(timestamp, s_timestamp, s_timestamp_len, 1);
292349
zend_hash_update(props, "timestamp", sizeof("timestamp"), &timestamp, sizeof(timestamp), NULL);
293350
}
294351
#endif

src/BSON/UTCDateTime.c

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ static bool php_phongo_utcdatetime_init_from_date(php_phongo_utcdatetime_t *inte
154154
return true;
155155
}
156156

157-
/* {{{ proto void UTCDateTime::__construct([integer|DateTimeInterface $milliseconds = null])
157+
/* {{{ proto void UTCDateTime::__construct([string|DateTimeInterface $milliseconds = null])
158158
Construct a new BSON UTCDateTime type from either the current time,
159159
milliseconds since the epoch, or a DateTimeInterface object. */
160160
PHP_METHOD(UTCDateTime, __construct)
@@ -183,18 +183,6 @@ PHP_METHOD(UTCDateTime, __construct)
183183
return;
184184
}
185185

186-
#if SIZEOF_PHONGO_LONG == 8
187-
{
188-
phongo_long milliseconds;
189-
190-
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &milliseconds) == FAILURE) {
191-
zend_restore_error_handling(&error_handling TSRMLS_CC);
192-
return;
193-
}
194-
195-
php_phongo_utcdatetime_init(intern, milliseconds);
196-
}
197-
#elif SIZEOF_PHONGO_LONG == 4
198186
{
199187
char *s_milliseconds;
200188
phongo_zpp_char_len s_milliseconds_len;
@@ -206,9 +194,6 @@ PHP_METHOD(UTCDateTime, __construct)
206194

207195
php_phongo_utcdatetime_init_from_string(intern, s_milliseconds, s_milliseconds_len TSRMLS_CC);
208196
}
209-
#else
210-
# error Unsupported architecture (integers are neither 32-bit nor 64-bit)
211-
#endif
212197

213198
zend_restore_error_handling(&error_handling TSRMLS_CC);
214199

@@ -368,12 +353,9 @@ phongo_create_object_retval php_phongo_utcdatetime_create_object(zend_class_entr
368353
HashTable *php_phongo_utcdatetime_get_properties(zval *object TSRMLS_DC) /* {{{ */
369354
{
370355
php_phongo_utcdatetime_t *intern;
371-
HashTable *props;
372-
373-
#if SIZEOF_LONG == 4
374-
char s_milliseconds[24];
375-
int s_milliseconds_len;
376-
#endif
356+
HashTable *props;
357+
char s_milliseconds[24];
358+
int s_milliseconds_len;
377359

378360
intern = Z_UTCDATETIME_OBJ_P(object);
379361
props = zend_std_get_properties(object TSRMLS_CC);
@@ -382,31 +364,21 @@ HashTable *php_phongo_utcdatetime_get_properties(zval *object TSRMLS_DC) /* {{{
382364
return props;
383365
}
384366

385-
#if SIZEOF_LONG == 4
386367
s_milliseconds_len = snprintf(s_milliseconds, sizeof(s_milliseconds), "%" PRId64, intern->milliseconds);
387-
#endif
388368

389369
#if PHP_VERSION_ID >= 70000
390370
{
391371
zval milliseconds;
392372

393-
#if SIZEOF_LONG == 4
394373
ZVAL_STRINGL(&milliseconds, s_milliseconds, s_milliseconds_len);
395-
#else
396-
ZVAL_LONG(&milliseconds, intern->milliseconds);
397-
#endif
398374
zend_hash_str_update(props, "milliseconds", sizeof("milliseconds")-1, &milliseconds);
399375
}
400376
#else
401377
{
402378
zval *milliseconds;
403379

404380
MAKE_STD_ZVAL(milliseconds);
405-
#if SIZEOF_LONG == 4
406381
ZVAL_STRINGL(milliseconds, s_milliseconds, s_milliseconds_len, 1);
407-
#else
408-
ZVAL_LONG(milliseconds, intern->milliseconds);
409-
#endif
410382
zend_hash_update(props, "milliseconds", sizeof("milliseconds"), &milliseconds, sizeof(milliseconds), NULL);
411383
}
412384
#endif

tests/bson/bson-timestamp-002.phpt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ var_dump($timestamp);
1313
--EXPECTF--
1414
object(MongoDB\BSON\Timestamp)#%d (%d) {
1515
["increment"]=>
16-
int(1234)
16+
string(4) "1234"
1717
["timestamp"]=>
18-
int(5678)
18+
string(4) "5678"
1919
}
2020
===DONE===

tests/bson/bson-timestamp-003.phpt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,17 @@ foreach ($tests as $test) {
2121
Test [2147483647:0]
2222
object(MongoDB\BSON\Timestamp)#%d (%d) {
2323
["increment"]=>
24-
int(2147483647)
24+
string(10) "2147483647"
2525
["timestamp"]=>
26-
int(0)
26+
string(1) "0"
2727
}
2828

2929
Test [0:2147483647]
3030
object(MongoDB\BSON\Timestamp)#%d (%d) {
3131
["increment"]=>
32-
int(0)
32+
string(1) "0"
3333
["timestamp"]=>
34-
int(2147483647)
34+
string(10) "2147483647"
3535
}
3636

37-
===DONE===
37+
===DONE===

tests/bson/bson-timestamp-004.phpt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ foreach ($tests as $test) {
2323
Test [4294967295:0]
2424
object(MongoDB\BSON\Timestamp)#%d (%d) {
2525
["increment"]=>
26-
int(4294967295)
26+
string(10) "4294967295"
2727
["timestamp"]=>
28-
int(0)
28+
string(1) "0"
2929
}
3030

3131
Test [0:4294967295]
3232
object(MongoDB\BSON\Timestamp)#%d (%d) {
3333
["increment"]=>
34-
int(0)
34+
string(1) "0"
3535
["timestamp"]=>
36-
int(4294967295)
36+
string(10) "4294967295"
3737
}
3838

3939
===DONE===

tests/bson/bson-timestamp-serialization-001.phpt

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,44 +24,44 @@ foreach ($tests as $test) {
2424
--EXPECTF--
2525
object(MongoDB\BSON\Timestamp)#%d (%d) {
2626
["increment"]=>
27-
int(1234)
27+
string(4) "1234"
2828
["timestamp"]=>
29-
int(5678)
29+
string(4) "5678"
3030
}
31-
string(80) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";i:1234;s:9:"timestamp";i:5678;}"
31+
string(88) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";s:4:"1234";s:9:"timestamp";s:4:"5678";}"
3232
object(MongoDB\BSON\Timestamp)#%d (%d) {
3333
["increment"]=>
34-
int(1234)
34+
string(4) "1234"
3535
["timestamp"]=>
36-
int(5678)
36+
string(4) "5678"
3737
}
3838

3939
object(MongoDB\BSON\Timestamp)#%d (%d) {
4040
["increment"]=>
41-
int(2147483647)
41+
string(10) "2147483647"
4242
["timestamp"]=>
43-
int(0)
43+
string(1) "0"
4444
}
45-
string(83) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";i:2147483647;s:9:"timestamp";i:0;}"
45+
string(92) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";s:10:"2147483647";s:9:"timestamp";s:1:"0";}"
4646
object(MongoDB\BSON\Timestamp)#%d (%d) {
4747
["increment"]=>
48-
int(2147483647)
48+
string(10) "2147483647"
4949
["timestamp"]=>
50-
int(0)
50+
string(1) "0"
5151
}
5252

5353
object(MongoDB\BSON\Timestamp)#%d (%d) {
5454
["increment"]=>
55-
int(0)
55+
string(1) "0"
5656
["timestamp"]=>
57-
int(2147483647)
57+
string(10) "2147483647"
5858
}
59-
string(83) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";i:0;s:9:"timestamp";i:2147483647;}"
59+
string(92) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";s:1:"0";s:9:"timestamp";s:10:"2147483647";}"
6060
object(MongoDB\BSON\Timestamp)#%d (%d) {
6161
["increment"]=>
62-
int(0)
62+
string(1) "0"
6363
["timestamp"]=>
64-
int(2147483647)
64+
string(10) "2147483647"
6565
}
6666

6767
===DONE===

tests/bson/bson-timestamp-serialization-002.phpt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,30 +25,30 @@ foreach ($tests as $test) {
2525
--EXPECTF--
2626
object(MongoDB\BSON\Timestamp)#%d (%d) {
2727
["increment"]=>
28-
int(4294967295)
28+
string(10) "4294967295"
2929
["timestamp"]=>
30-
int(0)
30+
string(1) "0"
3131
}
32-
string(83) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";i:4294967295;s:9:"timestamp";i:0;}"
32+
string(92) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";s:10:"4294967295";s:9:"timestamp";s:1:"0";}"
3333
object(MongoDB\BSON\Timestamp)#%d (%d) {
3434
["increment"]=>
35-
int(4294967295)
35+
string(10) "4294967295"
3636
["timestamp"]=>
37-
int(0)
37+
string(1) "0"
3838
}
3939

4040
object(MongoDB\BSON\Timestamp)#%d (%d) {
4141
["increment"]=>
42-
int(0)
42+
string(1) "0"
4343
["timestamp"]=>
44-
int(4294967295)
44+
string(10) "4294967295"
4545
}
46-
string(83) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";i:0;s:9:"timestamp";i:4294967295;}"
46+
string(92) "O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";s:1:"0";s:9:"timestamp";s:10:"4294967295";}"
4747
object(MongoDB\BSON\Timestamp)#%d (%d) {
4848
["increment"]=>
49-
int(0)
49+
string(1) "0"
5050
["timestamp"]=>
51-
int(4294967295)
51+
string(10) "4294967295"
5252
}
5353

5454
===DONE===

tests/bson/bson-timestamp-serialization_error-001.phpt

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,23 @@ echo throws(function() {
1414
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";
1515

1616
echo throws(function() {
17-
unserialize('O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";s:4:"1234";s:9:"timestamp";s:4:"5678";}');
17+
unserialize('O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";i:1234;s:9:"timestamp";s:4:"5678";}');
18+
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";
19+
20+
echo throws(function() {
21+
unserialize('O:22:"MongoDB\BSON\Timestamp":2:{s:9:"increment";s:4:"1234";s:9:"timestamp";i:5678;}');
1822
}, 'MongoDB\Driver\Exception\InvalidArgumentException'), "\n";
1923

2024
?>
2125
===DONE===
2226
<?php exit(0); ?>
2327
--EXPECT--
2428
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
25-
MongoDB\BSON\Timestamp initialization requires "increment" and "timestamp" integer fields
29+
MongoDB\BSON\Timestamp initialization requires "increment" and "timestamp" integer or numeric string fields
30+
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
31+
MongoDB\BSON\Timestamp initialization requires "increment" and "timestamp" integer or numeric string fields
2632
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
27-
MongoDB\BSON\Timestamp initialization requires "increment" and "timestamp" integer fields
33+
MongoDB\BSON\Timestamp initialization requires "increment" and "timestamp" integer or numeric string fields
2834
OK: Got MongoDB\Driver\Exception\InvalidArgumentException
29-
MongoDB\BSON\Timestamp initialization requires "increment" and "timestamp" integer fields
35+
MongoDB\BSON\Timestamp initialization requires "increment" and "timestamp" integer or numeric string fields
3036
===DONE===

0 commit comments

Comments
 (0)