/src/php-src/ext/standard/http.c
Line | Count | Source (jump to first uncovered line) |
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 | | /* {{{ php_url_encode_hash */ |
87 | | PHPAPI void php_url_encode_hash_ex(HashTable *ht, smart_str *formstr, |
88 | | const char *num_prefix, size_t num_prefix_len, |
89 | | const zend_string *key_prefix, |
90 | | zval *type, const zend_string *arg_sep, int enc_type) |
91 | 0 | { |
92 | 0 | zend_string *key = NULL; |
93 | 0 | const char *prop_name; |
94 | 0 | size_t prop_len; |
95 | 0 | zend_ulong idx; |
96 | 0 | zval *zdata = NULL; |
97 | 0 | ZEND_ASSERT(ht); |
98 | | |
99 | 0 | if (GC_IS_RECURSIVE(ht)) { |
100 | | /* Prevent recursion */ |
101 | 0 | return; |
102 | 0 | } |
103 | | |
104 | 0 | if (!arg_sep) { |
105 | 0 | arg_sep = PG(arg_separator).output; |
106 | 0 | if (ZSTR_LEN(arg_sep) == 0) { |
107 | 0 | arg_sep = ZSTR_CHAR('&'); |
108 | 0 | } |
109 | 0 | } |
110 | |
|
111 | 0 | ZEND_HASH_FOREACH_KEY_VAL(ht, idx, key, zdata) { |
112 | 0 | bool is_dynamic = 1; |
113 | 0 | if (Z_TYPE_P(zdata) == IS_INDIRECT) { |
114 | 0 | zdata = Z_INDIRECT_P(zdata); |
115 | 0 | if (Z_ISUNDEF_P(zdata)) { |
116 | 0 | continue; |
117 | 0 | } |
118 | | |
119 | 0 | is_dynamic = 0; |
120 | 0 | } |
121 | | |
122 | | /* handling for private & protected object properties */ |
123 | 0 | if (key) { |
124 | 0 | prop_name = ZSTR_VAL(key); |
125 | 0 | prop_len = ZSTR_LEN(key); |
126 | |
|
127 | 0 | if (type != NULL && zend_check_property_access(Z_OBJ_P(type), key, is_dynamic) != SUCCESS) { |
128 | | /* property not visible in this scope */ |
129 | 0 | continue; |
130 | 0 | } |
131 | | |
132 | 0 | if (ZSTR_VAL(key)[0] == '\0' && type != NULL) { |
133 | 0 | const char *tmp; |
134 | 0 | zend_unmangle_property_name_ex(key, &tmp, &prop_name, &prop_len); |
135 | 0 | } else { |
136 | 0 | prop_name = ZSTR_VAL(key); |
137 | 0 | prop_len = ZSTR_LEN(key); |
138 | 0 | } |
139 | 0 | } else { |
140 | 0 | prop_name = NULL; |
141 | 0 | prop_len = 0; |
142 | 0 | } |
143 | | |
144 | 0 | ZVAL_DEREF(zdata); |
145 | 0 | if (Z_TYPE_P(zdata) == IS_ARRAY |
146 | 0 | || (Z_TYPE_P(zdata) == IS_OBJECT |
147 | 0 | && !(Z_OBJCE_P(zdata)->ce_flags & ZEND_ACC_ENUM))) { |
148 | 0 | zend_string *new_prefix; |
149 | 0 | if (key) { |
150 | 0 | zend_string *encoded_key; |
151 | 0 | if (enc_type == PHP_QUERY_RFC3986) { |
152 | 0 | encoded_key = php_raw_url_encode(prop_name, prop_len); |
153 | 0 | } else { |
154 | 0 | encoded_key = php_url_encode(prop_name, prop_len); |
155 | 0 | } |
156 | |
|
157 | 0 | if (key_prefix) { |
158 | 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")); |
159 | 0 | } else { |
160 | 0 | new_prefix = zend_string_concat2(ZSTR_VAL(encoded_key), ZSTR_LEN(encoded_key), "%5B", strlen("%5B")); |
161 | 0 | } |
162 | 0 | zend_string_efree(encoded_key); |
163 | 0 | } else { /* is integer index */ |
164 | 0 | char *index_int_as_str; |
165 | 0 | size_t index_int_as_str_len; |
166 | |
|
167 | 0 | index_int_as_str_len = spprintf(&index_int_as_str, 0, ZEND_LONG_FMT, idx); |
168 | |
|
169 | 0 | if (key_prefix && num_prefix) { |
170 | | /* zend_string_concat4() */ |
171 | 0 | size_t len = ZSTR_LEN(key_prefix) + num_prefix_len + index_int_as_str_len + strlen("%5D%5B"); |
172 | 0 | new_prefix = zend_string_alloc(len, 0); |
173 | |
|
174 | 0 | memcpy(ZSTR_VAL(new_prefix), ZSTR_VAL(key_prefix), ZSTR_LEN(key_prefix)); |
175 | 0 | memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix), num_prefix, num_prefix_len); |
176 | 0 | memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len, index_int_as_str, index_int_as_str_len); |
177 | 0 | memcpy(ZSTR_VAL(new_prefix) + ZSTR_LEN(key_prefix) + num_prefix_len +index_int_as_str_len, "%5D%5B", strlen("%5D%5B")); |
178 | 0 | ZSTR_VAL(new_prefix)[len] = '\0'; |
179 | 0 | } else if (key_prefix) { |
180 | 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")); |
181 | 0 | } else if (num_prefix) { |
182 | 0 | new_prefix = zend_string_concat3(num_prefix, num_prefix_len, index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B")); |
183 | 0 | } else { |
184 | 0 | new_prefix = zend_string_concat2(index_int_as_str, index_int_as_str_len, "%5B", strlen("%5B")); |
185 | 0 | } |
186 | 0 | efree(index_int_as_str); |
187 | 0 | } |
188 | 0 | GC_TRY_PROTECT_RECURSION(ht); |
189 | 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); |
190 | 0 | GC_TRY_UNPROTECT_RECURSION(ht); |
191 | 0 | zend_string_efree(new_prefix); |
192 | 0 | } else if (Z_TYPE_P(zdata) == IS_NULL || Z_TYPE_P(zdata) == IS_RESOURCE) { |
193 | | /* Skip these types */ |
194 | 0 | continue; |
195 | 0 | } else { |
196 | 0 | php_url_encode_scalar(zdata, formstr, |
197 | 0 | enc_type, idx, |
198 | 0 | prop_name, prop_len, |
199 | 0 | num_prefix, num_prefix_len, |
200 | 0 | key_prefix, |
201 | 0 | arg_sep); |
202 | 0 | } |
203 | 0 | } ZEND_HASH_FOREACH_END(); |
204 | 0 | } |
205 | | /* }}} */ |
206 | | |
207 | | /* If there is a prefix we need to close the key with an encoded ] ("%5D") */ |
208 | | /* {{{ Generates a form-encoded query string from an associative array or object. */ |
209 | | PHP_FUNCTION(http_build_query) |
210 | 0 | { |
211 | 0 | zval *formdata; |
212 | 0 | char *prefix = NULL; |
213 | 0 | size_t prefix_len = 0; |
214 | 0 | zend_string *arg_sep = NULL; |
215 | 0 | smart_str formstr = {0}; |
216 | 0 | zend_long enc_type = PHP_QUERY_RFC1738; |
217 | |
|
218 | 0 | ZEND_PARSE_PARAMETERS_START(1, 4) |
219 | 0 | Z_PARAM_ARRAY_OR_OBJECT(formdata) |
220 | 0 | Z_PARAM_OPTIONAL |
221 | 0 | Z_PARAM_STRING(prefix, prefix_len) |
222 | 0 | Z_PARAM_STR_OR_NULL(arg_sep) |
223 | 0 | Z_PARAM_LONG(enc_type) |
224 | 0 | ZEND_PARSE_PARAMETERS_END(); |
225 | | |
226 | 0 | if (UNEXPECTED(Z_TYPE_P(formdata) == IS_OBJECT && (Z_OBJCE_P(formdata)->ce_flags & ZEND_ACC_ENUM))) { |
227 | 0 | zend_argument_type_error(1, "must not be an enum, %s given", zend_zval_value_name(formdata)); |
228 | 0 | RETURN_THROWS(); |
229 | 0 | } |
230 | | |
231 | 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); |
232 | |
|
233 | 0 | RETURN_STR(smart_str_extract(&formstr)); |
234 | 0 | } |
235 | | /* }}} */ |
236 | | |
237 | | static zend_result cache_request_parse_body_option(HashTable *options, zval *option, int cache_offset) |
238 | 0 | { |
239 | 0 | if (option) { |
240 | 0 | zend_long result; |
241 | 0 | ZVAL_DEREF(option); |
242 | 0 | if (Z_TYPE_P(option) == IS_STRING) { |
243 | 0 | zend_string *errstr; |
244 | 0 | result = zend_ini_parse_quantity(Z_STR_P(option), &errstr); |
245 | 0 | if (errstr) { |
246 | 0 | zend_error(E_WARNING, "%s", ZSTR_VAL(errstr)); |
247 | 0 | zend_string_release(errstr); |
248 | 0 | } |
249 | 0 | } else if (Z_TYPE_P(option) == IS_LONG) { |
250 | 0 | result = Z_LVAL_P(option); |
251 | 0 | } else { |
252 | 0 | zend_value_error("Invalid %s value in $options argument", zend_zval_value_name(option)); |
253 | 0 | return FAILURE; |
254 | 0 | } |
255 | 0 | SG(request_parse_body_context).options_cache[cache_offset].set = true; |
256 | 0 | SG(request_parse_body_context).options_cache[cache_offset].value = result; |
257 | 0 | } else { |
258 | 0 | SG(request_parse_body_context).options_cache[cache_offset].set = false; |
259 | 0 | } |
260 | | |
261 | 0 | return SUCCESS; |
262 | 0 | } |
263 | | |
264 | | static zend_result cache_request_parse_body_options(HashTable *options) |
265 | 0 | { |
266 | 0 | zend_string *key; |
267 | 0 | zval *value; |
268 | 0 | ZEND_HASH_FOREACH_STR_KEY_VAL(options, key, value) { |
269 | 0 | if (!key) { |
270 | 0 | zend_value_error("Invalid integer key in $options argument"); |
271 | 0 | return FAILURE; |
272 | 0 | } |
273 | 0 | if (ZSTR_LEN(key) == 0) { |
274 | 0 | zend_value_error("Invalid empty string key in $options argument"); |
275 | 0 | return FAILURE; |
276 | 0 | } |
277 | | |
278 | 0 | #define CHECK_OPTION(name) \ |
279 | 0 | if (zend_string_equals_literal_ci(key, #name)) { \ |
280 | 0 | if (cache_request_parse_body_option(options, value, REQUEST_PARSE_BODY_OPTION_ ## name) == FAILURE) { \ |
281 | 0 | return FAILURE; \ |
282 | 0 | } \ |
283 | 0 | continue; \ |
284 | 0 | } |
285 | | |
286 | 0 | switch (ZSTR_VAL(key)[0]) { |
287 | 0 | case 'm': |
288 | 0 | case 'M': |
289 | 0 | CHECK_OPTION(max_file_uploads); |
290 | 0 | CHECK_OPTION(max_input_vars); |
291 | 0 | CHECK_OPTION(max_multipart_body_parts); |
292 | 0 | break; |
293 | 0 | case 'p': |
294 | 0 | case 'P': |
295 | 0 | CHECK_OPTION(post_max_size); |
296 | 0 | break; |
297 | 0 | case 'u': |
298 | 0 | case 'U': |
299 | 0 | CHECK_OPTION(upload_max_filesize); |
300 | 0 | break; |
301 | 0 | } |
302 | | |
303 | 0 | zend_value_error("Invalid key \"%s\" in $options argument", ZSTR_VAL(key)); |
304 | 0 | return FAILURE; |
305 | 0 | } ZEND_HASH_FOREACH_END(); |
306 | | |
307 | 0 | #undef CACHE_OPTION |
308 | | |
309 | 0 | return SUCCESS; |
310 | 0 | } |
311 | | |
312 | | PHP_FUNCTION(request_parse_body) |
313 | 0 | { |
314 | 0 | HashTable *options = NULL; |
315 | |
|
316 | 0 | ZEND_PARSE_PARAMETERS_START(0, 1) |
317 | 0 | Z_PARAM_OPTIONAL |
318 | 0 | Z_PARAM_ARRAY_HT_OR_NULL(options) |
319 | 0 | ZEND_PARSE_PARAMETERS_END(); |
320 | | |
321 | 0 | SG(request_parse_body_context).throw_exceptions = true; |
322 | 0 | if (options) { |
323 | 0 | if (cache_request_parse_body_options(options) == FAILURE) { |
324 | 0 | goto exit; |
325 | 0 | } |
326 | 0 | } |
327 | | |
328 | 0 | if (!SG(request_info).content_type) { |
329 | 0 | zend_throw_error(zend_ce_request_parse_body_exception, "Request does not provide a content type"); |
330 | 0 | goto exit; |
331 | 0 | } |
332 | | |
333 | 0 | sapi_read_post_data(); |
334 | 0 | if (!SG(request_info).post_entry) { |
335 | 0 | zend_throw_error(zend_ce_request_parse_body_exception, "Content-Type \"%s\" is not supported", SG(request_info).content_type); |
336 | 0 | goto exit; |
337 | 0 | } |
338 | | |
339 | 0 | zval post, files, old_post, old_files; |
340 | 0 | zval *global_post = &PG(http_globals)[TRACK_VARS_POST]; |
341 | 0 | zval *global_files = &PG(http_globals)[TRACK_VARS_FILES]; |
342 | |
|
343 | 0 | ZVAL_COPY_VALUE(&old_post, global_post); |
344 | 0 | ZVAL_COPY_VALUE(&old_files, global_files); |
345 | 0 | array_init(global_post); |
346 | 0 | array_init(global_files); |
347 | 0 | sapi_handle_post(global_post); |
348 | 0 | ZVAL_COPY_VALUE(&post, global_post); |
349 | 0 | ZVAL_COPY_VALUE(&files, global_files); |
350 | 0 | ZVAL_COPY_VALUE(global_post, &old_post); |
351 | 0 | ZVAL_COPY_VALUE(global_files, &old_files); |
352 | |
|
353 | 0 | RETVAL_ARR(zend_new_pair(&post, &files)); |
354 | |
|
355 | 0 | exit: |
356 | 0 | SG(request_parse_body_context).throw_exceptions = false; |
357 | 0 | memset(&SG(request_parse_body_context).options_cache, 0, sizeof(SG(request_parse_body_context).options_cache)); |
358 | 0 | } |
359 | | |
360 | | PHP_FUNCTION(http_get_last_response_headers) |
361 | 0 | { |
362 | 0 | if (zend_parse_parameters_none() == FAILURE) { |
363 | 0 | RETURN_THROWS(); |
364 | 0 | } |
365 | | |
366 | 0 | if (!Z_ISUNDEF(BG(last_http_headers))) { |
367 | 0 | RETURN_COPY(&BG(last_http_headers)); |
368 | 0 | } else { |
369 | 0 | RETURN_NULL(); |
370 | 0 | } |
371 | 0 | } |
372 | | |
373 | | PHP_FUNCTION(http_clear_last_response_headers) |
374 | 0 | { |
375 | 0 | if (zend_parse_parameters_none() == FAILURE) { |
376 | 0 | RETURN_THROWS(); |
377 | 0 | } |
378 | | |
379 | 0 | zval_ptr_dtor(&BG(last_http_headers)); |
380 | 0 | ZVAL_UNDEF(&BG(last_http_headers)); |
381 | 0 | } |