Skip to content

Commit 117564a

Browse files
committed
Implementation following behaviour defined by nielsdos
1 parent 99f2043 commit 117564a

File tree

3 files changed

+151
-92
lines changed

3 files changed

+151
-92
lines changed

ext/standard/http.c

Lines changed: 149 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str,
6262
encoded_data = php_url_encode(Z_STRVAL_P(scalar), Z_STRLEN_P(scalar));
6363
}
6464
smart_str_append(form_str, encoded_data);
65-
zend_string_free(encoded_data);
65+
zend_string_release_ex(encoded_data, false);
6666
break;
6767
}
6868
case IS_LONG:
@@ -77,8 +77,8 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str,
7777
encoded_data = php_url_encode(ZSTR_VAL(tmp), ZSTR_LEN(tmp));
7878
}
7979
smart_str_append(form_str, encoded_data);
80-
zend_string_free(tmp);
81-
zend_string_free(encoded_data);
80+
zend_string_release_ex(tmp, false);
81+
zend_string_release_ex(encoded_data, false);
8282
break;
8383
}
8484
case IS_FALSE:
@@ -92,11 +92,137 @@ static void php_url_encode_scalar(zval *scalar, smart_str *form_str,
9292
}
9393
}
9494

95+
static HashTable* php_object_get_visible_properties(/* const */ zend_object *object)
96+
{
97+
/* const */ //HashTable *properties = zend_array_dup(object->handlers->get_properties(object));
98+
/* const */ HashTable *properties = object->handlers->get_properties(object);
99+
//HashTable *visible_properties = zend_new_array(zend_hash_num_elements(properties));
100+
HashTable *visible_properties = zend_array_dup(object->handlers->get_properties(object));
101+
zend_string *property_name;
102+
zval *value;
103+
104+
ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(properties, property_name, value) {
105+
bool is_dynamic = true;
106+
if (Z_TYPE_P(value) == IS_INDIRECT) {
107+
value = Z_INDIRECT_P(value);
108+
if (Z_ISUNDEF_P(value)) {
109+
continue;
110+
}
111+
is_dynamic = false;
112+
}
113+
114+
if (zend_check_property_access(object, property_name, is_dynamic) == FAILURE) {
115+
/* property not visible in this scope */
116+
zend_hash_del(visible_properties, property_name);
117+
continue;
118+
}
119+
120+
/* handling for private & protected object properties */
121+
if (ZSTR_VAL(property_name)[0] == '\0') {
122+
zend_hash_del(visible_properties, property_name);
123+
124+
const char *tmp;
125+
const char *unmangled_name;
126+
size_t unmangled_name_len;
127+
zend_unmangle_property_name_ex(property_name, &tmp, &unmangled_name, &unmangled_name_len);
128+
zend_hash_str_add(visible_properties, unmangled_name, unmangled_name_len, value);
129+
}
130+
} ZEND_HASH_FOREACH_END();
131+
//zend_array_destroy(properties);
132+
return visible_properties;
133+
}
134+
135+
static zend_string* php_url_encode_get_new_prefix(
136+
int encoding_type, zend_ulong index_int,
137+
const char *index_string, size_t index_string_len,
138+
const char *num_prefix, size_t num_prefix_len,
139+
const zend_string *key_prefix
140+
) {
141+
zend_string *new_prefix;
142+
143+
if (index_string) {
144+
zend_string *encoded_key;
145+
if (encoding_type == PHP_QUERY_RFC3986) {
146+
encoded_key = php_raw_url_encode(index_string, index_string_len);
147+
} else {
148+
encoded_key = php_url_encode(index_string, index_string_len);
149+
}
150+
151+
if (key_prefix) {
152+
new_prefix = zend_string_concat3(ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix), ZSTR_VAL(encoded_key), ZSTR_LEN(encoded_key), "%5D%5B", strlen("%5D%5B"));
153+
} else {
154+
new_prefix = zend_string_concat2(ZSTR_VAL(encoded_key), ZSTR_LEN(encoded_key), "%5B", strlen("%5B"));
155+
}
156+
zend_string_release_ex(encoded_key, false);
157+
} else { /* is integer index */
158+
char *index_int_as_str;
159+
size_t index_int_as_str_len;
160+
161+
index_int_as_str_len = spprintf(&index_int_as_str, 0, ZEND_LONG_FMT, index_int);
162+
163+
if (key_prefix && num_prefix) {
164+
/* zend_string_concat4() */
165+
size_t len = ZSTR_LEN(key_prefix) + num_prefix_len + index_int_as_str_len + strlen("%5D%5B");
166+
new_prefix = zend_string_alloc(len, 0);
167+
168+
memcpy(ZSTR_VAL(new_prefix), ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix));
169+
memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix), num_prefix, num_prefix_len);
170+
memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len, index_int_as_str, index_int_as_str_len);
171+
memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len +index_int_as_str_len, "%5D%5B", strlen("%5D%5B"));
172+
ZSTR_VAL(new_prefix)[len] = '\0';
173+
} else if (key_prefix) {
174+
new_prefix = zend_string_concat3(ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix), index_int_as_str, index_int_as_str_len, "%5D%5B", strlen("%5D%5B"));
175+
} else if (num_prefix) {
176+
new_prefix = zend_string_concat3(num_prefix, num_prefix_len, index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B"));
177+
} else {
178+
new_prefix = zend_string_concat2(index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B"));
179+
}
180+
efree(index_int_as_str);
181+
}
182+
183+
return new_prefix;
184+
}
185+
186+
static void php_url_encode_object(zend_object *object, smart_str *form_str,
187+
int encoding_type, zend_ulong index_int,
188+
const char *index_string, size_t index_string_len,
189+
const char *num_prefix, size_t num_prefix_len,
190+
const zend_string *key_prefix,
191+
const zend_string *arg_sep)
192+
{
193+
if (GC_IS_RECURSIVE(object)) {
194+
/* Prevent recursion */
195+
return;
196+
}
197+
198+
HashTable *properties = php_object_get_visible_properties(object);
199+
if (zend_hash_num_elements(properties) == 0) {
200+
zend_array_destroy(properties);
201+
202+
zval tmp;
203+
/* If the data object is stringable without visible properties handle it like a string instead of empty array */
204+
if (object->handlers->cast_object(object, &tmp, IS_STRING) == SUCCESS) {
205+
php_url_encode_scalar(&tmp, form_str,
206+
encoding_type, index_int,
207+
index_string, index_string_len,
208+
num_prefix, num_prefix_len,
209+
NULL,
210+
arg_sep);
211+
}
212+
return;
213+
}
214+
215+
GC_TRY_PROTECT_RECURSION(object);
216+
php_url_encode_hash_ex(properties, form_str, num_prefix, num_prefix_len, key_prefix, NULL, arg_sep, encoding_type);
217+
GC_TRY_UNPROTECT_RECURSION(object);
218+
zend_array_destroy(properties);
219+
}
220+
95221
/* {{{ php_url_encode_hash */
96222
PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
97223
const char *num_prefix, size_t num_prefix_len,
98224
const zend_string *key_prefix,
99-
zval *type, const zend_string *arg_sep, int enc_type)
225+
ZEND_ATTRIBUTE_UNUSED zval *type, const zend_string *arg_sep, int enc_type)
100226
{
101227
zend_string *key = NULL;
102228
const char *prop_name;
@@ -118,97 +244,32 @@ PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr,
118244
}
119245

120246
ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, zdata) {
121-
bool is_dynamic = 1;
122-
if (Z_TYPE_P(zdata) == IS_INDIRECT) {
123-
zdata = Z_INDIRECT_P(zdata);
124-
if (Z_ISUNDEF_P(zdata)) {
125-
continue;
126-
}
127-
128-
is_dynamic = 0;
129-
}
130-
131-
/* handling for private & protected object properties */
132247
if (key) {
133248
prop_name = ZSTR_VAL(key);
134249
prop_len = ZSTR_LEN(key);
135-
136-
if (type != NULL && zend_check_property_access(Z_OBJ_P(type), key, is_dynamic) != SUCCESS) {
137-
/* property not visible in this scope */
138-
continue;
139-
}
140-
141-
if (ZSTR_VAL(key)[0] == '\0' && type != NULL) {
142-
const char *tmp;
143-
zend_unmangle_property_name_ex(key, &tmp, &prop_name, &prop_len);
144-
} else {
145-
prop_name = ZSTR_VAL(key);
146-
prop_len = ZSTR_LEN(key);
147-
}
148250
} else {
149251
prop_name = NULL;
150252
prop_len = 0;
151253
}
152254

153255
ZVAL_DEREF(zdata);
154-
if (Z_TYPE_P(zdata) == IS_ARRAY || Z_TYPE_P(zdata) == IS_OBJECT) {
155-
/* If the data object is stringable handle it like a string instead of recursively */
156-
zval tmp;
157-
if (Z_TYPE_P(zdata) == IS_OBJECT &&
158-
Z_OBJ_HT_P(zdata)->cast_object(Z_OBJ_P(zdata), &tmp, IS_STRING) == SUCCESS) {
159-
php_url_encode_scalar(&tmp, formstr,
160-
enc_type, idx,
161-
prop_name, prop_len,
162-
num_prefix, num_prefix_len,
163-
key_prefix,
164-
arg_sep);
165-
continue;
166-
}
167-
168-
zend_string *new_prefix;
169-
if (key) {
170-
zend_string *encoded_key;
171-
if (enc_type == PHP_QUERY_RFC3986) {
172-
encoded_key = php_raw_url_encode(prop_name, prop_len);
173-
} else {
174-
encoded_key = php_url_encode(prop_name, prop_len);
175-
}
176-
177-
if (key_prefix) {
178-
new_prefix = zend_string_concat3(ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix), ZSTR_VAL(encoded_key), ZSTR_LEN(encoded_key), "%5D%5B", strlen("%5D%5B"));
179-
} else {
180-
new_prefix = zend_string_concat2(ZSTR_VAL(encoded_key), ZSTR_LEN(encoded_key), "%5B", strlen("%5B"));
181-
}
182-
zend_string_release_ex(encoded_key, false);
183-
} else { /* is integer index */
184-
char *index_int_as_str;
185-
size_t index_int_as_str_len;
186-
187-
index_int_as_str_len = spprintf(&index_int_as_str, 0, ZEND_LONG_FMT, idx);
188-
189-
if (key_prefix && num_prefix) {
190-
/* zend_string_concat4() */
191-
size_t len = ZSTR_LEN(key_prefix) + num_prefix_len + index_int_as_str_len + strlen("%5D%5B");
192-
new_prefix = zend_string_alloc(len, 0);
193-
194-
memcpy(ZSTR_VAL(new_prefix), ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix));
195-
memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix), num_prefix, num_prefix_len);
196-
memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len, index_int_as_str, index_int_as_str_len);
197-
memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len +index_int_as_str_len, "%5D%5B", strlen("%5D%5B"));
198-
ZSTR_VAL(new_prefix)[len] = '\0';
199-
} else if (key_prefix) {
200-
new_prefix = zend_string_concat3(ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix), index_int_as_str, index_int_as_str_len, "%5D%5B", strlen("%5D%5B"));
201-
} else if (num_prefix) {
202-
new_prefix = zend_string_concat3(num_prefix, num_prefix_len, index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B"));
203-
} else {
204-
new_prefix = zend_string_concat2(index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B"));
205-
}
206-
efree(index_int_as_str);
207-
}
256+
if (Z_TYPE_P(zdata) == IS_ARRAY) {
257+
zend_string *new_prefix = php_url_encode_get_new_prefix(enc_type, idx, prop_name, prop_len,
258+
num_prefix, num_prefix_len, key_prefix);
208259
GC_TRY_PROTECT_RECURSION(ht);
209-
php_url_encode_hash_ex(HASH_OF(zdata), formstr, NULL, 0, new_prefix, (Z_TYPE_P(zdata) == IS_OBJECT ? zdata : NULL), arg_sep, enc_type);
260+
php_url_encode_hash_ex(Z_ARRVAL_P(zdata), formstr, NULL, 0, new_prefix, NULL, arg_sep, enc_type);
210261
GC_TRY_UNPROTECT_RECURSION(ht);
211262
zend_string_release_ex(new_prefix, false);
263+
} else if (Z_TYPE_P(zdata) == IS_OBJECT) {
264+
zend_string *new_prefix = php_url_encode_get_new_prefix(enc_type, idx, prop_name, prop_len,
265+
num_prefix, num_prefix_len, key_prefix);
266+
php_url_encode_object(Z_OBJ_P(zdata), formstr,
267+
enc_type, idx,
268+
prop_name, prop_len,
269+
num_prefix, num_prefix_len,
270+
new_prefix,
271+
arg_sep);
272+
zend_string_release_ex(new_prefix, false);
212273
} else if (Z_TYPE_P(zdata) == IS_NULL || Z_TYPE_P(zdata) == IS_RESOURCE) {
213274
/* Skip these types */
214275
continue;
@@ -243,17 +304,15 @@ PHP_FUNCTION(http_build_query)
243304
Z_PARAM_LONG(enc_type)
244305
ZEND_PARSE_PARAMETERS_END();
245306

246-
/* If the data object is stringable handle it like a string instead of recursively */
247-
zval tmp;
248-
if (Z_TYPE_P(formdata) == IS_OBJECT && Z_OBJ_HT_P(formdata)->cast_object(Z_OBJ_P(formdata), &tmp, IS_STRING) == SUCCESS) {
249-
php_url_encode_scalar(&tmp, &formstr,
250-
enc_type, 0,
251-
NULL, 0,
307+
if (Z_TYPE_P(formdata) == IS_OBJECT) {
308+
php_url_encode_object(Z_OBJ_P(formdata), &formstr,
309+
(int) enc_type, /* int_index */ 0,
310+
/* string_index */ NULL, 0,
252311
prefix, prefix_len,
253312
NULL,
254313
arg_sep);
255314
} else {
256-
php_url_encode_hash_ex(HASH_OF(formdata), &formstr, prefix, prefix_len, /* key_prefix */ NULL, (Z_TYPE_P(formdata) == IS_OBJECT ? formdata : NULL), arg_sep, (int)enc_type);
315+
php_url_encode_hash_ex(Z_ARRVAL_P(formdata), &formstr, prefix, prefix_len, /* key_prefix */ NULL, NULL, arg_sep, (int)enc_type);
257316
}
258317

259318
RETURN_STR(smart_str_extract(&formstr));

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) "foo=lala&bar=meuh&test=test"
25+
string(27) "test=test&bar=meuh&foo=lala"
2626
string(9) "test=test"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@ $o = new KeyValStringable();
1717
var_dump(http_build_query($o));
1818
?>
1919
--EXPECT--
20-
string(12) "0=Stringable"
20+
string(12) "public=input"

0 commit comments

Comments
 (0)