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