xref: /PHP-8.4/main/php_variables.c (revision 87d59d7f)
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: Rasmus Lerdorf <rasmus@lerdorf.on.ca>                       |
14    |          Zeev Suraski <zeev@php.net>                                 |
15    +----------------------------------------------------------------------+
16  */
17 
18 #include <stdio.h>
19 #include "php.h"
20 #include "ext/standard/php_standard.h"
21 #include "ext/standard/credits.h"
22 #include "zend_smart_str.h"
23 #include "php_variables.h"
24 #include "php_globals.h"
25 #include "php_content_types.h"
26 #include "SAPI.h"
27 #include "zend_globals.h"
28 #include "zend_exceptions.h"
29 
30 /* for systems that need to override reading of environment variables */
31 static void _php_import_environment_variables(zval *array_ptr);
32 static void _php_load_environment_variables(zval *array_ptr);
33 PHPAPI void (*php_import_environment_variables)(zval *array_ptr) = _php_import_environment_variables;
34 PHPAPI void (*php_load_environment_variables)(zval *array_ptr) = _php_load_environment_variables;
35 
php_register_variable(const char * var,const char * strval,zval * track_vars_array)36 PHPAPI void php_register_variable(const char *var, const char *strval, zval *track_vars_array)
37 {
38 	php_register_variable_safe(var, strval, strlen(strval), track_vars_array);
39 }
40 
41 /* binary-safe version */
php_register_variable_safe(const char * var,const char * strval,size_t str_len,zval * track_vars_array)42 PHPAPI void php_register_variable_safe(const char *var, const char *strval, size_t str_len, zval *track_vars_array)
43 {
44 	zval new_entry;
45 	assert(strval != NULL);
46 
47 	ZVAL_STRINGL_FAST(&new_entry, strval, str_len);
48 
49 	php_register_variable_ex(var, &new_entry, track_vars_array);
50 }
51 
php_register_variable_quick(const char * name,size_t name_len,zval * val,HashTable * ht)52 static zend_always_inline void php_register_variable_quick(const char *name, size_t name_len, zval *val, HashTable *ht)
53 {
54 	zend_string *key = zend_string_init_interned(name, name_len, 0);
55 
56 	zend_hash_update_ind(ht, key, val);
57 	zend_string_release_ex(key, 0);
58 }
59 
php_register_known_variable(const char * var_name,size_t var_name_len,zval * value,zval * track_vars_array)60 PHPAPI void php_register_known_variable(const char *var_name, size_t var_name_len, zval *value, zval *track_vars_array)
61 {
62 	HashTable *symbol_table = NULL;
63 
64 	ZEND_ASSERT(var_name != NULL);
65 	ZEND_ASSERT(var_name_len != 0);
66 	ZEND_ASSERT(track_vars_array != NULL && Z_TYPE_P(track_vars_array) == IS_ARRAY);
67 
68 	symbol_table = Z_ARRVAL_P(track_vars_array);
69 
70 #if ZEND_DEBUG
71 	/* Verify the name is valid for a PHP variable */
72 	ZEND_ASSERT(!(var_name_len == strlen("GLOBALS") && !memcmp(var_name, "GLOBALS", strlen("GLOBALS"))));
73 	ZEND_ASSERT(!(var_name_len == strlen("this") && !memcmp(var_name, "this", strlen("this"))));
74 
75 	/* Assert that the variable name is not numeric */
76 	zend_ulong idx;
77 	ZEND_ASSERT(!ZEND_HANDLE_NUMERIC_STR(var_name, var_name_len, idx));
78 	/* ensure that we don't have null bytes, spaces, dots, or array bracket in the variable name (not binary safe) */
79 	const char *p = var_name;
80 	for (size_t l = 0; l < var_name_len; l++) {
81 		ZEND_ASSERT(*p != '\0' && *p != ' ' && *p != '.' && *p != '[');
82 		p++;
83 	}
84 
85 	/* Do not allow to register cookies this way */
86 	ZEND_ASSERT(Z_TYPE(PG(http_globals)[TRACK_VARS_COOKIE]) == IS_UNDEF ||
87 		Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE]) != symbol_table);
88 #endif
89 
90 	php_register_variable_quick(var_name, var_name_len, value, symbol_table);
91 }
92 
93 /* Discard variable if mangling made it start with __Host-, where pre-mangling it did not start with __Host-
94  * Discard variable if mangling made it start with __Secure-, where pre-mangling it did not start with __Secure- */
php_is_forbidden_variable_name(const char * mangled_name,size_t mangled_name_len,const char * pre_mangled_name)95 static bool php_is_forbidden_variable_name(const char *mangled_name, size_t mangled_name_len, const char *pre_mangled_name)
96 {
97 	if (mangled_name_len >= sizeof("__Host-")-1 && strncmp(mangled_name, "__Host-", sizeof("__Host-")-1) == 0 && strncmp(pre_mangled_name, "__Host-", sizeof("__Host-")-1) != 0) {
98 		return true;
99 	}
100 
101 	if (mangled_name_len >= sizeof("__Secure-")-1 && strncmp(mangled_name, "__Secure-", sizeof("__Secure-")-1) == 0 && strncmp(pre_mangled_name, "__Secure-", sizeof("__Secure-")-1) != 0) {
102 		return true;
103 	}
104 
105 	return false;
106 }
107 
php_register_variable_ex(const char * var_name,zval * val,zval * track_vars_array)108 PHPAPI void php_register_variable_ex(const char *var_name, zval *val, zval *track_vars_array)
109 {
110 	char *p = NULL;
111 	char *ip = NULL;		/* index pointer */
112 	char *index;
113 	char *var, *var_orig;
114 	size_t var_len, index_len;
115 	zval gpc_element, *gpc_element_p;
116 	bool is_array = 0;
117 	HashTable *symtable1 = NULL;
118 	ALLOCA_FLAG(use_heap)
119 
120 	assert(var_name != NULL);
121 
122 	if (track_vars_array && Z_TYPE_P(track_vars_array) == IS_ARRAY) {
123 		symtable1 = Z_ARRVAL_P(track_vars_array);
124 	}
125 
126 	if (!symtable1) {
127 		/* Nothing to do */
128 		zval_ptr_dtor_nogc(val);
129 		return;
130 	}
131 
132 
133 	/* ignore leading spaces in the variable name */
134 	while (*var_name==' ') {
135 		var_name++;
136 	}
137 
138 	/*
139 	 * Prepare variable name
140 	 */
141 	var_len = strlen(var_name);
142 	var = var_orig = do_alloca(var_len + 1, use_heap);
143 	memcpy(var_orig, var_name, var_len + 1);
144 
145 	/* ensure that we don't have spaces or dots in the variable name (not binary safe) */
146 	for (p = var; *p; p++) {
147 		if (*p == ' ' || *p == '.') {
148 			*p='_';
149 		} else if (*p == '[') {
150 			is_array = 1;
151 			ip = p;
152 			*p = 0;
153 			break;
154 		}
155 	}
156 	var_len = p - var;
157 
158 	if (var_len==0) { /* empty variable name, or variable name with a space in it */
159 		zval_ptr_dtor_nogc(val);
160 		free_alloca(var_orig, use_heap);
161 		return;
162 	}
163 
164 	if (var_len == sizeof("this")-1 && EG(current_execute_data)) {
165 		zend_execute_data *ex = EG(current_execute_data);
166 
167 		while (ex) {
168 			if (ex->func && ZEND_USER_CODE(ex->func->common.type)) {
169 				if ((ZEND_CALL_INFO(ex) & ZEND_CALL_HAS_SYMBOL_TABLE)
170 						&& ex->symbol_table == symtable1) {
171 					if (memcmp(var, "this", sizeof("this")-1) == 0) {
172 						zend_throw_error(NULL, "Cannot re-assign $this");
173 						zval_ptr_dtor_nogc(val);
174 						free_alloca(var_orig, use_heap);
175 						return;
176 					}
177 				}
178 				break;
179 			}
180 			ex = ex->prev_execute_data;
181 		}
182 	}
183 
184 	/* GLOBALS hijack attempt, reject parameter */
185 	if (symtable1 == &EG(symbol_table) &&
186 		var_len == sizeof("GLOBALS")-1 &&
187 		!memcmp(var, "GLOBALS", sizeof("GLOBALS")-1)) {
188 		zval_ptr_dtor_nogc(val);
189 		free_alloca(var_orig, use_heap);
190 		return;
191 	}
192 
193 	index = var;
194 	index_len = var_len;
195 
196 	if (is_array) {
197 		int nest_level = 0;
198 		while (1) {
199 			char *index_s;
200 			size_t new_idx_len = 0;
201 
202 			if(++nest_level > PG(max_input_nesting_level)) {
203 				HashTable *ht;
204 				/* too many levels of nesting */
205 
206 				if (track_vars_array) {
207 					ht = Z_ARRVAL_P(track_vars_array);
208 					zend_symtable_str_del(ht, var, var_len);
209 				}
210 
211 				zval_ptr_dtor_nogc(val);
212 
213 				/* do not output the error message to the screen,
214 				 this helps us to avoid "information disclosure" */
215 				if (!PG(display_errors)) {
216 					php_error_docref(NULL, E_WARNING, "Input variable nesting level exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_nesting_level in php.ini.", PG(max_input_nesting_level));
217 				}
218 				free_alloca(var_orig, use_heap);
219 				return;
220 			}
221 
222 			ip++;
223 			index_s = ip;
224 			if (isspace(*ip)) {
225 				ip++;
226 			}
227 			if (*ip==']') {
228 				index_s = NULL;
229 			} else {
230 				ip = strchr(ip, ']');
231 				if (!ip) {
232 					/* not an index; un-terminate the var name */
233 					*(index_s - 1) = '_';
234 					/* PHP variables cannot contain ' ', '.', '[' in their names, so we replace the characters with a '_' */
235 					for (p = index_s; *p; p++) {
236 						if (*p == ' ' || *p == '.' || *p == '[') {
237 							*p = '_';
238 						}
239 					}
240 
241 					index_len = 0;
242 					if (index) {
243 						index_len = strlen(index);
244 					}
245 					goto plain_var;
246 					return;
247 				}
248 				*ip = 0;
249 				new_idx_len = strlen(index_s);
250 			}
251 
252 			if (!index) {
253 				array_init(&gpc_element);
254 				if ((gpc_element_p = zend_hash_next_index_insert(symtable1, &gpc_element)) == NULL) {
255 					zend_array_destroy(Z_ARR(gpc_element));
256 					zval_ptr_dtor_nogc(val);
257 					free_alloca(var_orig, use_heap);
258 					return;
259 				}
260 			} else {
261 				if (php_is_forbidden_variable_name(index, index_len, var_name)) {
262 					zval_ptr_dtor_nogc(val);
263 					free_alloca(var_orig, use_heap);
264 					return;
265 				}
266 
267 				gpc_element_p = zend_symtable_str_find(symtable1, index, index_len);
268 				if (!gpc_element_p) {
269 					zval tmp;
270 					array_init(&tmp);
271 					gpc_element_p = zend_symtable_str_update_ind(symtable1, index, index_len, &tmp);
272 				} else {
273 					if (Z_TYPE_P(gpc_element_p) == IS_INDIRECT) {
274 						gpc_element_p = Z_INDIRECT_P(gpc_element_p);
275 					}
276 					if (Z_TYPE_P(gpc_element_p) != IS_ARRAY) {
277 						zval_ptr_dtor_nogc(gpc_element_p);
278 						array_init(gpc_element_p);
279 					} else {
280 						SEPARATE_ARRAY(gpc_element_p);
281 					}
282 				}
283 			}
284 			symtable1 = Z_ARRVAL_P(gpc_element_p);
285 			/* ip pointed to the '[' character, now obtain the key */
286 			index = index_s;
287 			index_len = new_idx_len;
288 
289 			ip++;
290 			if (*ip == '[') {
291 				is_array = 1;
292 				*ip = 0;
293 			} else {
294 				goto plain_var;
295 			}
296 		}
297 	} else {
298 plain_var:
299 		if (!index) {
300 			if (zend_hash_next_index_insert(symtable1, val) == NULL) {
301 				zval_ptr_dtor_nogc(val);
302 			}
303 		} else {
304 			if (php_is_forbidden_variable_name(index, index_len, var_name)) {
305 				zval_ptr_dtor_nogc(val);
306 				free_alloca(var_orig, use_heap);
307 				return;
308 			}
309 
310 			zend_ulong idx;
311 
312 			/*
313 			 * According to rfc2965, more specific paths are listed above the less specific ones.
314 			 * If we encounter a duplicate cookie name, we should skip it, since it is not possible
315 			 * to have the same (plain text) cookie name for the same path and we should not overwrite
316 			 * more specific cookies with the less specific ones.
317 			 */
318 			if (Z_TYPE(PG(http_globals)[TRACK_VARS_COOKIE]) != IS_UNDEF &&
319 				symtable1 == Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE]) &&
320 				zend_symtable_str_exists(symtable1, index, index_len)) {
321 				zval_ptr_dtor_nogc(val);
322 			} else if (ZEND_HANDLE_NUMERIC_STR(index, index_len, idx)) {
323 				zend_hash_index_update(symtable1, idx, val);
324 			} else {
325 				php_register_variable_quick(index, index_len, val, symtable1);
326 			}
327 		}
328 	}
329 	free_alloca(var_orig, use_heap);
330 }
331 
332 typedef struct post_var_data {
333 	smart_str str;
334 	char *ptr;
335 	char *end;
336 	uint64_t cnt;
337 
338 	/* Bytes in ptr that have already been scanned for '&' */
339 	size_t already_scanned;
340 } post_var_data_t;
341 
add_post_var(zval * arr,post_var_data_t * var,bool eof)342 static bool add_post_var(zval *arr, post_var_data_t *var, bool eof)
343 {
344 	char *start, *ksep, *vsep, *val;
345 	size_t klen, vlen;
346 	size_t new_vlen;
347 
348 	if (var->ptr >= var->end) {
349 		return 0;
350 	}
351 
352 	start = var->ptr + var->already_scanned;
353 	vsep = memchr(start, '&', var->end - start);
354 	if (!vsep) {
355 		if (!eof) {
356 			var->already_scanned = var->end - var->ptr;
357 			return 0;
358 		} else {
359 			vsep = var->end;
360 		}
361 	}
362 
363 	ksep = memchr(var->ptr, '=', vsep - var->ptr);
364 	if (ksep) {
365 		*ksep = '\0';
366 		/* "foo=bar&" or "foo=&" */
367 		klen = ksep - var->ptr;
368 		vlen = vsep - ++ksep;
369 	} else {
370 		ksep = "";
371 		/* "foo&" */
372 		klen = vsep - var->ptr;
373 		vlen = 0;
374 	}
375 
376 	php_url_decode(var->ptr, klen);
377 
378 	val = estrndup(ksep, vlen);
379 	if (vlen) {
380 		vlen = php_url_decode(val, vlen);
381 	}
382 
383 	if (sapi_module.input_filter(PARSE_POST, var->ptr, &val, vlen, &new_vlen)) {
384 		php_register_variable_safe(var->ptr, val, new_vlen, arr);
385 	}
386 	efree(val);
387 
388 	var->ptr = vsep + (vsep != var->end);
389 	var->already_scanned = 0;
390 	return 1;
391 }
392 
add_post_vars(zval * arr,post_var_data_t * vars,bool eof)393 static inline int add_post_vars(zval *arr, post_var_data_t *vars, bool eof)
394 {
395 	uint64_t max_vars = REQUEST_PARSE_BODY_OPTION_GET(max_input_vars, PG(max_input_vars));
396 
397 	vars->ptr = ZSTR_VAL(vars->str.s);
398 	vars->end = ZSTR_VAL(vars->str.s) + ZSTR_LEN(vars->str.s);
399 	while (add_post_var(arr, vars, eof)) {
400 		if (++vars->cnt > max_vars) {
401 			php_error_docref(NULL, E_WARNING,
402 					"Input variables exceeded %" PRIu64 ". "
403 					"To increase the limit change max_input_vars in php.ini.",
404 					max_vars);
405 			return FAILURE;
406 		}
407 	}
408 
409 	if (!eof && ZSTR_VAL(vars->str.s) != vars->ptr) {
410 		memmove(ZSTR_VAL(vars->str.s), vars->ptr, ZSTR_LEN(vars->str.s) = vars->end - vars->ptr);
411 	}
412 	return SUCCESS;
413 }
414 
415 #ifdef PHP_WIN32
416 #define SAPI_POST_HANDLER_BUFSIZ 16384
417 #else
418 # define SAPI_POST_HANDLER_BUFSIZ BUFSIZ
419 #endif
SAPI_POST_HANDLER_FUNC(php_std_post_handler)420 SAPI_API SAPI_POST_HANDLER_FUNC(php_std_post_handler)
421 {
422 	zval *arr = (zval *) arg;
423 	php_stream *s = SG(request_info).request_body;
424 	post_var_data_t post_data;
425 
426 	if (s && SUCCESS == php_stream_rewind(s)) {
427 		memset(&post_data, 0, sizeof(post_data));
428 
429 		while (!php_stream_eof(s)) {
430 			char buf[SAPI_POST_HANDLER_BUFSIZ] = {0};
431 			ssize_t len = php_stream_read(s, buf, SAPI_POST_HANDLER_BUFSIZ);
432 
433 			if (len > 0) {
434 				smart_str_appendl(&post_data.str, buf, len);
435 
436 				if (SUCCESS != add_post_vars(arr, &post_data, 0)) {
437 					smart_str_free(&post_data.str);
438 					return;
439 				}
440 			}
441 
442 			if (len != SAPI_POST_HANDLER_BUFSIZ){
443 				break;
444 			}
445 		}
446 
447 		if (post_data.str.s) {
448 			add_post_vars(arr, &post_data, 1);
449 			smart_str_free(&post_data.str);
450 		}
451 	}
452 }
453 #undef SAPI_POST_HANDLER_BUFSIZ
454 
SAPI_INPUT_FILTER_FUNC(php_default_input_filter)455 SAPI_API SAPI_INPUT_FILTER_FUNC(php_default_input_filter)
456 {
457 	/* TODO: check .ini setting here and apply user-defined input filter */
458 	if(new_val_len) *new_val_len = val_len;
459 	return 1;
460 }
461 
SAPI_TREAT_DATA_FUNC(php_default_treat_data)462 SAPI_API SAPI_TREAT_DATA_FUNC(php_default_treat_data)
463 {
464 	char *res = NULL, *var, *val, *separator = NULL;
465 	const char *c_var;
466 	zval array;
467 	int free_buffer = 0;
468 	char *strtok_buf = NULL;
469 	zend_long count = 0;
470 
471 	ZVAL_UNDEF(&array);
472 	switch (arg) {
473 		case PARSE_POST:
474 		case PARSE_GET:
475 		case PARSE_COOKIE:
476 			array_init(&array);
477 			switch (arg) {
478 				case PARSE_POST:
479 					zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_POST]);
480 					ZVAL_COPY_VALUE(&PG(http_globals)[TRACK_VARS_POST], &array);
481 					break;
482 				case PARSE_GET:
483 					zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_GET]);
484 					ZVAL_COPY_VALUE(&PG(http_globals)[TRACK_VARS_GET], &array);
485 					break;
486 				case PARSE_COOKIE:
487 					zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_COOKIE]);
488 					ZVAL_COPY_VALUE(&PG(http_globals)[TRACK_VARS_COOKIE], &array);
489 					break;
490 			}
491 			break;
492 		default:
493 			ZVAL_COPY_VALUE(&array, destArray);
494 			break;
495 	}
496 
497 	if (arg == PARSE_POST) {
498 		sapi_handle_post(&array);
499 		return;
500 	}
501 
502 	if (arg == PARSE_GET) {		/* GET data */
503 		c_var = SG(request_info).query_string;
504 		if (c_var && *c_var) {
505 			res = (char *) estrdup(c_var);
506 			free_buffer = 1;
507 		} else {
508 			free_buffer = 0;
509 		}
510 	} else if (arg == PARSE_COOKIE) {		/* Cookie data */
511 		c_var = SG(request_info).cookie_data;
512 		if (c_var && *c_var) {
513 			res = (char *) estrdup(c_var);
514 			free_buffer = 1;
515 		} else {
516 			free_buffer = 0;
517 		}
518 	} else if (arg == PARSE_STRING) {		/* String data */
519 		res = str;
520 		free_buffer = 1;
521 	}
522 
523 	if (!res) {
524 		return;
525 	}
526 
527 	switch (arg) {
528 		case PARSE_GET:
529 		case PARSE_STRING:
530 			separator = PG(arg_separator).input;
531 			break;
532 		case PARSE_COOKIE:
533 			separator = ";\0";
534 			break;
535 	}
536 
537 	var = php_strtok_r(res, separator, &strtok_buf);
538 
539 	while (var) {
540 		size_t val_len;
541 		size_t new_val_len;
542 
543 		val = strchr(var, '=');
544 
545 		if (arg == PARSE_COOKIE) {
546 			/* Remove leading spaces from cookie names, needed for multi-cookie header where ; can be followed by a space */
547 			while (isspace(*var)) {
548 				var++;
549 			}
550 			if (var == val || *var == '\0') {
551 				goto next_cookie;
552 			}
553 		}
554 
555 		zend_long max_input_vars = REQUEST_PARSE_BODY_OPTION_GET(max_input_vars, PG(max_input_vars));
556 		if (++count > max_input_vars) {
557 			php_error_docref(NULL, E_WARNING, "Input variables exceeded " ZEND_LONG_FMT ". To increase the limit change max_input_vars in php.ini.", max_input_vars);
558 			break;
559 		}
560 
561 		if (val) { /* have a value */
562 
563 			*val++ = '\0';
564 
565 			if (arg == PARSE_COOKIE) {
566 				val_len = php_raw_url_decode(val, strlen(val));
567 			} else {
568 				val_len = php_url_decode(val, strlen(val));
569 			}
570 		} else {
571 			val     = "";
572 			val_len =  0;
573 		}
574 
575 		val = estrndup(val, val_len);
576 		if (arg != PARSE_COOKIE) {
577 			php_url_decode(var, strlen(var));
578 		}
579 		if (sapi_module.input_filter(arg, var, &val, val_len, &new_val_len)) {
580 			php_register_variable_safe(var, val, new_val_len, &array);
581 		}
582 		efree(val);
583 next_cookie:
584 		var = php_strtok_r(NULL, separator, &strtok_buf);
585 	}
586 
587 	if (free_buffer) {
588 		efree(res);
589 	}
590 }
591 
valid_environment_name(const char * name,const char * end)592 static zend_always_inline int valid_environment_name(const char *name, const char *end)
593 {
594 	const char *s;
595 
596 	for (s = name; s < end; s++) {
597 		if (*s == ' ' || *s == '.' || *s == '[') {
598 			return 0;
599 		}
600 	}
601 	return 1;
602 }
603 
import_environment_variable(HashTable * ht,char * env)604 static zend_always_inline void import_environment_variable(HashTable *ht, char *env)
605 {
606 	char *p;
607 	size_t name_len, len;
608 	zval val;
609 	zend_ulong idx;
610 
611 	p = strchr(env, '=');
612 	if (!p
613 		|| p == env
614 		|| !valid_environment_name(env, p)) {
615 		/* malformed entry? */
616 		return;
617 	}
618 	name_len = p - env;
619 	p++;
620 	len = strlen(p);
621 	ZVAL_STRINGL_FAST(&val, p, len);
622 	if (ZEND_HANDLE_NUMERIC_STR(env, name_len, idx)) {
623 		zend_hash_index_update(ht, idx, &val);
624 	} else {
625 		php_register_variable_quick(env, name_len, &val, ht);
626 	}
627 }
628 
_php_import_environment_variables(zval * array_ptr)629 static void _php_import_environment_variables(zval *array_ptr)
630 {
631 	tsrm_env_lock();
632 
633 #ifndef PHP_WIN32
634 	for (char **env = environ; env != NULL && *env != NULL; env++) {
635 		import_environment_variable(Z_ARRVAL_P(array_ptr), *env);
636 	}
637 #else
638 	wchar_t *environmentw = GetEnvironmentStringsW();
639 	for (wchar_t *envw = environmentw; envw != NULL && *envw; envw += wcslen(envw) + 1) {
640 		char *env = php_win32_cp_w_to_any(envw);
641 		if (env != NULL) {
642 			import_environment_variable(Z_ARRVAL_P(array_ptr), env);
643 			free(env);
644 		}
645 	}
646 	FreeEnvironmentStringsW(environmentw);
647 #endif
648 
649 	tsrm_env_unlock();
650 }
651 
_php_load_environment_variables(zval * array_ptr)652 static void _php_load_environment_variables(zval *array_ptr)
653 {
654 	php_import_environment_variables(array_ptr);
655 }
656 
657 /* {{{ php_build_argv */
php_build_argv(const char * s,zval * track_vars_array)658 PHPAPI void php_build_argv(const char *s, zval *track_vars_array)
659 {
660 	zval arr, argc, tmp;
661 	int count = 0;
662 
663 	if (!(SG(request_info).argc || track_vars_array)) {
664 		return;
665 	}
666 
667 	array_init(&arr);
668 
669 	/* Prepare argv */
670 	if (SG(request_info).argc) { /* are we in cli sapi? */
671 		int i;
672 		for (i = 0; i < SG(request_info).argc; i++) {
673 			ZVAL_STRING(&tmp, SG(request_info).argv[i]);
674 			if (zend_hash_next_index_insert(Z_ARRVAL(arr), &tmp) == NULL) {
675 				zend_string_efree(Z_STR(tmp));
676 			}
677 		}
678 	} else 	if (s && *s) {
679 		while (1) {
680 			const char *space = strchr(s, '+');
681 			/* auto-type */
682 			ZVAL_STRINGL(&tmp, s, space ? space - s : strlen(s));
683 			count++;
684 			if (zend_hash_next_index_insert(Z_ARRVAL(arr), &tmp) == NULL) {
685 				zend_string_efree(Z_STR(tmp));
686 			}
687 			if (!space) {
688 				break;
689 			}
690 			s = space + 1;
691 		}
692 	}
693 
694 	/* prepare argc */
695 	if (SG(request_info).argc) {
696 		ZVAL_LONG(&argc, SG(request_info).argc);
697 	} else {
698 		ZVAL_LONG(&argc, count);
699 	}
700 
701 	if (SG(request_info).argc) {
702 		Z_ADDREF(arr);
703 		zend_hash_update(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGV), &arr);
704 		zend_hash_update(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGC), &argc);
705 	}
706 	if (track_vars_array && Z_TYPE_P(track_vars_array) == IS_ARRAY) {
707 		Z_ADDREF(arr);
708 		zend_hash_update(Z_ARRVAL_P(track_vars_array), ZSTR_KNOWN(ZEND_STR_ARGV), &arr);
709 		zend_hash_update(Z_ARRVAL_P(track_vars_array), ZSTR_KNOWN(ZEND_STR_ARGC), &argc);
710 	}
711 	zval_ptr_dtor_nogc(&arr);
712 }
713 /* }}} */
714 
715 /* {{{ php_register_server_variables */
php_register_server_variables(void)716 static inline void php_register_server_variables(void)
717 {
718 	zval tmp;
719 	zval *arr = &PG(http_globals)[TRACK_VARS_SERVER];
720 	HashTable *ht;
721 
722 	zval_ptr_dtor_nogc(arr);
723 	array_init(arr);
724 
725 	/* Server variables */
726 	if (sapi_module.register_server_variables) {
727 		sapi_module.register_server_variables(arr);
728 	}
729 	ht = Z_ARRVAL_P(arr);
730 
731 	/* PHP Authentication support */
732 	if (SG(request_info).auth_user) {
733 		ZVAL_STRING(&tmp, SG(request_info).auth_user);
734 		php_register_variable_quick("PHP_AUTH_USER", sizeof("PHP_AUTH_USER")-1, &tmp, ht);
735 	}
736 	if (SG(request_info).auth_password) {
737 		ZVAL_STRING(&tmp, SG(request_info).auth_password);
738 		php_register_variable_quick("PHP_AUTH_PW", sizeof("PHP_AUTH_PW")-1, &tmp, ht);
739 	}
740 	if (SG(request_info).auth_digest) {
741 		ZVAL_STRING(&tmp, SG(request_info).auth_digest);
742 		php_register_variable_quick("PHP_AUTH_DIGEST", sizeof("PHP_AUTH_DIGEST")-1, &tmp, ht);
743 	}
744 
745 	/* store request init time */
746 	ZVAL_DOUBLE(&tmp, sapi_get_request_time());
747 	php_register_variable_quick("REQUEST_TIME_FLOAT", sizeof("REQUEST_TIME_FLOAT")-1, &tmp, ht);
748 	ZVAL_LONG(&tmp, zend_dval_to_lval(Z_DVAL(tmp)));
749 	php_register_variable_quick("REQUEST_TIME", sizeof("REQUEST_TIME")-1, &tmp, ht);
750 }
751 /* }}} */
752 
753 /* {{{ php_autoglobal_merge */
php_autoglobal_merge(HashTable * dest,HashTable * src)754 static void php_autoglobal_merge(HashTable *dest, HashTable *src)
755 {
756 	zval *src_entry, *dest_entry;
757 	zend_string *string_key;
758 	zend_ulong num_key;
759 	int globals_check = (dest == (&EG(symbol_table)));
760 
761 	ZEND_HASH_FOREACH_KEY_VAL(src, num_key, string_key, src_entry) {
762 		if (Z_TYPE_P(src_entry) != IS_ARRAY
763 			|| (string_key && (dest_entry = zend_hash_find(dest, string_key)) == NULL)
764 			|| (string_key == NULL && (dest_entry = zend_hash_index_find(dest, num_key)) == NULL)
765 			|| Z_TYPE_P(dest_entry) != IS_ARRAY) {
766 			Z_TRY_ADDREF_P(src_entry);
767 			if (string_key) {
768 				if (!globals_check || !zend_string_equals_literal(string_key, "GLOBALS")) {
769 					zend_hash_update(dest, string_key, src_entry);
770 				} else {
771 					Z_TRY_DELREF_P(src_entry);
772 				}
773 			} else {
774 				zend_hash_index_update(dest, num_key, src_entry);
775 			}
776 		} else {
777 			SEPARATE_ARRAY(dest_entry);
778 			php_autoglobal_merge(Z_ARRVAL_P(dest_entry), Z_ARRVAL_P(src_entry));
779 		}
780 	} ZEND_HASH_FOREACH_END();
781 }
782 /* }}} */
783 
784 /* {{{ php_hash_environment */
php_hash_environment(void)785 PHPAPI int php_hash_environment(void)
786 {
787 	memset(PG(http_globals), 0, sizeof(PG(http_globals)));
788 	zend_activate_auto_globals();
789 	if (PG(register_argc_argv)) {
790 		php_build_argv(SG(request_info).query_string, &PG(http_globals)[TRACK_VARS_SERVER]);
791 	}
792 	return SUCCESS;
793 }
794 /* }}} */
795 
php_auto_globals_create_get(zend_string * name)796 static bool php_auto_globals_create_get(zend_string *name)
797 {
798 	if (PG(variables_order) && (strchr(PG(variables_order),'G') || strchr(PG(variables_order),'g'))) {
799 		sapi_module.treat_data(PARSE_GET, NULL, NULL);
800 	} else {
801 		zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_GET]);
802 		array_init(&PG(http_globals)[TRACK_VARS_GET]);
803 	}
804 
805 	zend_hash_update(&EG(symbol_table), name, &PG(http_globals)[TRACK_VARS_GET]);
806 	Z_ADDREF(PG(http_globals)[TRACK_VARS_GET]);
807 
808 	return 0; /* don't rearm */
809 }
810 
php_auto_globals_create_post(zend_string * name)811 static bool php_auto_globals_create_post(zend_string *name)
812 {
813 	if (PG(variables_order) &&
814 			(strchr(PG(variables_order),'P') || strchr(PG(variables_order),'p')) &&
815 		!SG(headers_sent) &&
816 		SG(request_info).request_method &&
817 		!strcasecmp(SG(request_info).request_method, "POST")) {
818 		sapi_module.treat_data(PARSE_POST, NULL, NULL);
819 	} else {
820 		zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_POST]);
821 		array_init(&PG(http_globals)[TRACK_VARS_POST]);
822 	}
823 
824 	zend_hash_update(&EG(symbol_table), name, &PG(http_globals)[TRACK_VARS_POST]);
825 	Z_ADDREF(PG(http_globals)[TRACK_VARS_POST]);
826 
827 	return 0; /* don't rearm */
828 }
829 
php_auto_globals_create_cookie(zend_string * name)830 static bool php_auto_globals_create_cookie(zend_string *name)
831 {
832 	if (PG(variables_order) && (strchr(PG(variables_order),'C') || strchr(PG(variables_order),'c'))) {
833 		sapi_module.treat_data(PARSE_COOKIE, NULL, NULL);
834 	} else {
835 		zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_COOKIE]);
836 		array_init(&PG(http_globals)[TRACK_VARS_COOKIE]);
837 	}
838 
839 	zend_hash_update(&EG(symbol_table), name, &PG(http_globals)[TRACK_VARS_COOKIE]);
840 	Z_ADDREF(PG(http_globals)[TRACK_VARS_COOKIE]);
841 
842 	return 0; /* don't rearm */
843 }
844 
php_auto_globals_create_files(zend_string * name)845 static bool php_auto_globals_create_files(zend_string *name)
846 {
847 	if (Z_TYPE(PG(http_globals)[TRACK_VARS_FILES]) == IS_UNDEF) {
848 		array_init(&PG(http_globals)[TRACK_VARS_FILES]);
849 	}
850 
851 	zend_hash_update(&EG(symbol_table), name, &PG(http_globals)[TRACK_VARS_FILES]);
852 	Z_ADDREF(PG(http_globals)[TRACK_VARS_FILES]);
853 
854 	return 0; /* don't rearm */
855 }
856 
857 /* Ugly hack to fix HTTP_PROXY issue, see bug #72573 */
check_http_proxy(HashTable * var_table)858 static void check_http_proxy(HashTable *var_table)
859 {
860 	if (zend_hash_str_exists(var_table, "HTTP_PROXY", sizeof("HTTP_PROXY")-1)) {
861 		char *local_proxy = getenv("HTTP_PROXY");
862 
863 		if (!local_proxy) {
864 			zend_hash_str_del(var_table, "HTTP_PROXY", sizeof("HTTP_PROXY")-1);
865 		} else {
866 			zval local_zval;
867 			ZVAL_STRING(&local_zval, local_proxy);
868 			zend_hash_str_update(var_table, "HTTP_PROXY", sizeof("HTTP_PROXY")-1, &local_zval);
869 		}
870 	}
871 }
872 
php_auto_globals_create_server(zend_string * name)873 static bool php_auto_globals_create_server(zend_string *name)
874 {
875 	if (PG(variables_order) && (strchr(PG(variables_order),'S') || strchr(PG(variables_order),'s'))) {
876 		php_register_server_variables();
877 
878 		if (PG(register_argc_argv)) {
879 			if (SG(request_info).argc) {
880 				zval *argc, *argv;
881 
882 				if ((argc = zend_hash_find_ex_ind(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGC), 1)) != NULL &&
883 					(argv = zend_hash_find_ex_ind(&EG(symbol_table), ZSTR_KNOWN(ZEND_STR_ARGV), 1)) != NULL) {
884 					Z_ADDREF_P(argv);
885 					zend_hash_update(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), ZSTR_KNOWN(ZEND_STR_ARGV), argv);
886 					zend_hash_update(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]), ZSTR_KNOWN(ZEND_STR_ARGC), argc);
887 				}
888 			} else {
889 				php_build_argv(SG(request_info).query_string, &PG(http_globals)[TRACK_VARS_SERVER]);
890 			}
891 		}
892 
893 	} else {
894 		zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_SERVER]);
895 		array_init(&PG(http_globals)[TRACK_VARS_SERVER]);
896 		zend_hash_real_init_mixed(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]));
897 	}
898 
899 	check_http_proxy(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]));
900 	zend_hash_update(&EG(symbol_table), name, &PG(http_globals)[TRACK_VARS_SERVER]);
901 	Z_ADDREF(PG(http_globals)[TRACK_VARS_SERVER]);
902 
903 	/* TODO: TRACK_VARS_SERVER is modified in a number of places (e.g. phar) past this point,
904 	 * where rc>1 due to the $_SERVER global. Ideally this shouldn't happen, but for now we
905 	 * ignore this issue, as it would probably require larger changes. */
906 	HT_ALLOW_COW_VIOLATION(Z_ARRVAL(PG(http_globals)[TRACK_VARS_SERVER]));
907 
908 	return 0; /* don't rearm */
909 }
910 
php_auto_globals_create_env(zend_string * name)911 static bool php_auto_globals_create_env(zend_string *name)
912 {
913 	zval_ptr_dtor_nogc(&PG(http_globals)[TRACK_VARS_ENV]);
914 	array_init(&PG(http_globals)[TRACK_VARS_ENV]);
915 
916 	if (PG(variables_order) && (strchr(PG(variables_order),'E') || strchr(PG(variables_order),'e'))) {
917 		php_import_environment_variables(&PG(http_globals)[TRACK_VARS_ENV]);
918 	}
919 
920 	check_http_proxy(Z_ARRVAL(PG(http_globals)[TRACK_VARS_ENV]));
921 	zend_hash_update(&EG(symbol_table), name, &PG(http_globals)[TRACK_VARS_ENV]);
922 	Z_ADDREF(PG(http_globals)[TRACK_VARS_ENV]);
923 
924 	return 0; /* don't rearm */
925 }
926 
php_auto_globals_create_request(zend_string * name)927 static bool php_auto_globals_create_request(zend_string *name)
928 {
929 	zval form_variables;
930 	unsigned char _gpc_flags[3] = {0, 0, 0};
931 	char *p;
932 
933 	array_init(&form_variables);
934 
935 	if (PG(request_order) != NULL) {
936 		p = PG(request_order);
937 	} else {
938 		p = PG(variables_order);
939 	}
940 
941 	for (; p && *p; p++) {
942 		switch (*p) {
943 			case 'g':
944 			case 'G':
945 				if (!_gpc_flags[0]) {
946 					php_autoglobal_merge(Z_ARRVAL(form_variables), Z_ARRVAL(PG(http_globals)[TRACK_VARS_GET]));
947 					_gpc_flags[0] = 1;
948 				}
949 				break;
950 			case 'p':
951 			case 'P':
952 				if (!_gpc_flags[1]) {
953 					php_autoglobal_merge(Z_ARRVAL(form_variables), Z_ARRVAL(PG(http_globals)[TRACK_VARS_POST]));
954 					_gpc_flags[1] = 1;
955 				}
956 				break;
957 			case 'c':
958 			case 'C':
959 				if (!_gpc_flags[2]) {
960 					php_autoglobal_merge(Z_ARRVAL(form_variables), Z_ARRVAL(PG(http_globals)[TRACK_VARS_COOKIE]));
961 					_gpc_flags[2] = 1;
962 				}
963 				break;
964 		}
965 	}
966 
967 	zend_hash_update(&EG(symbol_table), name, &form_variables);
968 	return 0;
969 }
970 
php_startup_auto_globals(void)971 void php_startup_auto_globals(void)
972 {
973 	zend_register_auto_global(zend_string_init_interned("_GET", sizeof("_GET")-1, 1), 0, php_auto_globals_create_get);
974 	zend_register_auto_global(zend_string_init_interned("_POST", sizeof("_POST")-1, 1), 0, php_auto_globals_create_post);
975 	zend_register_auto_global(zend_string_init_interned("_COOKIE", sizeof("_COOKIE")-1, 1), 0, php_auto_globals_create_cookie);
976 	zend_register_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_SERVER), PG(auto_globals_jit), php_auto_globals_create_server);
977 	zend_register_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_ENV), PG(auto_globals_jit), php_auto_globals_create_env);
978 	zend_register_auto_global(ZSTR_KNOWN(ZEND_STR_AUTOGLOBAL_REQUEST), PG(auto_globals_jit), php_auto_globals_create_request);
979 	zend_register_auto_global(zend_string_init_interned("_FILES", sizeof("_FILES")-1, 1), 0, php_auto_globals_create_files);
980 }
981