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