Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 28 additions & 80 deletions src/bson.c
Original file line number Diff line number Diff line change
Expand Up @@ -1271,44 +1271,6 @@ static void phongo_bson_append(bson_t *bson, php_phongo_bson_flags_t flags, cons
}
}

#if PHP_VERSION_ID >= 70000
static bool is_public_property(zend_class_entry *ce, zend_string *name, zend_string **member TSRMLS_DC) /* {{{ */
#else
static bool is_public_property(zend_class_entry *ce, const char *prop_name, int prop_name_len TSRMLS_DC) /* {{{ */
#endif
{
zend_property_info *property_info = NULL;

#if PHP_VERSION_ID >= 70000
if (ZSTR_VAL(name)[0] == 0) {
const char *prop_name,
*class_name;
size_t prop_name_len;

zend_unmangle_property_name_ex(name,
&class_name, &prop_name, &prop_name_len);
(*member) = zend_string_init(prop_name, prop_name_len, 0);
} else (*member) = zend_string_copy(name);
property_info = zend_get_property_info(ce, (*member), 1 TSRMLS_CC);

if (!property_info) /* undefined property */
return true;

if (property_info == ZEND_WRONG_PROPERTY_INFO) {
return false;
}

return (property_info->flags & ZEND_ACC_PUBLIC);
#else
zval member;
ZVAL_STRINGL(&member, prop_name, prop_name_len, 0);
property_info = zend_get_property_info(ce, &member, 1 TSRMLS_CC);

return (property_info && (property_info->flags & ZEND_ACC_PUBLIC));
#endif
}
/* }}} */

void phongo_zval_to_bson(zval *data, php_phongo_bson_flags_t flags, bson_t *bson, bson_t **bson_out TSRMLS_DC) /* {{{ */
{
HashTable *ht_data = NULL;
Expand Down Expand Up @@ -1422,49 +1384,37 @@ void phongo_zval_to_bson(zval *data, php_phongo_bson_flags_t flags, bson_t *bson
zval *value;

ZEND_HASH_FOREACH_KEY_VAL(ht_data, num_key, string_key, value) {
/* Ensure we're working with a string key */
if (!string_key) {
string_key = zend_long_to_str(num_key);
} else {
zend_string_addref(string_key);
}

if (Z_TYPE_P(data) == IS_OBJECT) {
zend_string *member = NULL;

/* Ignore non-public properties */
if (!instanceof_function(Z_OBJCE_P(data), php_phongo_serializable_ce) &&
!is_public_property(Z_OBJCE_P(data), string_key, &member TSRMLS_CC)) {
if (member) {
zend_string_release(member);
}
zend_string_release(string_key);
continue;
}

if (flags & PHONGO_BSON_ADD_ID) {
if (!strcmp(member ? ZSTR_VAL(member) : ZSTR_VAL(string_key), "_id")) {
flags &= ~PHONGO_BSON_ADD_ID;
if (string_key) {
if (ht_data_from_properties) {
/* Skip protected and private properties */
if (ZSTR_VAL(string_key)[0] == '\0' && ZSTR_LEN(string_key) > 0) {
zend_string_release(string_key);
continue;
}
}

phongo_bson_append(bson, flags & ~PHONGO_BSON_ADD_ID,
member ? ZSTR_VAL(member) : ZSTR_VAL(string_key),
member ? ZSTR_LEN(member) : ZSTR_LEN(string_key),
value TSRMLS_CC);
if (strlen(ZSTR_VAL(string_key)) != ZSTR_LEN(string_key)) {
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE TSRMLS_CC, "BSON keys cannot contain null bytes. Unexpected null byte after \"%s\".", ZSTR_VAL(string_key));

if (member) {
zend_string_release(member);
return;
}
} else {

if (flags & PHONGO_BSON_ADD_ID) {
if (!strcmp(ZSTR_VAL(string_key), "_id")) {
flags &= ~PHONGO_BSON_ADD_ID;
}
}
phongo_bson_append(bson, flags & ~PHONGO_BSON_ADD_ID, ZSTR_VAL(string_key), ZSTR_LEN(string_key), value TSRMLS_CC);
}

/* Ensure we're working with a string key */
if (!string_key) {
string_key = zend_long_to_str(num_key);
} else {
zend_string_addref(string_key);
}

phongo_bson_append(bson, flags & ~PHONGO_BSON_ADD_ID, ZSTR_VAL(string_key), strlen(ZSTR_VAL(string_key)), value TSRMLS_CC);

zend_string_release(string_key);
} ZEND_HASH_FOREACH_END();
}
Expand All @@ -1489,17 +1439,16 @@ void phongo_zval_to_bson(zval *data, php_phongo_bson_flags_t flags, bson_t *bson

if (hash_type == HASH_KEY_IS_STRING) {
if (ht_data_from_properties) {
const char *class_name;
zend_unmangle_property_name(string_key, string_key_len-1, &class_name, (const char **)&string_key);
string_key_len = strlen(string_key);

/* Ignore non-public properties */
if (!is_public_property(Z_OBJCE_P(data), string_key, string_key_len TSRMLS_CC)) {
/* Skip protected and private properties */
if (string_key[0] == '\0' && string_key_len > 1) {
continue;
}
} else {
/* Chop off the \0 from string lengths */
string_key_len -= 1;
}

if (strlen(string_key) != string_key_len - 1) {
phongo_throw_exception(PHONGO_ERROR_UNEXPECTED_VALUE TSRMLS_CC, "BSON keys cannot contain null bytes. Unexpected null byte after \"%s\".", ZSTR_VAL(string_key));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this exception message. This appears after we might skip object properties whose name contains a leading null byte (i.e. PHP's encoding for protected and private members). For arrays, the check happens on all string keys.

We don't check this for integer keys, since we convert those to strings ourself and don't have to worry about null bytes.


return;
}

if (flags & PHONGO_BSON_ADD_ID) {
Expand All @@ -1512,10 +1461,9 @@ void phongo_zval_to_bson(zval *data, php_phongo_bson_flags_t flags, bson_t *bson
/* Ensure we're working with a string key */
if (hash_type == HASH_KEY_IS_LONG) {
spprintf(&string_key, 0, "%ld", num_key);
string_key_len = strlen(string_key);
}

phongo_bson_append(bson, flags & ~PHONGO_BSON_ADD_ID, string_key, string_key_len, *value TSRMLS_CC);
phongo_bson_append(bson, flags & ~PHONGO_BSON_ADD_ID, string_key, strlen(string_key), *value TSRMLS_CC);

if (hash_type == HASH_KEY_IS_LONG) {
efree(string_key);
Expand Down
27 changes: 27 additions & 0 deletions tests/bson/bson-fromPHP-005.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
BSON\fromPHP(): PHP document with public property whose name is an empty string
--FILE--
<?php

require_once __DIR__ . '/../utils/tools.php';

$tests = [
['' => 1],
(object) ['' => 1],
];

foreach ($tests as $document) {
$s = fromPHP($document);
echo "Test ", toJSON($s), "\n";
hex_dump($s);
}

?>
===DONE===
<?php exit(0); ?>
--EXPECT--
Test { "" : 1 }
0 : 0b 00 00 00 10 00 01 00 00 00 00 [...........]
Test { "" : 1 }
0 : 0b 00 00 00 10 00 01 00 00 00 00 [...........]
===DONE===
62 changes: 62 additions & 0 deletions tests/bson/bson-fromPHP-006.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
--TEST--
BSON\fromPHP(): PHP documents with null bytes in field name
--FILE--
<?php

require_once __DIR__ . '/../utils/tools.php';

echo "\nTesting array with one leading null byte in field name\n";
echo throws(function() {
fromPHP(["\0" => 1]);
}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n";

echo "\nTesting array with one trailing null byte in field name\n";
echo throws(function() {
fromPHP(["a\0" => 1]);
}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n";

echo "\nTesting array with multiple null bytes in field name\n";
echo throws(function() {
fromPHP(["\0\0\0" => 1]);
}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n";

/* Per PHPC-884, field names with a leading null byte are ignored when encoding
* a document from an object's property hash table, since PHP uses leading bytes
* to denote protected and private properties. */
echo "\nTesting object with one leading null byte in field name\n";
hex_dump(fromPHP((object) ["\0" => 1]));

echo "\nTesting object with one trailing null byte in field name\n";
echo throws(function() {
fromPHP((object) ["a\0" => 1]);
}, 'MongoDB\Driver\Exception\UnexpectedValueException'), "\n";

echo "\nTesting object with multiple null bytes in field name\n";
hex_dump(fromPHP((object) ["\0\0\0" => 1]));

?>
===DONE===
<?php exit(0); ?>
--EXPECT--
Testing array with one leading null byte in field name
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
BSON keys cannot contain null bytes. Unexpected null byte after "".

Testing array with one trailing null byte in field name
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
BSON keys cannot contain null bytes. Unexpected null byte after "a".

Testing array with multiple null bytes in field name
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
BSON keys cannot contain null bytes. Unexpected null byte after "".

Testing object with one leading null byte in field name
0 : 05 00 00 00 00 [.....]

Testing object with one trailing null byte in field name
OK: Got MongoDB\Driver\Exception\UnexpectedValueException
BSON keys cannot contain null bytes. Unexpected null byte after "a".

Testing object with multiple null bytes in field name
0 : 05 00 00 00 00 [.....]
===DONE===