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