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