1 #include "common.h"
2 
3 extern struct uwsgi_server uwsgi;
4 
5 static sapi_module_struct uwsgi_sapi_module;
6 
7 static int uwsgi_php_init(void);
8 
9 typedef size_t php_strlen_size;
10 typedef zend_long php_long_size;
11 
12 struct uwsgi_php {
13 	struct uwsgi_string_list *allowed_docroot;
14 	struct uwsgi_string_list *allowed_ext;
15 	struct uwsgi_string_list *allowed_scripts;
16 	struct uwsgi_string_list *index;
17 	struct uwsgi_string_list *set;
18 	struct uwsgi_string_list *append_config;
19 #ifdef UWSGI_PCRE
20 	struct uwsgi_regexp_list *app_bypass;
21 #endif
22 	struct uwsgi_string_list *vars;
23 	struct uwsgi_string_list *constants;
24 	char *docroot;
25 	size_t docroot_len;
26 	char *app;
27 	char *app_qs;
28 	char *fallback;
29 	char *fallback2;
30 	char *fallback_qs;
31 	size_t ini_size;
32 	int dump_config;
33 	char *server_software;
34 	size_t server_software_len;
35 
36 	struct uwsgi_string_list *exec_before;
37 	struct uwsgi_string_list *exec_after;
38 
39 	char *sapi_name;
40 	HashTable user_config_cache;
41 } uphp;
42 
uwsgi_opt_php_ini(char * opt,char * value,void * foobar)43 void uwsgi_opt_php_ini(char *opt, char *value, void *foobar) {
44 	uwsgi_sapi_module.php_ini_path_override = uwsgi_str(value);
45         uwsgi_sapi_module.php_ini_ignore = 1;
46 }
47 
48 struct uwsgi_option uwsgi_php_options[] = {
49 
50         {"php-ini", required_argument, 0, "set php.ini path", uwsgi_opt_php_ini, NULL, 0},
51         {"php-config", required_argument, 0, "set php.ini path", uwsgi_opt_php_ini, NULL, 0},
52         {"php-ini-append", required_argument, 0, "set php.ini path (append mode)", uwsgi_opt_add_string_list, &uphp.append_config, 0},
53         {"php-config-append", required_argument, 0, "set php.ini path (append mode)", uwsgi_opt_add_string_list, &uphp.append_config, 0},
54         {"php-set", required_argument, 0, "set a php config directive", uwsgi_opt_add_string_list, &uphp.set, 0},
55         {"php-index", required_argument, 0, "list the php index files", uwsgi_opt_add_string_list, &uphp.index, 0},
56         {"php-docroot", required_argument, 0, "force php DOCUMENT_ROOT", uwsgi_opt_set_str, &uphp.docroot, 0},
57         {"php-allowed-docroot", required_argument, 0, "list the allowed document roots", uwsgi_opt_add_string_list, &uphp.allowed_docroot, 0},
58         {"php-allowed-ext", required_argument, 0, "list the allowed php file extensions", uwsgi_opt_add_string_list, &uphp.allowed_ext, 0},
59         {"php-allowed-script", required_argument, 0, "list the allowed php scripts (require absolute path)", uwsgi_opt_add_string_list, &uphp.allowed_scripts, 0},
60         {"php-server-software", required_argument, 0, "force php SERVER_SOFTWARE", uwsgi_opt_set_str, &uphp.server_software, 0},
61         {"php-app", required_argument, 0, "force the php file to run at each request", uwsgi_opt_set_str, &uphp.app, 0},
62         {"php-app-qs", required_argument, 0, "when in app mode force QUERY_STRING to the specified value + REQUEST_URI", uwsgi_opt_set_str, &uphp.app_qs, 0},
63         {"php-fallback", required_argument, 0, "run the specified php script when the requested one does not exist", uwsgi_opt_set_str, &uphp.fallback, 0},
64         {"php-fallback2", required_argument, 0, "run the specified php script relative to the document root when the requested one does not exist", uwsgi_opt_set_str, &uphp.fallback2, 0},
65         {"php-fallback-qs", required_argument, 0, "php-fallback with QUERY_STRING set", uwsgi_opt_set_str, &uphp.fallback_qs, 0},
66 #ifdef UWSGI_PCRE
67         {"php-app-bypass", required_argument, 0, "if the regexp matches the uri the --php-app is bypassed", uwsgi_opt_add_regexp_list, &uphp.app_bypass, 0},
68 #endif
69         {"php-var", required_argument, 0, "add/overwrite a CGI variable at each request", uwsgi_opt_add_string_list, &uphp.vars, 0},
70         {"php-constant", required_argument, 0, "define a php constant for each request", uwsgi_opt_add_string_list, &uphp.constants, 0},
71         {"php-dump-config", no_argument, 0, "dump php config (if modified via --php-set or append options)", uwsgi_opt_true, &uphp.dump_config, 0},
72         {"php-exec-before", required_argument, 0, "run specified php code before the requested script", uwsgi_opt_add_string_list, &uphp.exec_before, 0},
73         {"php-exec-begin", required_argument, 0, "run specified php code before the requested script", uwsgi_opt_add_string_list, &uphp.exec_before, 0},
74         {"php-exec-after", required_argument, 0, "run specified php code after the requested script", uwsgi_opt_add_string_list, &uphp.exec_after, 0},
75         {"php-exec-end", required_argument, 0, "run specified php code after the requested script", uwsgi_opt_add_string_list, &uphp.exec_after, 0},
76         {"php-sapi-name", required_argument, 0, "hack the sapi name (required for enabling zend opcode cache)", uwsgi_opt_set_str, &uphp.sapi_name, 0},
77 	UWSGI_END_OF_OPTIONS
78 };
79 
80 
sapi_uwsgi_ub_write(const char * str,size_t str_length)81 static size_t sapi_uwsgi_ub_write(const char *str, size_t str_length)
82 {
83 	struct wsgi_request *wsgi_req = (struct wsgi_request *) SG(server_context);
84 
85 	uwsgi_response_write_body_do(wsgi_req, (char *) str, str_length);
86 	if (wsgi_req->write_errors > uwsgi.write_errors_tolerance) {
87 		php_handle_aborted_connection();
88 		return -1;
89 	}
90 	return str_length;
91 }
92 
sapi_uwsgi_send_headers(sapi_headers_struct * sapi_headers)93 static int sapi_uwsgi_send_headers(sapi_headers_struct *sapi_headers)
94 {
95 	sapi_header_struct *h;
96 	zend_llist_position pos;
97 
98 	if (SG(request_info).no_headers == 1) {
99                 return SAPI_HEADER_SENT_SUCCESSFULLY;
100         }
101 
102 	struct wsgi_request *wsgi_req = (struct wsgi_request *) SG(server_context);
103 
104 	if (!SG(sapi_headers).http_status_line) {
105 		char status[4];
106 		int hrc = SG(sapi_headers).http_response_code;
107 		if (!hrc) hrc = 200;
108 		uwsgi_num2str2n(hrc, status, 4);
109 		if (uwsgi_response_prepare_headers(wsgi_req, status, 3))
110 			return SAPI_HEADER_SEND_FAILED;
111 	}
112 	else {
113 		char *sl = SG(sapi_headers).http_status_line;
114 		if (uwsgi_response_prepare_headers(wsgi_req, sl + 9 , strlen(sl + 9)))
115 			return SAPI_HEADER_SEND_FAILED;
116 	}
117 
118 	h = zend_llist_get_first_ex(&sapi_headers->headers, &pos);
119 	while (h) {
120 		uwsgi_response_add_header(wsgi_req, NULL, 0, h->header, h->header_len);
121 		h = zend_llist_get_next_ex(&sapi_headers->headers, &pos);
122 	}
123 
124 	return SAPI_HEADER_SENT_SUCCESSFULLY;
125 }
126 
sapi_uwsgi_read_post(char * buffer,size_t count_bytes)127 static size_t sapi_uwsgi_read_post(char *buffer, size_t count_bytes)
128 {
129 	uint read_bytes = 0;
130 
131 	struct wsgi_request *wsgi_req = (struct wsgi_request *) SG(server_context);
132 
133         count_bytes = MIN(count_bytes, wsgi_req->post_cl - SG(read_post_bytes));
134 
135         while (read_bytes < count_bytes) {
136 		ssize_t rlen = 0;
137 		char *buf = uwsgi_request_body_read(wsgi_req, count_bytes - read_bytes, &rlen);
138 		if (buf == uwsgi.empty) break;
139 		if (buf) {
140 			memcpy(buffer, buf, rlen);
141 			read_bytes += rlen;
142 			continue;
143 		}
144 		break;
145         }
146 
147         return read_bytes;
148 }
149 
150 
sapi_uwsgi_read_cookies()151 static char *sapi_uwsgi_read_cookies()
152 {
153 	uint16_t len = 0;
154 	struct wsgi_request *wsgi_req = (struct wsgi_request *) SG(server_context);
155 
156 	char *cookie = uwsgi_get_var(wsgi_req, (char *)"HTTP_COOKIE", 11, &len);
157 	if (cookie) {
158 		return estrndup(cookie, len);
159 	}
160 
161 	return NULL;
162 }
163 
sapi_uwsgi_register_variables(zval * track_vars_array)164 static void sapi_uwsgi_register_variables(zval *track_vars_array)
165 {
166 	int i;
167 	struct wsgi_request *wsgi_req = (struct wsgi_request *) SG(server_context);
168 	php_import_environment_variables(track_vars_array);
169 
170 	if (uphp.server_software) {
171 		if (!uphp.server_software_len) uphp.server_software_len = strlen(uphp.server_software);
172 		php_register_variable_safe("SERVER_SOFTWARE", uphp.server_software, uphp.server_software_len, track_vars_array);
173 	}
174 	else {
175 		php_register_variable_safe("SERVER_SOFTWARE", "uWSGI", 5, track_vars_array);
176 	}
177 
178 	for (i = 0; i < wsgi_req->var_cnt; i += 2) {
179 		php_register_variable_safe( estrndup(wsgi_req->hvec[i].iov_base, wsgi_req->hvec[i].iov_len),
180 			wsgi_req->hvec[i + 1].iov_base, wsgi_req->hvec[i + 1].iov_len,
181 			track_vars_array);
182         }
183 
184 	php_register_variable_safe("PATH_INFO", wsgi_req->path_info, wsgi_req->path_info_len, track_vars_array);
185 	if (wsgi_req->query_string_len > 0) {
186 		php_register_variable_safe("QUERY_STRING", wsgi_req->query_string, wsgi_req->query_string_len, track_vars_array);
187 	}
188 
189 	php_register_variable_safe("SCRIPT_NAME", wsgi_req->script_name, wsgi_req->script_name_len, track_vars_array);
190 	php_register_variable_safe("SCRIPT_FILENAME", wsgi_req->file, wsgi_req->file_len, track_vars_array);
191 
192 	php_register_variable_safe("DOCUMENT_ROOT", wsgi_req->document_root, wsgi_req->document_root_len, track_vars_array);
193 
194 	if (wsgi_req->path_info_len) {
195 		char *path_translated = ecalloc(1, wsgi_req->file_len + wsgi_req->path_info_len + 1);
196 
197 		memcpy(path_translated, wsgi_req->file, wsgi_req->file_len);
198 		memcpy(path_translated + wsgi_req->file_len, wsgi_req->path_info, wsgi_req->path_info_len);
199 		php_register_variable_safe("PATH_TRANSLATED", path_translated, wsgi_req->file_len + wsgi_req->path_info_len , track_vars_array);
200 	}
201 	else {
202 		php_register_variable_safe("PATH_TRANSLATED", "", 0, track_vars_array);
203 	}
204 
205 	php_register_variable_safe("PHP_SELF", wsgi_req->script_name, wsgi_req->script_name_len, track_vars_array);
206 
207 	struct uwsgi_string_list *usl = uphp.vars;
208 	while(usl) {
209 		char *equal = strchr(usl->value, '=');
210 		if (equal) {
211 			php_register_variable_safe( estrndup(usl->value, equal-usl->value),
212 				equal+1, strlen(equal+1), track_vars_array);
213 		}
214 		usl = usl->next;
215 	}
216 }
217 
218 static sapi_module_struct uwsgi_sapi_module;
219 
220 
221 
222 
uwsgi_php_append_config(char * filename)223 void uwsgi_php_append_config(char *filename) {
224 	size_t file_size = 0;
225         char *file_content = uwsgi_open_and_read(filename, &file_size, 1, NULL);
226 	uwsgi_sapi_module.ini_entries = realloc(uwsgi_sapi_module.ini_entries, uphp.ini_size + file_size);
227 	memcpy(uwsgi_sapi_module.ini_entries + uphp.ini_size, file_content, file_size);
228 	uphp.ini_size += file_size-1;
229 	free(file_content);
230 }
231 
uwsgi_php_set(char * opt)232 void uwsgi_php_set(char *opt) {
233 
234 	uwsgi_sapi_module.ini_entries = realloc(uwsgi_sapi_module.ini_entries, uphp.ini_size + strlen(opt)+2);
235 	memcpy(uwsgi_sapi_module.ini_entries + uphp.ini_size, opt, strlen(opt));
236 
237 	uphp.ini_size += strlen(opt)+1;
238 	uwsgi_sapi_module.ini_entries[uphp.ini_size-1] = '\n';
239 	uwsgi_sapi_module.ini_entries[uphp.ini_size] = 0;
240 }
241 
242 extern ps_module ps_mod_uwsgi;
PHP_MINIT_FUNCTION(uwsgi_php_minit)243 PHP_MINIT_FUNCTION(uwsgi_php_minit) {
244 	php_session_register_module(&ps_mod_uwsgi);
245 	struct uwsgi_string_list *usl = uphp.constants;
246 	while(usl) {
247 		char *equal = strchr(usl->value, '=');
248 		if (equal) {
249 			size_t name_len = equal - usl->value;
250 			char *name = usl->value;
251 			char *strval = equal + 1;
252 			equal = NULL;
253 			zend_register_string_constant(name, name_len, strval, CONST_CS | CONST_PERSISTENT, module_number);
254 		}
255 		usl = usl->next;
256 	}
257 	return SUCCESS;
258 }
259 
PHP_FUNCTION(uwsgi_version)260 PHP_FUNCTION(uwsgi_version) {
261 	RETURN_STRING(UWSGI_VERSION);
262 }
263 
PHP_FUNCTION(uwsgi_worker_id)264 PHP_FUNCTION(uwsgi_worker_id) {
265 	RETURN_LONG(uwsgi.mywid);
266 }
267 
PHP_FUNCTION(uwsgi_masterpid)268 PHP_FUNCTION(uwsgi_masterpid) {
269 	if (uwsgi.master_process) {
270 		RETURN_LONG(uwsgi.workers[0].pid);
271 	}
272 	RETURN_LONG(0);
273 }
274 
PHP_FUNCTION(uwsgi_cache_exists)275 PHP_FUNCTION(uwsgi_cache_exists) {
276 
277         char *key = NULL;
278         int keylen = 0;
279         char *cache = NULL;
280         int cachelen = 0;
281 
282         if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &key, &keylen, &cache, &cachelen) == FAILURE) {
283                 RETURN_NULL();
284         }
285 
286         if (uwsgi_cache_magic_exists(key, keylen, cache)) {
287                 RETURN_TRUE;
288         }
289 
290         RETURN_NULL();
291 }
292 
PHP_FUNCTION(uwsgi_cache_clear)293 PHP_FUNCTION(uwsgi_cache_clear) {
294 
295         char *cache = NULL;
296         int cachelen = 0;
297 
298         if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &cache, &cachelen) == FAILURE) {
299                 RETURN_NULL();
300         }
301 
302         if (!uwsgi_cache_magic_clear(cache)) {
303                 RETURN_TRUE;
304         }
305 
306         RETURN_NULL();
307 }
308 
309 
PHP_FUNCTION(uwsgi_cache_del)310 PHP_FUNCTION(uwsgi_cache_del) {
311 
312 	char *key = NULL;
313         int keylen = 0;
314 	char *cache = NULL;
315 	int cachelen = 0;
316 
317         if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &key, &keylen, &cache, &cachelen) == FAILURE) {
318                 RETURN_NULL();
319         }
320 
321         if (!uwsgi_cache_magic_del(key, keylen, cache)) {
322 		RETURN_TRUE;
323         }
324 
325 	RETURN_NULL();
326 }
327 
PHP_FUNCTION(uwsgi_cache_get)328 PHP_FUNCTION(uwsgi_cache_get) {
329 
330 	char *key = NULL;
331 	int keylen = 0;
332 	char *cache = NULL;
333 	int cachelen = 0;
334 	uint64_t valsize;
335 
336 	if (!uwsgi.caches)
337 		RETURN_NULL();
338 
339 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|s", &key, &keylen, &cache, &cachelen) == FAILURE) {
340                 RETURN_NULL();
341         }
342 
343 	char *value = uwsgi_cache_magic_get(key, keylen, &valsize, NULL, cache);
344 	if (value) {
345 		char *ret = estrndup(value, valsize);
346 		free(value);
347 		RETURN_STRING(ret);
348 	}
349 	RETURN_NULL();
350 }
351 
PHP_FUNCTION(uwsgi_cache_set)352 PHP_FUNCTION(uwsgi_cache_set) {
353 	char *key = NULL;
354 	int keylen;
355 	char *value = NULL;
356 	int vallen;
357 	uint64_t expires = 0;
358 	char *cache = NULL;
359 	int cachelen = 0;
360 
361 	if (!uwsgi.caches)
362 		RETURN_NULL();
363 
364 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|ls", &key, &keylen, &value, &vallen, &expires, &cache, &cachelen) == FAILURE) {
365                 RETURN_NULL();
366         }
367 
368 	if (!uwsgi_cache_magic_set(key, keylen, value, vallen, expires, 0, cache)) {
369 		RETURN_TRUE;
370 	}
371 	RETURN_NULL();
372 
373 }
374 
PHP_FUNCTION(uwsgi_cache_update)375 PHP_FUNCTION(uwsgi_cache_update) {
376         char *key = NULL;
377         int keylen;
378         char *value = NULL;
379         int vallen;
380         uint64_t expires = 0;
381         char *cache = NULL;
382         int cachelen = 0;
383 
384         if (!uwsgi.caches)
385                 RETURN_NULL();
386 
387         if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss|ls", &key, &keylen, &value, &vallen, &expires, &cache, &cachelen) == FAILURE) {
388                 RETURN_NULL();
389         }
390 
391         if (!uwsgi_cache_magic_set(key, keylen, value, vallen, expires, UWSGI_CACHE_FLAG_UPDATE, cache)) {
392                 RETURN_TRUE;
393         }
394         RETURN_NULL();
395 
396 }
397 
398 
PHP_FUNCTION(uwsgi_rpc)399 PHP_FUNCTION(uwsgi_rpc) {
400 
401 
402 	int num_args = 0;
403 	int i;
404 	char *node = NULL;
405 	char *func = NULL;
406 	zval ***varargs = NULL;
407 	zval *z_current_obj;
408 	char *argv[256];
409         uint16_t argvs[256];
410 	uint64_t size = 0;
411 
412 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "+", &varargs, &num_args) == FAILURE) {
413 		RETURN_NULL();
414 	}
415 
416         if (num_args < 2)
417 		goto clear;
418 
419 	if (num_args > 256 + 2)
420 		goto clear;
421 
422 	z_current_obj = *varargs[0];
423 	if (Z_TYPE_P(z_current_obj) != IS_STRING) {
424 		goto clear;
425 	}
426 
427 	node = Z_STRVAL_P(z_current_obj);
428 
429 	z_current_obj = *varargs[1];
430 	if (Z_TYPE_P(z_current_obj) != IS_STRING) {
431 		goto clear;
432 	}
433 
434 	func = Z_STRVAL_P(z_current_obj);
435 
436 	for(i=0;i<(num_args-2);i++) {
437 		z_current_obj = *varargs[i+2];
438 		if (Z_TYPE_P(z_current_obj) != IS_STRING) {
439 			goto clear;
440 		}
441 		argv[i] = Z_STRVAL_P(z_current_obj);
442 		argvs[i] = Z_STRLEN_P(z_current_obj);
443 	}
444 
445 	// response must always be freed
446         char *response = uwsgi_do_rpc(node, func, num_args - 2, argv, argvs, &size);
447         if (response) {
448 		// here we do not free varargs for performance reasons
449 		char *ret = estrndup(response, size);
450 		free(response);
451 		RETURN_STRING(ret);
452         }
453 
454 clear:
455 	efree(varargs);
456 	RETURN_NULL();
457 
458 }
459 
460 
PHP_FUNCTION(uwsgi_setprocname)461 PHP_FUNCTION(uwsgi_setprocname) {
462 
463 	char *name;
464 	int name_len;
465 
466 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &name, &name_len) == FAILURE) {
467 		RETURN_NULL();
468 	}
469 
470 	uwsgi_set_processname(estrndup(name, name_len));
471 
472 	RETURN_NULL();
473 }
474 
PHP_FUNCTION(uwsgi_signal)475 PHP_FUNCTION(uwsgi_signal) {
476 
477 	long long_signum;
478 	uint8_t signum = 0;
479 
480         if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &long_signum) == FAILURE) {
481                 RETURN_NULL();
482         }
483 
484 	signum = (uint8_t) long_signum;
485 	uwsgi_signal_send(uwsgi.signal_socket, signum);
486 
487         RETURN_NULL();
488 }
489 
490 zend_function_entry uwsgi_php_functions[] = {
491 	PHP_FE(uwsgi_version,   NULL)
492 	PHP_FE(uwsgi_setprocname,   NULL)
493 	PHP_FE(uwsgi_worker_id,   NULL)
494 	PHP_FE(uwsgi_masterpid,   NULL)
495 	PHP_FE(uwsgi_signal,   NULL)
496 
497 	PHP_FE(uwsgi_rpc,   NULL)
498 
499 	PHP_FE(uwsgi_cache_get,   NULL)
500 	PHP_FE(uwsgi_cache_set,   NULL)
501 	PHP_FE(uwsgi_cache_update,   NULL)
502 	PHP_FE(uwsgi_cache_del,   NULL)
503 	PHP_FE(uwsgi_cache_clear,   NULL)
504 	PHP_FE(uwsgi_cache_exists,   NULL)
505 	{ NULL, NULL, NULL},
506 };
507 
PHP_MINFO_FUNCTION(uwsgi_php_minfo)508 PHP_MINFO_FUNCTION(uwsgi_php_minfo) {
509 	php_info_print_table_start( );
510 	php_info_print_table_row(2, "uwsgi api", "enabled");
511 	if (uwsgi.caches) {
512 		php_info_print_table_row(2, "uwsgi cache", "enabled");
513 	}
514 	else {
515 		php_info_print_table_row(2, "uwsgi cache", "disabled");
516 	}
517 	php_info_print_table_end( );
518 }
519 
520 static zend_module_entry uwsgi_module_entry = {
521         STANDARD_MODULE_HEADER,
522         "uwsgi",
523         uwsgi_php_functions,
524         PHP_MINIT(uwsgi_php_minit),
525 	NULL,
526         NULL,
527         NULL,
528         PHP_MINFO(uwsgi_php_minfo),
529         UWSGI_VERSION,
530         STANDARD_MODULE_PROPERTIES
531 };
532 
533 typedef struct _user_config_cache_entry {
534 	time_t expires;
535 	HashTable *user_config;
536 } user_config_cache_entry;
537 
538 #if (PHP_MAJOR_VERSION >= 7)
user_config_cache_entry_dtor(zval * el)539 static void user_config_cache_entry_dtor(zval *el) {
540 	user_config_cache_entry *entry = (user_config_cache_entry *)Z_PTR_P(el);
541 #else
542 static void user_config_cache_entry_dtor(user_config_cache_entry *entry) {
543 #endif
544 	zend_hash_destroy(entry->user_config);
545 	free(entry->user_config);
546 	free(entry);
547 }
548 
549 static void activate_user_config(const char *filename, const char *doc_root, size_t doc_root_len) {
550 	char *ptr;
551 	user_config_cache_entry *new_entry, *entry;
552 
553 	time_t request_time = (time_t)sapi_get_request_time();
554 
555 	// get dirname (path) from filename
556 	size_t path_len = (strrchr(filename, DEFAULT_SLASH) - filename) + 1;
557 	char path[path_len];
558 	memcpy(path, filename, path_len);
559 	path[path_len] = '\0';
560 
561 	// get or create entry from cache
562 #if (PHP_MAJOR_VERSION >= 7)
563 	if ((entry = zend_hash_str_find_ptr(&uphp.user_config_cache, path, path_len)) == NULL) {
564 #else
565 	if (zend_hash_find(&uphp.user_config_cache, path, path_len + 1, (void **) &entry) == FAILURE) {
566 #endif
567 		new_entry = pemalloc(sizeof(user_config_cache_entry), 1);
568 		new_entry->expires = 0;
569 		new_entry->user_config = (HashTable *) pemalloc(sizeof(HashTable), 1);
570 
571 		// make zend_hash to store all user.ini settings.
572 		zend_hash_init(new_entry->user_config, 0, NULL, (dtor_func_t) config_zval_dtor, 1);
573 #if (PHP_MAJOR_VERSION >= 7)
574 		entry = zend_hash_str_update_ptr(&uphp.user_config_cache, path, path_len, new_entry);
575 #else
576 		zend_hash_update(&uphp.user_config_cache, path, path_len + 1, new_entry, sizeof(user_config_cache_entry), (void **) &entry);
577 #endif
578 	}
579 
580 	if (request_time > entry->expires) {
581 
582 		// clear the expired config
583 		zend_hash_clean(entry->user_config);
584 
585 		// set pointer to end of docroot
586 		ptr = path + (doc_root_len - 1);
587 
588 		// parse all user.ini files starting from docroot.
589 		while ((ptr = strchr(ptr, DEFAULT_SLASH)) != NULL) {
590 			*ptr = 0;
591 			php_parse_user_ini_file(path, PG(user_ini_filename), entry->user_config);
592 			*ptr = '/';
593 			ptr++;
594 		}
595 
596 		// set (new) expiry time
597 		entry->expires = request_time + PG(user_ini_cache_ttl);
598 	}
599 
600 	// activate all user.ini variables
601 	php_ini_activate_config(entry->user_config, PHP_INI_PERDIR, PHP_INI_STAGE_HTACCESS);
602 }
603 
604 static int php_uwsgi_startup(sapi_module_struct *sapi_module)
605 {
606 
607 	if (php_module_startup(&uwsgi_sapi_module, &uwsgi_module_entry, 1)==FAILURE) {
608 		return FAILURE;
609 	} else {
610 		return SUCCESS;
611 	}
612 }
613 
614 #if (PHP_MAJOR_VERSION >= 8)
615 static void sapi_uwsgi_log_message(const char *message, int syslog_type_int) {
616 #elif ((PHP_MAJOR_VERSION == 7) && (PHP_MINOR_VERSION >= 1))
617 static void sapi_uwsgi_log_message(char *message, int syslog_type_int) {
618 #else
619 static void sapi_uwsgi_log_message(char *message) {
620 #endif
621 	uwsgi_log("%s\n", message);
622 }
623 
624 static sapi_module_struct uwsgi_sapi_module = {
625 	"uwsgi",
626 	"uWSGI/php",
627 
628 	php_uwsgi_startup,
629 	php_module_shutdown_wrapper,
630 
631 	NULL,									/* activate */
632 	NULL,									/* deactivate */
633 
634 	sapi_uwsgi_ub_write,
635 	NULL,
636 	NULL,									/* get uid */
637 	NULL,									/* getenv */
638 
639 	php_error,
640 
641 	NULL,
642 	sapi_uwsgi_send_headers,
643 	NULL,
644 	sapi_uwsgi_read_post,
645 	sapi_uwsgi_read_cookies,
646 
647 	sapi_uwsgi_register_variables,
648 	sapi_uwsgi_log_message,									/* Log message */
649 	NULL,									/* Get request time */
650 	NULL,									/* Child terminate */
651 
652 	STANDARD_SAPI_MODULE_PROPERTIES
653 };
654 
655 int uwsgi_php_init(void) {
656 
657 	struct uwsgi_string_list *pset = uphp.set;
658 	struct uwsgi_string_list *append_config = uphp.append_config;
659 
660 #ifdef ZTS
661         tsrm_startup(1, 1, 0, NULL);
662 #endif
663 
664 	sapi_startup(&uwsgi_sapi_module);
665 
666 	// applying custom options
667 	while(append_config) {
668 		uwsgi_php_append_config(append_config->value);
669 		append_config = append_config->next;
670 	}
671        	while(pset) {
672                	uwsgi_php_set(pset->value);
673                	pset = pset->next;
674        	}
675 
676 	if (uphp.dump_config) {
677 		uwsgi_log("--- PHP custom config ---\n\n");
678 		uwsgi_log("%s\n", uwsgi_sapi_module.ini_entries);
679 		uwsgi_log("--- end of PHP custom config ---\n");
680 	}
681 
682 	zend_hash_init(&uphp.user_config_cache, 0, NULL, (dtor_func_t) user_config_cache_entry_dtor, 1);
683 
684 	// fix docroot
685         if (uphp.docroot) {
686 		char *orig_docroot = uphp.docroot;
687 		uphp.docroot = uwsgi_expand_path(uphp.docroot, strlen(uphp.docroot), NULL);
688 		if (!uphp.docroot) {
689 			uwsgi_log("unable to set php docroot to %s\n", orig_docroot);
690 			exit(1);
691 		}
692 		uwsgi_log("PHP document root set to %s\n", uphp.docroot);
693 		uphp.docroot_len = strlen(uphp.docroot);
694 	}
695 
696 	if (uphp.sapi_name) {
697 		uwsgi_sapi_module.name = uphp.sapi_name;
698 	}
699 	uwsgi_sapi_module.startup(&uwsgi_sapi_module);
700 	uwsgi_log("PHP %s initialized\n", PHP_VERSION);
701 
702 	return 0;
703 }
704 
705 int uwsgi_php_walk(struct wsgi_request *wsgi_req, char *full_path, char *docroot, size_t docroot_len, char **path_info) {
706 
707         // and now start walking...
708         uint16_t i;
709         char *ptr = wsgi_req->path_info;
710         char *dst = full_path+docroot_len;
711         char *part = ptr;
712         int part_size = 0;
713         struct stat st;
714 
715 	if (wsgi_req->path_info_len == 0) return 0;
716 
717         if (ptr[0] == '/') part_size++;
718 
719         for(i=0;i<wsgi_req->path_info_len;i++) {
720                 if (ptr[i] == '/') {
721                         memcpy(dst, part, part_size-1);
722                         *(dst+part_size-1) = 0;
723 
724                         if (stat(full_path, &st)) {
725                                 return -1;
726                         }
727 
728 
729                         // not a directory, stop walking
730                         if (!S_ISDIR(st.st_mode)) {
731                                 if (i < (wsgi_req->path_info_len)-1) {
732                                         *path_info = ptr + i;
733                                 }
734                                 return 0;
735                         }
736 
737 
738                         // check for buffer overflow !!!
739                         *(dst+part_size-1) = '/';
740                         *(dst+part_size) = 0;
741 
742                         dst += part_size ;
743                         part_size = 0;
744                         part = ptr + i + 1;
745                 }
746 
747                 part_size++;
748         }
749 
750         if (part < wsgi_req->path_info+wsgi_req->path_info_len) {
751                 memcpy(dst, part, part_size-1);
752                 *(dst+part_size-1) = 0;
753                 if (stat(full_path, &st)) {
754                         return -1;
755                 }
756         }
757 
758         return 0;
759 
760 
761 }
762 
763 
764 int uwsgi_php_request(struct wsgi_request *wsgi_req) {
765 
766 	char real_filename[PATH_MAX+1];
767 	char *path_info = NULL;
768 	size_t real_filename_len = 0;
769 	struct stat php_stat;
770 	char *filename = NULL;
771 	int force_empty_script_name = 0;
772 
773 	zend_file_handle file_handle;
774 
775 	SG(server_context) = (void *) wsgi_req;
776 
777 	if (uwsgi_parse_vars(wsgi_req)) {
778 		return -1;
779 	}
780 
781 	char *orig_path_info = wsgi_req->path_info;
782 	uint16_t orig_path_info_len = wsgi_req->path_info_len;
783 
784 	if (uphp.docroot) {
785 		wsgi_req->document_root = uphp.docroot;
786 	}
787 	// fallback to cwd
788 	else if (!wsgi_req->document_root_len) {
789 		wsgi_req->document_root = uwsgi.cwd;
790 	}
791 	else {
792 		// explode DOCUMENT_ROOT (both for security and sanity checks)
793 		// this memory will be cleared on request end
794 		char *sanitized_docroot = ecalloc(1, PATH_MAX+1);
795 		if (!uwsgi_expand_path(wsgi_req->document_root, wsgi_req->document_root_len, sanitized_docroot)) {
796 			efree(sanitized_docroot);
797 			return -1;
798 		}
799 		wsgi_req->document_root = sanitized_docroot;
800 	}
801 
802 	// fix document_root_len
803 	wsgi_req->document_root_len = strlen(wsgi_req->document_root);
804 
805 	if (uphp.app) {
806 #ifdef UWSGI_PCRE
807 		struct uwsgi_regexp_list *bypass = uphp.app_bypass;
808 		while (bypass) {
809                         if (uwsgi_regexp_match(bypass->pattern, bypass->pattern_extra, wsgi_req->uri, wsgi_req->uri_len) >= 0) {
810 				goto oldstyle;
811                         }
812                         bypass = bypass->next;
813                 }
814 #endif
815 
816 		strcpy(real_filename, uphp.app);
817 		if (wsgi_req->path_info_len == 1 && wsgi_req->path_info[0] == '/') {
818 			goto appready;
819 		}
820 		if (uphp.app_qs) {
821 			size_t app_qs_len = strlen(uphp.app_qs);
822 			size_t qs_len = wsgi_req->path_info_len + app_qs_len;
823 			if (wsgi_req->query_string_len > 0) {
824 				qs_len += 1 + wsgi_req->query_string_len;
825 			}
826 			char *qs = ecalloc(1, qs_len+1);
827 			memcpy(qs, uphp.app_qs, app_qs_len);
828 			memcpy(qs+app_qs_len, wsgi_req->path_info, wsgi_req->path_info_len);
829 			if (wsgi_req->query_string_len > 0) {
830 				char *ptr = qs+app_qs_len+wsgi_req->path_info_len;
831 				*ptr = '&';
832 				memcpy(ptr+1, wsgi_req->query_string, wsgi_req->query_string_len);
833 			}
834 			wsgi_req->query_string = qs;
835 			wsgi_req->query_string_len = qs_len;
836 		}
837 appready:
838 		wsgi_req->path_info = "";
839 		wsgi_req->path_info_len = 0;
840 		force_empty_script_name = 1;
841 		goto secure2;
842 	}
843 
844 #ifdef UWSGI_PCRE
845 oldstyle:
846 #endif
847 
848 	filename = uwsgi_concat4n(wsgi_req->document_root, wsgi_req->document_root_len, "/", 1, wsgi_req->path_info, wsgi_req->path_info_len, "", 0);
849 	activate_user_config(filename, wsgi_req->document_root, wsgi_req->document_root_len);
850 
851 	if (uwsgi_php_walk(wsgi_req, filename, wsgi_req->document_root, wsgi_req->document_root_len, &path_info)) {
852 		free(filename);
853 
854 		if (uphp.fallback || uphp.fallback2) {
855 			if (uphp.fallback) {
856 				filename = uwsgi_str(uphp.fallback);
857 			} else {
858 				filename = uwsgi_concat2n(wsgi_req->document_root, strlen(wsgi_req->document_root),
859 						uphp.fallback2, strlen(uphp.fallback2));
860 				wsgi_req->script_name = uphp.fallback2;
861 				wsgi_req->script_name_len = strlen(uphp.fallback2);
862 			}
863 
864 			if (uphp.fallback_qs) {
865 				size_t fqs_len = strlen(uphp.fallback_qs);
866 				size_t new_qs_len = orig_path_info_len
867 					+ fqs_len + 1
868 					+ wsgi_req->query_string_len;
869 				char *new_qs = ecalloc(1, new_qs_len + 1);
870 
871 				memcpy(new_qs, uphp.fallback_qs, fqs_len);
872 				new_qs[fqs_len] = '=';
873 				memcpy(new_qs + fqs_len + 1, orig_path_info, orig_path_info_len);
874 				if (wsgi_req->query_string_len) {
875 					new_qs[fqs_len + 1 + orig_path_info_len] = '&';
876 					memcpy(new_qs + fqs_len + 2 + orig_path_info_len,
877 						wsgi_req->query_string, wsgi_req->query_string_len);
878 				}
879 
880 				wsgi_req->query_string = new_qs;
881 				wsgi_req->query_string_len = new_qs_len;
882 			}
883 		}
884 		else {
885 			uwsgi_404(wsgi_req);
886 			return -1;
887 		}
888 	}
889 
890 	if (path_info) {
891 		wsgi_req->path_info = path_info;
892 		wsgi_req->path_info_len = orig_path_info_len - (path_info - orig_path_info);
893 	}
894 	else {
895 		wsgi_req->path_info = "";
896 		wsgi_req->path_info_len = 0;
897 	}
898 
899 
900 	if (!realpath(filename, real_filename)) {
901 		free(filename);
902 		uwsgi_404(wsgi_req);
903 		return -1;
904 	}
905 
906 	free(filename);
907 	real_filename_len = strlen(real_filename);
908 
909 	// first check for valid doc roots
910 	if (uphp.allowed_docroot) {
911 		struct uwsgi_string_list *usl = uphp.allowed_docroot;
912 		while(usl) {
913 			if (!uwsgi_starts_with(real_filename, real_filename_len, usl->value, usl->len)) {
914 				goto secure;
915 			}
916 			usl = usl->next;
917 		}
918 		uwsgi_403(wsgi_req);
919 		uwsgi_log("PHP security error: %s is not under an allowed docroot\n", real_filename);
920 		return -1;
921 	}
922 	// then for default docroot (if any)
923 	else if (uphp.docroot)
924 	{
925 		if (!uwsgi_starts_with(real_filename, real_filename_len, uphp.docroot, uphp.docroot_len)) {
926 			goto secure;
927 		}
928 		uwsgi_403(wsgi_req);
929 		uwsgi_log("PHP security error: %s is not under the default docroot\n", real_filename);
930 		return -1;
931 	}
932 
933 secure:
934 
935 	if (stat(real_filename, &php_stat)) {
936                 uwsgi_404(wsgi_req);
937                 return UWSGI_OK;
938         }
939 
940         if (S_ISDIR(php_stat.st_mode)) {
941 
942                 // add / to directories
943                 if (orig_path_info_len == 0 || (orig_path_info_len > 0 && orig_path_info[orig_path_info_len-1] != '/')) {
944 			wsgi_req->path_info = orig_path_info;
945 			wsgi_req->path_info_len = orig_path_info_len;
946                         uwsgi_redirect_to_slash(wsgi_req);
947                         return UWSGI_OK;
948                 }
949                 struct uwsgi_string_list *upi = uphp.index;
950                 real_filename[real_filename_len] = '/';
951                 real_filename_len++;
952                 int found = 0;
953                 while(upi) {
954                         if (real_filename_len + upi->len + 1 < PATH_MAX) {
955                                 // add + 1 to ensure null byte
956                                 memcpy(real_filename+real_filename_len, upi->value, upi->len + 1);
957                                 if (!access(real_filename, R_OK)) {
958 
959                                         found = 1;
960                                         break;
961                                 }
962                         }
963                         upi = upi->next;
964                 }
965 
966                 if (!found) {
967                         uwsgi_404(wsgi_req);
968                         return UWSGI_OK;
969                 }
970 
971 		real_filename_len = strlen(real_filename);
972 
973         }
974 
975 
976 	if (uphp.allowed_ext) {
977 		struct uwsgi_string_list *usl = uphp.allowed_ext;
978                 while(usl) {
979 			if (real_filename_len >= usl->len) {
980 				if (!uwsgi_strncmp(real_filename+(real_filename_len-usl->len), usl->len, usl->value, usl->len)) {
981                                 	goto secure2;
982                         	}
983 			}
984                         usl = usl->next;
985                 }
986                 uwsgi_403(wsgi_req);
987                 uwsgi_log("PHP security error: %s does not end with an allowed extension\n", real_filename);
988                 return -1;
989 	}
990 
991 secure2:
992 
993 	wsgi_req->file = real_filename;
994 	wsgi_req->file_len = strlen(wsgi_req->file);
995 
996 	if (uphp.allowed_scripts) {
997                 struct uwsgi_string_list *usl = uphp.allowed_scripts;
998                 while(usl) {
999                 	if (!uwsgi_strncmp(wsgi_req->file, wsgi_req->file_len, usl->value, usl->len)) {
1000                         	goto secure3;
1001                         }
1002                         usl = usl->next;
1003                 }
1004                 uwsgi_403(wsgi_req);
1005                 uwsgi_log("PHP security error: %s is not an allowed script\n", real_filename);
1006                 return -1;
1007         }
1008 
1009 secure3:
1010 	if (force_empty_script_name) {
1011 		wsgi_req->script_name = "";
1012 		wsgi_req->script_name_len = 0;
1013 	}
1014 	else if (!uphp.fallback2) {
1015 		wsgi_req->script_name = orig_path_info;
1016 		if (path_info) {
1017 			wsgi_req->script_name_len = path_info - orig_path_info;
1018 		}
1019 		else {
1020 			wsgi_req->script_name_len = orig_path_info_len;
1021 		}
1022 	}
1023 
1024 #ifdef UWSGI_DEBUG
1025 	uwsgi_log("php filename = %s script_name = %.*s (%d) document_root = %.*s (%d)\n", real_filename, wsgi_req->script_name_len, wsgi_req->script_name, wsgi_req->script_name_len,
1026 		wsgi_req->document_root_len, wsgi_req->document_root, wsgi_req->document_root_len);
1027 #endif
1028 
1029 	// now check for allowed paths and extensions
1030 
1031 	SG(request_info).request_uri = estrndup(wsgi_req->uri, wsgi_req->uri_len);
1032         SG(request_info).request_method = estrndup(wsgi_req->method, wsgi_req->method_len);
1033 	SG(request_info).proto_num = 1001;
1034 
1035 	SG(request_info).query_string = estrndup(wsgi_req->query_string, wsgi_req->query_string_len);
1036         SG(request_info).content_length = wsgi_req->post_cl;
1037 	SG(request_info).content_type = estrndup(wsgi_req->content_type, wsgi_req->content_type_len);
1038 
1039 	// reinitialize it at every request !!!
1040 	SG(sapi_headers).http_response_code = 200;
1041 
1042 	SG(request_info).path_translated = wsgi_req->file;
1043 
1044         memset(&file_handle, 0, sizeof(zend_file_handle));
1045         file_handle.type = ZEND_HANDLE_FILENAME;
1046         file_handle.filename = real_filename;
1047 
1048         if (php_request_startup() == FAILURE) {
1049 		uwsgi_500(wsgi_req);
1050                 return -1;
1051         }
1052 
1053 	struct uwsgi_string_list *usl=NULL;
1054 
1055 	uwsgi_foreach(usl, uphp.exec_before) {
1056 		if (zend_eval_string_ex(usl->value, NULL, "uWSGI php exec before", 1) == FAILURE) goto end;
1057 	}
1058 
1059         php_execute_script(&file_handle);
1060 
1061 	uwsgi_foreach(usl, uphp.exec_after) {
1062 		if (zend_eval_string_ex(usl->value, NULL, "uWSGI php exec after", 1) == FAILURE) goto end;
1063 	}
1064 
1065 end:
1066         php_request_shutdown(NULL);
1067 
1068 	return 0;
1069 }
1070 
1071 void uwsgi_php_after_request(struct wsgi_request *wsgi_req) {
1072 
1073 	log_request(wsgi_req);
1074 }
1075 
1076 
1077 SAPI_API struct uwsgi_plugin php_plugin = {
1078 	.name = "php",
1079 	.modifier1 = 14,
1080 	.init = uwsgi_php_init,
1081 	.request = uwsgi_php_request,
1082 	.after_request = uwsgi_php_after_request,
1083 	.options = uwsgi_php_options,
1084 };
1085 
1086