Skip to content

Commit d614724

Browse files
committed
Implement optional stringable behaviour for http_build_query
1 parent 4a96c4b commit d614724

File tree

3 files changed

+94
-16
lines changed

3 files changed

+94
-16
lines changed

ext/standard/http.c

Lines changed: 76 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,61 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str,
9595
}
9696
}
9797

98+
enum property_status {
99+
PROPERTY_STATUS_UNDEF,
100+
PROPERTY_STATUS_DYNAMIC,
101+
PROPERTY_STATUS_NOT_DYNAMIC,
102+
};
103+
104+
static zend_always_inline zval* compute_is_undef_or_dynamic_property(zval *zdata, enum property_status* status) {
105+
if (Z_TYPE_P(zdata) == IS_INDIRECT) {
106+
zdata = Z_INDIRECT_P(zdata);
107+
if (Z_ISUNDEF_P(zdata)) {
108+
*status = PROPERTY_STATUS_UNDEF;
109+
}
110+
111+
*status = PROPERTY_STATUS_NOT_DYNAMIC;
112+
} else {
113+
*status = PROPERTY_STATUS_DYNAMIC;
114+
}
115+
116+
return zdata;
117+
}
118+
119+
static bool has_public_properties(HashTable *ht, zval *type) {
120+
zend_string *key = NULL;
121+
zend_ulong _unused_idx;
122+
zval *zdata = NULL;
123+
ZEND_ASSERT(ht);
124+
ZEND_ASSERT(type);
125+
126+
ZEND_HASH_FOREACH_KEY_VAL(ht, _unused_idx, key, zdata) {
127+
enum property_status status;
128+
zdata = compute_is_undef_or_dynamic_property(zdata, &status);
129+
if (status == PROPERTY_STATUS_UNDEF) {
130+
continue;
131+
}
132+
bool is_dynamic = status == PROPERTY_STATUS_DYNAMIC;
133+
134+
if (zend_check_property_access(Z_OBJ_P(type), key, is_dynamic) == SUCCESS) {
135+
/* property visible in this scope */
136+
return true;
137+
}
138+
} ZEND_HASH_FOREACH_END();
139+
140+
return false;
141+
}
142+
143+
static bool should_handle_object_as_stringable(zval *zdata, zval *tmp) {
144+
/* If the data object is stringable and has no properties handle it like a string instead of recursively */
145+
if (Z_TYPE_P(zdata) == IS_OBJECT &&
146+
!has_public_properties(HASH_OF(zdata), zdata) &&
147+
Z_OBJ_HT_P(zdata)->cast_object(Z_OBJ_P(zdata), tmp, IS_STRING) == SUCCESS) {
148+
return true;
149+
}
150+
return false;
151+
}
152+
98153
/* {{{ php_url_encode_hash */
99154
PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
100155
const char *num_prefix, size_t num_prefix_len,
@@ -124,21 +179,15 @@ PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
124179
arg_sep_len = strlen(arg_sep);
125180

126181
ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, zdata) {
127-
bool is_dynamic = 1;
128-
if (Z_TYPE_P(zdata) == IS_INDIRECT) {
129-
zdata = Z_INDIRECT_P(zdata);
130-
if (Z_ISUNDEF_P(zdata)) {
131-
continue;
132-
}
133-
134-
is_dynamic = 0;
182+
enum property_status status;
183+
zdata = compute_is_undef_or_dynamic_property(zdata, &status);
184+
if (status == PROPERTY_STATUS_UNDEF) {
185+
continue;
135186
}
187+
bool is_dynamic = status == PROPERTY_STATUS_DYNAMIC;
136188

137189
/* handling for private & protected object properties */
138190
if (key) {
139-
prop_name = ZSTR_VAL(key);
140-
prop_len = ZSTR_LEN(key);
141-
142191
if (type != NULL && zend_check_property_access(Z_OBJ_P(type), key, is_dynamic) != SUCCESS) {
143192
/* property not visible in this scope */
144193
continue;
@@ -158,17 +207,16 @@ PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
158207

159208
ZVAL_DEREF(zdata);
160209
if (Z_TYPE_P(zdata) == IS_ARRAY || Z_TYPE_P(zdata) == IS_OBJECT) {
161-
/* If the data object is stringable handle it like a string instead of recursively */
162210
zval tmp;
163-
if (Z_TYPE_P(zdata) == IS_OBJECT &&
164-
Z_OBJ_HT_P(zdata)->cast_object(Z_OBJ_P(zdata), &tmp, IS_STRING) == SUCCESS) {
211+
if (should_handle_object_as_stringable(zdata, &tmp)) {
165212
php_url_encode_scalar(&tmp, formstr,
166213
enc_type, idx,
167214
prop_name, prop_len,
168215
num_prefix, num_prefix_len,
169216
key_prefix, key_prefix_len,
170217
key_suffix, key_suffix_len,
171218
arg_sep, arg_sep_len);
219+
zval_ptr_dtor(&tmp);
172220
continue;
173221
}
174222

@@ -269,7 +317,20 @@ PHP_FUNCTION(http_build_query)
269317
Z_PARAM_LONG(enc_type)
270318
ZEND_PARSE_PARAMETERS_END();
271319

272-
php_url_encode_hash_ex(HASH_OF(formdata), &formstr, prefix, prefix_len, NULL, 0, NULL, 0, (Z_TYPE_P(formdata) == IS_OBJECT ? formdata : NULL), arg_sep, (int)enc_type);
320+
/* Special case when we get an object that's stringable as input */
321+
zval tmp;
322+
if (should_handle_object_as_stringable(formdata, &tmp)) {
323+
php_url_encode_scalar(&tmp, &formstr,
324+
enc_type, 0,
325+
NULL, 0,
326+
prefix, prefix_len,
327+
NULL, 0,
328+
NULL, 0,
329+
arg_sep, arg_sep_len);
330+
zval_ptr_dtor(&tmp);
331+
} else {
332+
php_url_encode_hash_ex(HASH_OF(formdata), &formstr, prefix, prefix_len, NULL, 0, NULL, 0, (Z_TYPE_P(formdata) == IS_OBJECT ? formdata : NULL), arg_sep, (int)enc_type);
333+
}
273334

274335
if (!formstr.s) {
275336
RETURN_EMPTY_STRING();

ext/standard/tests/http/http_build_query/bug26817.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ $obj->foo();
2222
var_dump(http_build_query($obj));
2323
?>
2424
--EXPECT--
25-
string(27) "test=test&bar=meuh&foo=lala"
25+
string(27) "foo=lala&bar=meuh&test=test"
2626
string(9) "test=test"

ext/standard/tests/http/http_build_query/http_build_query_object_just_stringable.phpt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,19 @@ class StringableObject {
1010

1111
$o = new StringableObject();
1212

13+
var_dump(http_build_query(['hello', $o]));
14+
var_dump(http_build_query($o));
15+
var_dump(http_build_query(['hello', $o], numeric_prefix: 'prefix_'));
16+
var_dump(http_build_query($o, numeric_prefix: 'prefix_'));
17+
18+
class StringableObjectWithNonInternedString {
19+
public function __toString() : string {
20+
return str_repeat("abcd", 3);
21+
}
22+
}
23+
24+
$o = new StringableObjectWithNonInternedString();
25+
1326
var_dump(http_build_query(['hello', $o]));
1427
var_dump(http_build_query($o));
1528
var_dump(http_build_query(['hello', $o], numeric_prefix: 'prefix_'));
@@ -20,3 +33,7 @@ string(20) "0=hello&1=Stringable"
2033
string(12) "0=Stringable"
2134
string(34) "prefix_0=hello&prefix_1=Stringable"
2235
string(19) "prefix_0=Stringable"
36+
string(22) "0=hello&1=abcdabcdabcd"
37+
string(14) "0=abcdabcdabcd"
38+
string(36) "prefix_0=hello&prefix_1=abcdabcdabcd"
39+
string(21) "prefix_0=abcdabcdabcd"

0 commit comments

Comments
 (0)