/src/php-src/ext/standard/http.c
Line | Count | Source |
1 | | /* |
2 | | +----------------------------------------------------------------------+ |
3 | | | Copyright © The PHP Group and Contributors. | |
4 | | +----------------------------------------------------------------------+ |
5 | | | This source file is subject to the Modified BSD License that is | |
6 | | | bundled with this package in the file LICENSE, and is available | |
7 | | | through the World Wide Web at <https://www.php.net/license/>. | |
8 | | | | |
9 | | | SPDX-License-Identifier: BSD-3-Clause | |
10 | | +----------------------------------------------------------------------+ |
11 | | | Authors: Sara Golemon <pollita@php.net> | |
12 | | +----------------------------------------------------------------------+ |
13 | | */ |
14 | | |
15 | | #include "php_http.h" |
16 | | #include "php_ini.h" |
17 | | #include "url.h" |
18 | | #include "SAPI.h" |
19 | | #include "zend_exceptions.h" |
20 | | #include "basic_functions.h" |
21 | | #include "zend_enum.h" |
22 | | |
23 | | static void php_url_encode_scalar(zval *scalar, smart_str *form_str, |
24 | | int encoding_type, zend_ulong index_int, |
25 | | const char *index_string, size_t index_string_len, |
26 | | const char *num_prefix, size_t num_prefix_len, |
27 | | const zend_string *key_prefix, |
28 | | const zend_string *arg_sep) |
29 | 0 | { |
30 | 0 | if (form_str->s) { |
31 | 0 | smart_str_append(form_str, arg_sep); |
32 | 0 | } |
33 | | /* Simple key=value */ |
34 | 0 | if (key_prefix) { |
35 | 0 | smart_str_append(form_str, key_prefix); |
36 | 0 | } |
37 | 0 | if (index_string) { |
38 | 0 | php_url_encode_to_smart_str(form_str, index_string, index_string_len, encoding_type == PHP_QUERY_RFC3986); |
39 | 0 | } else { |
40 | | /* Numeric key */ |
41 | 0 | if (num_prefix) { |
42 | 0 | smart_str_appendl(form_str, num_prefix, num_prefix_len); |
43 | 0 | } |
44 | 0 | smart_str_append_long(form_str, index_int); |
45 | 0 | } |
46 | 0 | if (key_prefix) { |
47 | 0 | smart_str_appendl(form_str, "%5D", strlen("%5D")); |
48 | 0 | } |
49 | 0 | smart_str_appendc(form_str, '='); |
50 | |
|
51 | 0 | try_again: |
52 | 0 | switch (Z_TYPE_P(scalar)) { |
53 | 0 | case IS_STRING: |
54 | 0 | php_url_encode_to_smart_str(form_str, Z_STRVAL_P(scalar), Z_STRLEN_P(scalar), encoding_type == PHP_QUERY_RFC3986); |
55 | 0 | break; |
56 | 0 | case IS_LONG: |
57 | 0 | smart_str_append_long(form_str, Z_LVAL_P(scalar)); |
58 | 0 | break; |
59 | 0 | case IS_DOUBLE: { |
60 | 0 | zend_string *tmp = zend_double_to_str(Z_DVAL_P(scalar)); |
61 | 0 | php_url_encode_to_smart_str(form_str, ZSTR_VAL(tmp), ZSTR_LEN(tmp), encoding_type == PHP_QUERY_RFC3986); |
62 | 0 | zend_string_free(tmp); |
63 | 0 | break; |
64 | 0 | } |
65 | 0 | case IS_FALSE: |
66 | 0 | smart_str_appendc(form_str, '0'); |
67 | 0 | break; |
68 | 0 | case IS_TRUE: |
69 | 0 | smart_str_appendc(form_str, '1'); |
70 | 0 | break; |
71 | 0 | case IS_OBJECT: |
72 | 0 | ZEND_ASSERT(Z_OBJCE_P(scalar)->ce_flags & ZEND_ACC_ENUM); |
73 | 0 | if (Z_OBJCE_P(scalar)->enum_backing_type == IS_UNDEF) { |
74 | 0 | zend_value_error("Unbacked enum %s cannot be converted to a string", ZSTR_VAL(Z_OBJCE_P(scalar)->name)); |
75 | 0 | return; |
76 | 0 | } |
77 | 0 | scalar = zend_enum_fetch_case_value(Z_OBJ_P(scalar)); |
78 | 0 | goto try_again; |
79 | | /* All possible types are either handled here or previously */ |
80 | 0 | default: ZEND_UNREACHABLE(); |
81 | 0 | } |
82 | 0 | } |
83 | | |
84 | | static zend_always_inline bool php_url_check_stack_limit(void) |
85 | 0 | { |
86 | 0 | #ifdef ZEND_CHECK_STACK_LIMIT |
87 | 0 | return zend_call_stack_overflowed(EG(stack_limit)); |
88 | | #else |
89 | | return false; |
90 | | #endif |
91 | 0 | } |
92 | | |
93 | | /* {{{ php_url_encode_hash */ |
94 | | PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr, |
95 | | const char *num_prefix, size_t num_prefix_len, |
96 | | const zend_string *key_prefix, |
97 | | zval *type, const zend_string *arg_sep, int enc_type) |
98 | 0 | { |
99 | 0 | zend_string *key = NULL; |
100 | 0 | const char *prop_name; |
101 | 0 | size_t prop_len; |
102 | 0 | zend_ulong idx; |
103 | 0 | zval *zdata = NULL; |
104 | 0 | ZEND_ASSERT(ht); |
105 | |
|
106 | 0 | if (GC_IS_RECURSIVE(ht)) { |
107 | | /* Prevent recursion */ |
108 | 0 | return; |
109 | 0 | } |
110 | | |
111 | | /* Very deeply structured data could trigger a stack overflow, even without recursion. */ |
112 | 0 | if (UNEXPECTED(php_url_check_stack_limit())) { |
113 | 0 | zend_throw_error(NULL, "Maximum call stack size reached."); |
114 | 0 | return; |
115 | 0 | } |
116 | | |
117 | 0 | if (!arg_sep) { |
118 | 0 | arg_sep = PG(arg_separator).output; |
119 | 0 | if (ZSTR_LEN(arg_sep) == 0) { |
120 | 0 | arg_sep = ZSTR_CHAR('&'); |
121 | 0 | } |
122 | 0 | } |
123 | |
|
124 | 0 | ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, zdata) { |
125 | 0 | bool is_dynamic = true; |
126 | 0 | if (Z_TYPE_P(zdata) == IS_INDIRECT) { |
127 | 0 | zdata = Z_INDIRECT_P(zdata); |
128 | 0 | if (Z_ISUNDEF_P(zdata)) { |
129 | 0 | continue; |
130 | 0 | } |
131 | | |
132 | 0 | is_dynamic = false; |
133 | 0 | } |
134 | | |
135 | | /* handling for private & protected object properties */ |
136 | 0 | if (key) { |
137 | 0 | prop_name = ZSTR_VAL(key); |
138 | 0 | prop_len = ZSTR_LEN(key); |
139 | |
|
140 | 0 | if (type != NULL && zend_check_property_access(Z_OBJ_P(type), key, is_dynamic) != SUCCESS) { |
141 | | /* property not visible in this scope */ |
142 | 0 | continue; |
143 | 0 | } |
144 | | |
145 | 0 | if (ZSTR_VAL(key)[0] == '\0' && type != NULL) { |
146 | 0 | const char *tmp; |
147 | 0 | zend_unmangle_property_name_ex(key, &tmp, &prop_name, &prop_len); |
148 | 0 | } else { |
149 | 0 | prop_name = ZSTR_VAL(key); |
150 | 0 | prop_len = ZSTR_LEN(key); |
151 | 0 | } |
152 | 0 | } else { |
153 | 0 | prop_name = NULL; |
154 | 0 | prop_len = 0; |
155 | 0 | } |
156 | | |
157 | 0 | ZVAL_DEREF(zdata); |
158 | 0 | if (Z_TYPE_P(zdata) == IS_ARRAY |
159 | 0 | || (Z_TYPE_P(zdata) == IS_OBJECT |
160 | 0 | && !(Z_OBJCE_P(zdata)->ce_flags & ZEND_ACC_ENUM))) { |
161 | 0 | zend_string *new_prefix; |
162 | 0 | if (key) { |
163 | 0 | zend_string *encoded_key; |
164 | 0 | if (enc_type == PHP_QUERY_RFC3986) { |
165 | 0 | encoded_key = php_raw_url_encode(prop_name, prop_len); |
166 | 0 | } else { |
167 | 0 | encoded_key = php_url_encode(prop_name, prop_len); |
168 | 0 | } |
169 | |
|
170 | 0 | if (key_prefix) { |
171 | 0 | 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")); |
172 | 0 | } else { |
173 | 0 | new_prefix = zend_string_concat2(ZSTR_VAL(encoded_key), ZSTR_LEN(encoded_key), "%5B", strlen("%5B")); |
174 | 0 | } |
175 | 0 | zend_string_efree(encoded_key); |
176 | 0 | } else { /* is integer index */ |
177 | 0 | char *index_int_as_str; |
178 | 0 | size_t index_int_as_str_len; |
179 | |
|
180 | 0 | index_int_as_str_len = spprintf(&index_int_as_str, 0, ZEND_LONG_FMT, idx); |
181 | |
|
182 | 0 | if (key_prefix && num_prefix) { |
183 | | /* zend_string_concat4() */ |
184 | 0 | size_t len = ZSTR_LEN(key_prefix) + num_prefix_len + index_int_as_str_len + strlen("%5D%5B"); |
185 | 0 | new_prefix = zend_string_alloc(len, 0); |
186 | |
|
187 | 0 | memcpy(ZSTR_VAL(new_prefix), ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix)); |
188 | 0 | memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix), num_prefix, num_prefix_len); |
189 | 0 | memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len, index_int_as_str, index_int_as_str_len); |
190 | 0 | memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len +index_int_as_str_len, "%5D%5B", strlen("%5D%5B")); |
191 | 0 | ZSTR_VAL(new_prefix)[len] = '\0'; |
192 | 0 | } else if (key_prefix) { |
193 | 0 | 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")); |
194 | 0 | } else if (num_prefix) { |
195 | 0 | new_prefix = zend_string_concat3(num_prefix, num_prefix_len, index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B")); |
196 | 0 | } else { |
197 | 0 | new_prefix = zend_string_concat2(index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B")); |
198 | 0 | } |
199 | 0 | efree(index_int_as_str); |
200 | 0 | } |
201 | 0 | GC_TRY_PROTECT_RECURSION(ht); |
202 | 0 | 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); |
203 | 0 | GC_TRY_UNPROTECT_RECURSION(ht); |
204 | 0 | zend_string_efree(new_prefix); |
205 | 0 | } else if (Z_TYPE_P(zdata) == IS_NULL || Z_TYPE_P(zdata) == IS_RESOURCE) { |
206 | | /* Skip these types */ |
207 | 0 | continue; |
208 | 0 | } else { |
209 | 0 | php_url_encode_scalar(zdata, formstr, |
210 | 0 | enc_type, idx, |
211 | 0 | prop_name, prop_len, |
212 | 0 | num_prefix, num_prefix_len, |
213 | 0 | key_prefix, |
214 | 0 | arg_sep); |
215 | 0 | } |
216 | 0 | } ZEND_HASH_FOREACH_END(); |
217 | 0 | } |
218 | | /* }}} */ |
219 | | |
220 | | /* If there is a prefix we need to close the key with an encoded ] ("%5D") */ |
221 | | /* {{{ Generates a form-encoded query string from an associative array or object. */ |
222 | | PHP_FUNCTION(http_build_query) |
223 | 0 | { |
224 | 0 | zval *formdata; |
225 | 0 | char *prefix = NULL; |
226 | 0 | size_t prefix_len = 0; |
227 | 0 | zend_string *arg_sep = NULL; |
228 | 0 | smart_str formstr = {0}; |
229 | 0 | zend_long enc_type = PHP_QUERY_RFC1738; |
230 | |
|
231 | 0 | ZEND_PARSE_PARAMETERS_START(1, 4) |
232 | 0 | Z_PARAM_ARRAY_OR_OBJECT(formdata) |
233 | 0 | Z_PARAM_OPTIONAL |
234 | 0 | Z_PARAM_STRING(prefix, prefix_len) |
235 | 0 | Z_PARAM_STR_OR_NULL(arg_sep) |
236 | 0 | Z_PARAM_LONG(enc_type) |
237 | 0 | ZEND_PARSE_PARAMETERS_END(); |
238 | | |
239 | 0 | if (UNEXPECTED(Z_TYPE_P(formdata) == IS_OBJECT && (Z_OBJCE_P(formdata)->ce_flags & ZEND_ACC_ENUM))) { |
240 | 0 | zend_argument_type_error(1, "must not be an enum, %s given", zend_zval_value_name(formdata)); |
241 | 0 | RETURN_THROWS(); |
242 | 0 | } |
243 | | |
244 | 0 | 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); |
245 | |
|
246 | 0 | RETURN_STR(smart_str_extract(&formstr)); |
247 | 0 | } |
248 | | /* }}} */ |
249 | | |
250 | | static zend_result cache_request_parse_body_option(HashTable *options, zval *option, int cache_offset) |
251 | 0 | { |
252 | 0 | if (option) { |
253 | 0 | zend_long result; |
254 | 0 | ZVAL_DEREF(option); |
255 | 0 | if (Z_TYPE_P(option) == IS_STRING) { |
256 | 0 | zend_string *errstr; |
257 | 0 | result = zend_ini_parse_quantity(Z_STR_P(option), &errstr); |
258 | 0 | if (errstr) { |
259 | 0 | zend_error(E_WARNING, "%s", ZSTR_VAL(errstr)); |
260 | 0 | zend_string_release(errstr); |
261 | 0 | } |
262 | 0 | } else if (Z_TYPE_P(option) == IS_LONG) { |
263 | 0 | result = Z_LVAL_P(option); |
264 | 0 | } else { |
265 | 0 | zend_value_error("Invalid %s value in $options argument", zend_zval_value_name(option)); |
266 | 0 | return FAILURE; |
267 | 0 | } |
268 | 0 | SG(request_parse_body_context).options_cache[cache_offset].set = true; |
269 | 0 | SG(request_parse_body_context).options_cache[cache_offset].value = result; |
270 | 0 | } else { |
271 | 0 | SG(request_parse_body_context).options_cache[cache_offset].set = false; |
272 | 0 | } |
273 | | |
274 | 0 | return SUCCESS; |
275 | 0 | } |
276 | | |
277 | | static zend_result cache_request_parse_body_options(HashTable *options) |
278 | 0 | { |
279 | 0 | zend_string *key; |
280 | 0 | zval *value; |
281 | 0 | ZEND_HASH_FOREACH_STR_KEY_VAL(options, key, value) { |
282 | 0 | if (!key) { |
283 | 0 | zend_value_error("Invalid integer key in $options argument"); |
284 | 0 | return FAILURE; |
285 | 0 | } |
286 | 0 | if (ZSTR_LEN(key) == 0) { |
287 | 0 | zend_value_error("Invalid empty string key in $options argument"); |
288 | 0 | return FAILURE; |
289 | 0 | } |
290 | | |
291 | 0 | #define CHECK_OPTION(name) \ |
292 | 0 | if (zend_string_equals_literal_ci(key, #name)) { \ |
293 | 0 | if (cache_request_parse_body_option(options, value, REQUEST_PARSE_BODY_OPTION_ ## name) == FAILURE) { \ |
294 | 0 | return FAILURE; \ |
295 | 0 | } \ |
296 | 0 | continue; \ |
297 | 0 | } |
298 | | |
299 | 0 | switch (ZSTR_VAL(key)[0]) { |
300 | 0 | case 'm': |
301 | 0 | case 'M': |
302 | 0 | CHECK_OPTION(max_file_uploads); |
303 | 0 | CHECK_OPTION(max_input_vars); |
304 | 0 | CHECK_OPTION(max_multipart_body_parts); |
305 | 0 | break; |
306 | 0 | case 'p': |
307 | 0 | case 'P': |
308 | 0 | CHECK_OPTION(post_max_size); |
309 | 0 | break; |
310 | 0 | case 'u': |
311 | 0 | case 'U': |
312 | 0 | CHECK_OPTION(upload_max_filesize); |
313 | 0 | break; |
314 | 0 | } |
315 | | |
316 | 0 | zend_value_error("Invalid key \"%s\" in $options argument", ZSTR_VAL(key)); |
317 | 0 | return FAILURE; |
318 | 0 | } ZEND_HASH_FOREACH_END(); |
319 | | |
320 | 0 | #undef CACHE_OPTION |
321 | | |
322 | 0 | return SUCCESS; |
323 | 0 | } |
324 | | |
325 | | PHP_FUNCTION(request_parse_body) |
326 | 0 | { |
327 | 0 | HashTable *options = NULL; |
328 | |
|
329 | 0 | ZEND_PARSE_PARAMETERS_START(0, 1) |
330 | 0 | Z_PARAM_OPTIONAL |
331 | 0 | Z_PARAM_ARRAY_HT_OR_NULL(options) |
332 | 0 | ZEND_PARSE_PARAMETERS_END(); |
333 | | |
334 | 0 | SG(request_parse_body_context).throw_exceptions = true; |
335 | 0 | if (options) { |
336 | 0 | if (cache_request_parse_body_options(options) == FAILURE) { |
337 | 0 | goto exit; |
338 | 0 | } |
339 | 0 | } |
340 | | |
341 | 0 | if (!SG(request_info).content_type) { |
342 | 0 | zend_throw_error(zend_ce_request_parse_body_exception, "Request does not provide a content type"); |
343 | 0 | goto exit; |
344 | 0 | } |
345 | | |
346 | 0 | sapi_read_post_data(); |
347 | 0 | if (!SG(request_info).post_entry) { |
348 | 0 | zend_throw_error(zend_ce_request_parse_body_exception, "Content-Type \"%s\" is not supported", SG(request_info).content_type); |
349 | 0 | goto exit; |
350 | 0 | } |
351 | | |
352 | 0 | zval post, files, old_post, old_files; |
353 | 0 | zval *global_post = &PG(http_globals)[TRACK_VARS_POST]; |
354 | 0 | zval *global_files = &PG(http_globals)[TRACK_VARS_FILES]; |
355 | |
|
356 | 0 | ZVAL_COPY_VALUE(&old_post, global_post); |
357 | 0 | ZVAL_COPY_VALUE(&old_files, global_files); |
358 | 0 | array_init(global_post); |
359 | 0 | array_init(global_files); |
360 | 0 | sapi_handle_post(global_post); |
361 | 0 | ZVAL_COPY_VALUE(&post, global_post); |
362 | 0 | ZVAL_COPY_VALUE(&files, global_files); |
363 | 0 | ZVAL_COPY_VALUE(global_post, &old_post); |
364 | 0 | ZVAL_COPY_VALUE(global_files, &old_files); |
365 | |
|
366 | 0 | RETVAL_ARR(zend_new_pair(&post, &files)); |
367 | |
|
368 | 0 | exit: |
369 | 0 | SG(request_parse_body_context).throw_exceptions = false; |
370 | 0 | memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache)); |
371 | 0 | } |
372 | | |
373 | | PHP_FUNCTION(http_get_last_response_headers) |
374 | 0 | { |
375 | 0 | ZEND_PARSE_PARAMETERS_NONE(); |
376 | | |
377 | 0 | if (!Z_ISUNDEF(BG(last_http_headers))) { |
378 | 0 | RETURN_COPY(&BG(last_http_headers)); |
379 | 0 | } else { |
380 | 0 | RETURN_NULL(); |
381 | 0 | } |
382 | 0 | } |
383 | | |
384 | | PHP_FUNCTION(http_clear_last_response_headers) |
385 | 0 | { |
386 | 0 | ZEND_PARSE_PARAMETERS_NONE(); |
387 | | |
388 | 0 | zval_ptr_dtor(&BG(last_http_headers)); |
389 | 0 | ZVAL_UNDEF(&BG(last_http_headers)); |
390 | 0 | } |