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 | Author: Moriyoshi Koizumi <moriyoshi@php.net> |
14 | Xinchen Hui <laruence@php.net> |
15 +----------------------------------------------------------------------+
16 */
17
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <fcntl.h>
21 #include <assert.h>
22
23 #ifdef PHP_WIN32
24 # include <process.h>
25 # include <io.h>
26 # include "win32/time.h"
27 # include "win32/signal.h"
28 # include "win32/php_registry.h"
29 # include <sys/timeb.h>
30 #else
31 # include "php_config.h"
32 #endif
33
34 #ifdef __riscos__
35 #include <unixlib/local.h>
36 #endif
37
38 #if HAVE_SYS_TIME_H
39 #include <sys/time.h>
40 #endif
41 #if HAVE_UNISTD_H
42 #include <unistd.h>
43 #endif
44
45 #include <signal.h>
46 #include <locale.h>
47
48 #if HAVE_DLFCN_H
49 #include <dlfcn.h>
50 #endif
51
52 #include "SAPI.h"
53 #include "php.h"
54 #include "php_ini.h"
55 #include "php_main.h"
56 #include "php_globals.h"
57 #include "php_variables.h"
58 #include "zend_hash.h"
59 #include "zend_modules.h"
60 #include "fopen_wrappers.h"
61 #include "http_status_codes.h"
62
63 #include "zend_compile.h"
64 #include "zend_execute.h"
65 #include "zend_highlight.h"
66 #include "zend_exceptions.h"
67
68 #include "php_getopt.h"
69
70 #ifndef PHP_WIN32
71 # define php_select(m, r, w, e, t) select(m, r, w, e, t)
72 # define SOCK_EINVAL EINVAL
73 # define SOCK_EAGAIN EAGAIN
74 # define SOCK_EINTR EINTR
75 # define SOCK_EADDRINUSE EADDRINUSE
76 #else
77 # include "win32/select.h"
78 # define SOCK_EINVAL WSAEINVAL
79 # define SOCK_EAGAIN WSAEWOULDBLOCK
80 # define SOCK_EINTR WSAEINTR
81 # define SOCK_EADDRINUSE WSAEADDRINUSE
82 #endif
83
84 #include "ext/standard/file.h" /* for php_set_sock_blocking() :-( */
85 #include "zend_smart_str.h"
86 #include "ext/standard/html.h"
87 #include "ext/standard/url.h" /* for php_raw_url_decode() */
88 #include "ext/standard/php_string.h" /* for php_dirname() */
89 #include "ext/date/php_date.h" /* for php_format_date() */
90 #include "php_network.h"
91
92 #include "php_http_parser.h"
93 #include "php_cli_server.h"
94 #include "php_cli_server_arginfo.h"
95 #include "mime_type_map.h"
96
97 #include "php_cli_process_title.h"
98 #include "php_cli_process_title_arginfo.h"
99
100 #define OUTPUT_NOT_CHECKED -1
101 #define OUTPUT_IS_TTY 1
102 #define OUTPUT_NOT_TTY 0
103
104 #if HAVE_FORK
105 # include <sys/wait.h>
106 static pid_t php_cli_server_master;
107 static pid_t *php_cli_server_workers;
108 static zend_long php_cli_server_workers_max;
109 #endif
110
111 static zend_string* cli_concat_persistent_zstr_with_char(zend_string *old_str, const char *at, size_t length);
112
113 typedef struct php_cli_server_poller {
114 fd_set rfds, wfds;
115 struct {
116 fd_set rfds, wfds;
117 } active;
118 php_socket_t max_fd;
119 } php_cli_server_poller;
120
121 typedef struct php_cli_server_request {
122 enum php_http_method request_method;
123 int protocol_version;
124 zend_string *request_uri;
125 char *vpath;
126 size_t vpath_len;
127 char *path_translated;
128 size_t path_translated_len;
129 char *path_info;
130 size_t path_info_len;
131 char *query_string;
132 size_t query_string_len;
133 HashTable headers;
134 HashTable headers_original_case;
135 char *content;
136 size_t content_len;
137 const char *ext;
138 size_t ext_len;
139 zend_stat_t sb;
140 } php_cli_server_request;
141
142 typedef struct php_cli_server_chunk {
143 struct php_cli_server_chunk *next;
144 enum php_cli_server_chunk_type {
145 PHP_CLI_SERVER_CHUNK_HEAP,
146 PHP_CLI_SERVER_CHUNK_IMMORTAL
147 } type;
148 union {
149 struct { void *block; char *p; size_t len; } heap;
150 struct { const char *p; size_t len; } immortal;
151 } data;
152 } php_cli_server_chunk;
153
154 typedef struct php_cli_server_buffer {
155 php_cli_server_chunk *first;
156 php_cli_server_chunk *last;
157 } php_cli_server_buffer;
158
159 typedef struct php_cli_server_content_sender {
160 php_cli_server_buffer buffer;
161 } php_cli_server_content_sender;
162
163 typedef struct php_cli_server_client {
164 struct php_cli_server *server;
165 php_socket_t sock;
166 struct sockaddr *addr;
167 socklen_t addr_len;
168 zend_string *addr_str;
169 php_http_parser parser;
170 bool request_read;
171 zend_string *current_header_name;
172 zend_string *current_header_value;
173 enum { HEADER_NONE=0, HEADER_FIELD, HEADER_VALUE } last_header_element;
174 size_t post_read_offset;
175 php_cli_server_request request;
176 bool content_sender_initialized;
177 php_cli_server_content_sender content_sender;
178 int file_fd;
179 } php_cli_server_client;
180
181 typedef struct php_cli_server {
182 php_socket_t server_sock;
183 php_cli_server_poller poller;
184 int is_running;
185 char *host;
186 int port;
187 int address_family;
188 char *document_root;
189 size_t document_root_len;
190 char *router;
191 size_t router_len;
192 socklen_t socklen;
193 HashTable clients;
194 HashTable extension_mime_types;
195 } php_cli_server;
196
197 typedef struct php_cli_server_http_response_status_code_pair {
198 int code;
199 const char *str;
200 } php_cli_server_http_response_status_code_pair;
201
202 static php_cli_server_http_response_status_code_pair template_map[] = {
203 { 400, "<h1>%s</h1><p>Your browser sent a request that this server could not understand.</p>" },
204 { 404, "<h1>%s</h1><p>The requested resource <code class=\"url\">%s</code> was not found on this server.</p>" },
205 { 405, "<h1>%s</h1><p>Requested method not allowed.</p>" },
206 { 500, "<h1>%s</h1><p>The server is temporarily unavailable.</p>" },
207 { 501, "<h1>%s</h1><p>Request method not supported.</p>" }
208 };
209
210 #define PHP_CLI_SERVER_LOG_PROCESS 1
211 #define PHP_CLI_SERVER_LOG_ERROR 2
212 #define PHP_CLI_SERVER_LOG_MESSAGE 3
213
214 static int php_cli_server_log_level = 3;
215
216 #if HAVE_UNISTD_H || defined(PHP_WIN32)
217 static int php_cli_output_is_tty = OUTPUT_NOT_CHECKED;
218 #endif
219
220 static const char php_cli_server_request_error_unexpected_eof[] = "Unexpected EOF";
221
222 static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len);
223 static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len);
224 static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk);
225 static void php_cli_server_logf(int type, const char *format, ...);
226 static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message);
227
228 ZEND_DECLARE_MODULE_GLOBALS(cli_server)
229
230 /* {{{ static char php_cli_server_css[]
231 * copied from ext/standard/info.c
232 */
233 static const char php_cli_server_css[] = "<style>\n" \
234 "body { background-color: #fcfcfc; color: #333333; margin: 0; padding:0; }\n" \
235 "h1 { font-size: 1.5em; font-weight: normal; background-color: #9999cc; min-height:2em; line-height:2em; border-bottom: 1px inset black; margin: 0; }\n" \
236 "h1, p { padding-left: 10px; }\n" \
237 "code.url { background-color: #eeeeee; font-family:monospace; padding:0 2px;}\n" \
238 "</style>\n";
239 /* }}} */
240
241 #ifdef PHP_WIN32
php_cli_server_get_system_time(char * buf)242 static bool php_cli_server_get_system_time(char *buf) {
243 struct _timeb system_time;
244 errno_t err;
245
246 if (buf == NULL) {
247 return false;
248 }
249
250 _ftime(&system_time);
251 err = ctime_s(buf, 52, &(system_time.time) );
252 if (err) {
253 return false;
254 }
255 return true;
256 }
257 #else
php_cli_server_get_system_time(char * buf)258 static bool php_cli_server_get_system_time(char *buf) {
259 struct timeval tv;
260 struct tm tm;
261
262 gettimeofday(&tv, NULL);
263
264 /* TODO: should be checked for NULL tm/return value */
265 php_localtime_r(&tv.tv_sec, &tm);
266 php_asctime_r(&tm, buf);
267 return true;
268 }
269 #endif
270
271 /* Destructor for php_cli_server_request->headers, this frees header value */
cli_header_value_dtor(zval * zv)272 static void cli_header_value_dtor(zval *zv) /* {{{ */
273 {
274 zend_string_release_ex(Z_STR_P(zv), /* persistent */ true);
275 } /* }}} */
276
get_last_error(void)277 static char *get_last_error(void) /* {{{ */
278 {
279 return pestrdup(strerror(errno), 1);
280 } /* }}} */
281
status_comp(const void * a,const void * b)282 static int status_comp(const void *a, const void *b) /* {{{ */
283 {
284 const http_response_status_code_pair *pa = (const http_response_status_code_pair *) a;
285 const http_response_status_code_pair *pb = (const http_response_status_code_pair *) b;
286
287 if (pa->code < pb->code) {
288 return -1;
289 } else if (pa->code > pb->code) {
290 return 1;
291 }
292
293 return 0;
294 } /* }}} */
295
get_status_string(int code)296 static const char *get_status_string(int code) /* {{{ */
297 {
298 http_response_status_code_pair needle = {code, NULL},
299 *result = NULL;
300
301 result = bsearch(&needle, http_status_map, http_status_map_len, sizeof(needle), status_comp);
302
303 if (result) {
304 return result->str;
305 }
306
307 /* Returning NULL would require complicating append_http_status_line() to
308 * not segfault in that case, so let's just return a placeholder, since RFC
309 * 2616 requires a reason phrase. This is basically what a lot of other Web
310 * servers do in this case anyway. */
311 return "Unknown Status Code";
312 } /* }}} */
313
get_template_string(int code)314 static const char *get_template_string(int code) /* {{{ */
315 {
316 size_t e = (sizeof(template_map) / sizeof(php_cli_server_http_response_status_code_pair));
317 size_t s = 0;
318
319 while (e != s) {
320 size_t c = MIN((e + s + 1) / 2, e - 1);
321 int d = template_map[c].code;
322 if (d > code) {
323 e = c;
324 } else if (d < code) {
325 s = c;
326 } else {
327 return template_map[c].str;
328 }
329 }
330 return NULL;
331 } /* }}} */
332
append_http_status_line(smart_str * buffer,int protocol_version,int response_code,bool persistent)333 static void append_http_status_line(smart_str *buffer, int protocol_version, int response_code, bool persistent) /* {{{ */
334 {
335 if (!response_code) {
336 response_code = 200;
337 }
338 smart_str_appendl_ex(buffer, "HTTP", 4, persistent);
339 smart_str_appendc_ex(buffer, '/', persistent);
340 smart_str_append_long_ex(buffer, protocol_version / 100, persistent);
341 smart_str_appendc_ex(buffer, '.', persistent);
342 smart_str_append_long_ex(buffer, protocol_version % 100, persistent);
343 smart_str_appendc_ex(buffer, ' ', persistent);
344 smart_str_append_long_ex(buffer, response_code, persistent);
345 smart_str_appendc_ex(buffer, ' ', persistent);
346 smart_str_appends_ex(buffer, get_status_string(response_code), persistent);
347 smart_str_appendl_ex(buffer, "\r\n", 2, persistent);
348 } /* }}} */
349
append_essential_headers(smart_str * buffer,php_cli_server_client * client,bool persistent,sapi_headers_struct * sapi_headers)350 static void append_essential_headers(smart_str* buffer, php_cli_server_client *client, bool persistent, sapi_headers_struct *sapi_headers) /* {{{ */
351 {
352 zval *val;
353 struct timeval tv = {0};
354 bool append_date_header = true;
355
356 if (sapi_headers != NULL) {
357 zend_llist_position pos;
358 sapi_header_struct *h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
359 while (h) {
360 if (h->header_len > strlen("Date:")) {
361 if (strncasecmp(h->header, "Date:", strlen("Date:")) == 0) {
362 append_date_header = false;
363 break;
364 }
365 }
366 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
367 }
368 }
369
370 if (NULL != (val = zend_hash_str_find(&client->request.headers, "host", sizeof("host")-1))) {
371 smart_str_appends_ex(buffer, "Host: ", persistent);
372 smart_str_append_ex(buffer, Z_STR_P(val), persistent);
373 smart_str_appends_ex(buffer, "\r\n", persistent);
374 }
375
376 if (append_date_header && !gettimeofday(&tv, NULL)) {
377 zend_string *dt = php_format_date("D, d M Y H:i:s", sizeof("D, d M Y H:i:s") - 1, tv.tv_sec, 0);
378 smart_str_appends_ex(buffer, "Date: ", persistent);
379 smart_str_append_ex(buffer, dt, persistent);
380 smart_str_appends_ex(buffer, " GMT\r\n", persistent);
381 zend_string_release_ex(dt, 0);
382 }
383
384 smart_str_appendl_ex(buffer, "Connection: close\r\n", sizeof("Connection: close\r\n") - 1, persistent);
385 } /* }}} */
386
get_mime_type(const php_cli_server * server,const char * ext,size_t ext_len)387 static const char *get_mime_type(const php_cli_server *server, const char *ext, size_t ext_len) /* {{{ */
388 {
389 char *ret;
390 ALLOCA_FLAG(use_heap)
391 char *ext_lower = do_alloca(ext_len + 1, use_heap);
392 zend_str_tolower_copy(ext_lower, ext, ext_len);
393 ret = zend_hash_str_find_ptr(&server->extension_mime_types, ext_lower, ext_len);
394 free_alloca(ext_lower, use_heap);
395 return (const char*)ret;
396 } /* }}} */
397
PHP_FUNCTION(apache_request_headers)398 PHP_FUNCTION(apache_request_headers) /* {{{ */
399 {
400 php_cli_server_client *client;
401
402 if (zend_parse_parameters_none() == FAILURE) {
403 RETURN_THROWS();
404 }
405
406 client = SG(server_context);
407
408 /* Need to duplicate the header HashTable */
409 RETURN_ARR(zend_array_dup(&client->request.headers_original_case));
410 }
411 /* }}} */
412
add_response_header(sapi_header_struct * h,zval * return_value)413 static void add_response_header(sapi_header_struct *h, zval *return_value) /* {{{ */
414 {
415 char *s, *p;
416 ptrdiff_t len;
417 ALLOCA_FLAG(use_heap)
418
419 if (h->header_len > 0) {
420 p = strchr(h->header, ':');
421 len = p - h->header;
422 if (p && (len > 0)) {
423 while (len > 0 && (h->header[len-1] == ' ' || h->header[len-1] == '\t')) {
424 len--;
425 }
426 if (len) {
427 s = do_alloca(len + 1, use_heap);
428 memcpy(s, h->header, len);
429 s[len] = 0;
430 do {
431 p++;
432 } while (*p == ' ' || *p == '\t');
433 add_assoc_stringl_ex(return_value, s, (uint32_t)len, p, h->header_len - (p - h->header));
434 free_alloca(s, use_heap);
435 }
436 }
437 }
438 }
439 /* }}} */
440
PHP_FUNCTION(apache_response_headers)441 PHP_FUNCTION(apache_response_headers) /* {{{ */
442 {
443 if (zend_parse_parameters_none() == FAILURE) {
444 RETURN_THROWS();
445 }
446
447 array_init(return_value);
448 zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t)add_response_header, return_value);
449 }
450 /* }}} */
451
452 /* {{{ cli_server module */
453
cli_server_init_globals(zend_cli_server_globals * cg)454 static void cli_server_init_globals(zend_cli_server_globals *cg)
455 {
456 cg->color = 0;
457 }
458
459 PHP_INI_BEGIN()
460 STD_PHP_INI_BOOLEAN("cli_server.color", "0", PHP_INI_ALL, OnUpdateBool, color, zend_cli_server_globals, cli_server_globals)
PHP_INI_END()461 PHP_INI_END()
462
463 static PHP_MINIT_FUNCTION(cli_server)
464 {
465 ZEND_INIT_MODULE_GLOBALS(cli_server, cli_server_init_globals, NULL);
466 REGISTER_INI_ENTRIES();
467 return SUCCESS;
468 }
469
PHP_MSHUTDOWN_FUNCTION(cli_server)470 static PHP_MSHUTDOWN_FUNCTION(cli_server)
471 {
472 UNREGISTER_INI_ENTRIES();
473 return SUCCESS;
474 }
475
PHP_MINFO_FUNCTION(cli_server)476 static PHP_MINFO_FUNCTION(cli_server)
477 {
478 DISPLAY_INI_ENTRIES();
479 }
480
481 zend_module_entry cli_server_module_entry = {
482 STANDARD_MODULE_HEADER,
483 "cli_server",
484 NULL,
485 PHP_MINIT(cli_server),
486 PHP_MSHUTDOWN(cli_server),
487 NULL,
488 NULL,
489 PHP_MINFO(cli_server),
490 PHP_VERSION,
491 STANDARD_MODULE_PROPERTIES
492 };
493 /* }}} */
494
495 const zend_function_entry server_additional_functions[] = {
496 PHP_FE(cli_set_process_title, arginfo_cli_set_process_title)
497 PHP_FE(cli_get_process_title, arginfo_cli_get_process_title)
498 PHP_FE(apache_request_headers, arginfo_apache_request_headers)
499 PHP_FE(apache_response_headers, arginfo_apache_response_headers)
500 PHP_FALIAS(getallheaders, apache_request_headers, arginfo_getallheaders)
501 PHP_FE_END
502 };
503
sapi_cli_server_startup(sapi_module_struct * sapi_module)504 static int sapi_cli_server_startup(sapi_module_struct *sapi_module) /* {{{ */
505 {
506 return php_module_startup(sapi_module, &cli_server_module_entry);
507 } /* }}} */
508
sapi_cli_server_ub_write(const char * str,size_t str_length)509 static size_t sapi_cli_server_ub_write(const char *str, size_t str_length) /* {{{ */
510 {
511 php_cli_server_client *client = SG(server_context);
512 if (!client) {
513 return 0;
514 }
515 return php_cli_server_client_send_through(client, str, str_length);
516 } /* }}} */
517
sapi_cli_server_flush(void * server_context)518 static void sapi_cli_server_flush(void *server_context) /* {{{ */
519 {
520 php_cli_server_client *client = server_context;
521
522 if (!client) {
523 return;
524 }
525
526 if (!ZEND_VALID_SOCKET(client->sock)) {
527 php_handle_aborted_connection();
528 return;
529 }
530
531 if (!SG(headers_sent)) {
532 sapi_send_headers();
533 SG(headers_sent) = 1;
534 }
535 } /* }}} */
536
sapi_cli_server_discard_headers(sapi_headers_struct * sapi_headers)537 static int sapi_cli_server_discard_headers(sapi_headers_struct *sapi_headers) /* {{{ */{
538 return SAPI_HEADER_SENT_SUCCESSFULLY;
539 }
540 /* }}} */
541
sapi_cli_server_send_headers(sapi_headers_struct * sapi_headers)542 static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers) /* {{{ */
543 {
544 php_cli_server_client *client = SG(server_context);
545 smart_str buffer = { 0 };
546 sapi_header_struct *h;
547 zend_llist_position pos;
548
549 if (client == NULL || SG(request_info).no_headers) {
550 return SAPI_HEADER_SENT_SUCCESSFULLY;
551 }
552
553 if (SG(sapi_headers).http_status_line) {
554 smart_str_appends(&buffer, SG(sapi_headers).http_status_line);
555 smart_str_appendl(&buffer, "\r\n", 2);
556 } else {
557 append_http_status_line(&buffer, client->request.protocol_version, SG(sapi_headers).http_response_code, 0);
558 }
559
560 append_essential_headers(&buffer, client, 0, sapi_headers);
561
562 h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
563 while (h) {
564 if (h->header_len) {
565 smart_str_appendl(&buffer, h->header, h->header_len);
566 smart_str_appendl(&buffer, "\r\n", 2);
567 }
568 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
569 }
570 smart_str_appendl(&buffer, "\r\n", 2);
571
572 php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
573
574 smart_str_free(&buffer);
575 return SAPI_HEADER_SENT_SUCCESSFULLY;
576 }
577 /* }}} */
578
sapi_cli_server_read_cookies(void)579 static char *sapi_cli_server_read_cookies(void) /* {{{ */
580 {
581 php_cli_server_client *client = SG(server_context);
582 zval *val;
583 if (NULL == (val = zend_hash_str_find(&client->request.headers, "cookie", sizeof("cookie")-1))) {
584 return NULL;
585 }
586 return Z_STRVAL_P(val);
587 } /* }}} */
588
sapi_cli_server_read_post(char * buf,size_t count_bytes)589 static size_t sapi_cli_server_read_post(char *buf, size_t count_bytes) /* {{{ */
590 {
591 php_cli_server_client *client = SG(server_context);
592 if (client->request.content) {
593 size_t content_len = client->request.content_len;
594 size_t nbytes_copied = MIN(client->post_read_offset + count_bytes, content_len) - client->post_read_offset;
595 memmove(buf, client->request.content + client->post_read_offset, nbytes_copied);
596 client->post_read_offset += nbytes_copied;
597 return nbytes_copied;
598 }
599 return 0;
600 } /* }}} */
601
sapi_cli_server_register_variable(zval * track_vars_array,const char * key,const char * val)602 static void sapi_cli_server_register_variable(zval *track_vars_array, const char *key, const char *val) /* {{{ */
603 {
604 char *new_val = (char *)val;
605 size_t new_val_len;
606
607 if (NULL == val) {
608 return;
609 }
610
611 if (sapi_module.input_filter(PARSE_SERVER, (char*)key, &new_val, strlen(val), &new_val_len)) {
612 php_register_variable_safe((char *)key, new_val, new_val_len, track_vars_array);
613 }
614 } /* }}} */
615
sapi_cli_server_register_known_var_char(zval * track_vars_array,const char * var_name,size_t var_name_len,const char * value,size_t value_len)616 static void sapi_cli_server_register_known_var_char(zval *track_vars_array,
617 const char *var_name, size_t var_name_len, const char *value, size_t value_len)
618 {
619 zval new_entry;
620
621 if (!value) {
622 return;
623 }
624
625 ZVAL_STRINGL_FAST(&new_entry, value, value_len);
626
627 php_register_known_variable(var_name, var_name_len, &new_entry, track_vars_array);
628 }
629
sapi_cli_server_register_known_var_str(zval * track_vars_array,const char * var_name,size_t var_name_len,zend_string * value)630 static void sapi_cli_server_register_known_var_str(zval *track_vars_array,
631 const char *var_name, size_t var_name_len, /* const */ zend_string *value)
632 {
633 zval new_entry;
634
635 if (!value) {
636 return;
637 }
638
639 ZVAL_STR_COPY(&new_entry, value);
640
641 php_register_known_variable(var_name, var_name_len, &new_entry, track_vars_array);
642 }
643
644 /* The entry zval will always contain a zend_string* */
sapi_cli_server_register_entry_cb(zval * entry,int num_args,va_list args,zend_hash_key * hash_key)645 static int sapi_cli_server_register_entry_cb(zval *entry, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ {
646 zval *track_vars_array = va_arg(args, zval *);
647
648 ZEND_ASSERT(Z_TYPE_P(entry) == IS_STRING);
649
650 if (hash_key->key) {
651 char *real_key, *key;
652 uint32_t i;
653 key = estrndup(ZSTR_VAL(hash_key->key), ZSTR_LEN(hash_key->key));
654 for(i=0; i<ZSTR_LEN(hash_key->key); i++) {
655 if (key[i] == '-') {
656 key[i] = '_';
657 } else {
658 key[i] = toupper(key[i]);
659 }
660 }
661 spprintf(&real_key, 0, "%s_%s", "HTTP", key);
662 if (strcmp(key, "CONTENT_TYPE") == 0 || strcmp(key, "CONTENT_LENGTH") == 0) {
663 /* Is it possible to use sapi_cli_server_register_known_var_char() and not go through the SAPI filter? */
664 sapi_cli_server_register_variable(track_vars_array, key, Z_STRVAL_P(entry));
665 }
666 sapi_cli_server_register_variable(track_vars_array, real_key, Z_STRVAL_P(entry));
667 efree(key);
668 efree(real_key);
669 }
670
671 return ZEND_HASH_APPLY_KEEP;
672 }
673 /* }}} */
674
sapi_cli_server_register_variables(zval * track_vars_array)675 static void sapi_cli_server_register_variables(zval *track_vars_array) /* {{{ */
676 {
677 php_cli_server_client *client = SG(server_context);
678
679 sapi_cli_server_register_known_var_char(track_vars_array,
680 "DOCUMENT_ROOT", strlen("DOCUMENT_ROOT"), client->server->document_root, client->server->document_root_len);
681 {
682 char *tmp;
683 if ((tmp = strrchr(ZSTR_VAL(client->addr_str), ':'))) {
684 char addr[64], port[8];
685 const char *addr_start = ZSTR_VAL(client->addr_str), *addr_end = tmp;
686 if (addr_start[0] == '[') addr_start++;
687 if (addr_end[-1] == ']') addr_end--;
688
689 strncpy(port, tmp + 1, 8);
690 port[7] = '\0';
691 size_t addr_len = addr_end - addr_start;
692 strncpy(addr, addr_start, addr_len);
693 addr[addr_len] = '\0';
694 ZEND_ASSERT(addr_len == strlen(addr));
695 sapi_cli_server_register_known_var_char(track_vars_array,
696 "REMOTE_ADDR", strlen("REMOTE_ADDR"), addr, addr_len);
697 sapi_cli_server_register_known_var_char(track_vars_array,
698 "REMOTE_PORT", strlen("REMOTE_PORT"), port, strlen(port));
699 } else {
700 sapi_cli_server_register_known_var_str(track_vars_array,
701 "REMOTE_ADDR", strlen("REMOTE_ADDR"), client->addr_str);
702 }
703 }
704 {
705 zend_string *tmp = strpprintf(0, "PHP %s Development Server", PHP_VERSION);
706 sapi_cli_server_register_known_var_str(track_vars_array, "SERVER_SOFTWARE", strlen("SERVER_SOFTWARE"), tmp);
707 zend_string_release_ex(tmp, /* persistent */ false);
708 }
709 {
710 zend_string *tmp = strpprintf(0, "HTTP/%d.%d", client->request.protocol_version / 100, client->request.protocol_version % 100);
711 sapi_cli_server_register_known_var_str(track_vars_array, "SERVER_PROTOCOL", strlen("SERVER_PROTOCOL"), tmp);
712 zend_string_release_ex(tmp, /* persistent */ false);
713 }
714 sapi_cli_server_register_known_var_char(track_vars_array,
715 "SERVER_NAME", strlen("SERVER_NAME"), client->server->host, strlen(client->server->host));
716 {
717 zend_string *tmp = strpprintf(0, "%i", client->server->port);
718 sapi_cli_server_register_known_var_str(track_vars_array, "SERVER_PORT", strlen("SERVER_PORT"), tmp);
719 zend_string_release_ex(tmp, /* persistent */ false);
720 }
721
722 sapi_cli_server_register_known_var_str(track_vars_array,
723 "REQUEST_URI", strlen("REQUEST_URI"), client->request.request_uri);
724 sapi_cli_server_register_known_var_char(track_vars_array,
725 "REQUEST_METHOD", strlen("REQUEST_METHOD"),
726 SG(request_info).request_method, strlen(SG(request_info).request_method));
727 sapi_cli_server_register_known_var_char(track_vars_array,
728 "SCRIPT_NAME", strlen("SCRIPT_NAME"), client->request.vpath, client->request.vpath_len);
729 if (SG(request_info).path_translated) {
730 sapi_cli_server_register_known_var_char(track_vars_array,
731 "SCRIPT_FILENAME", strlen("SCRIPT_FILENAME"),
732 SG(request_info).path_translated, strlen(SG(request_info).path_translated));
733 } else if (client->server->router) {
734 sapi_cli_server_register_known_var_char(track_vars_array,
735 "SCRIPT_FILENAME", strlen("SCRIPT_FILENAME"), client->server->router, client->server->router_len);
736 }
737 if (client->request.path_info) {
738 sapi_cli_server_register_known_var_char(track_vars_array,
739 "PATH_INFO", strlen("PATH_INFO"), client->request.path_info, client->request.path_info_len);
740 }
741 if (client->request.path_info_len) {
742 zend_string *tmp = strpprintf(0, "%s%s", client->request.vpath, client->request.path_info);
743 sapi_cli_server_register_known_var_str(track_vars_array, "PHP_SELF", strlen("PHP_SELF"), tmp);
744 zend_string_release_ex(tmp, /* persistent */ false);
745 } else {
746 sapi_cli_server_register_known_var_char(track_vars_array,
747 "PHP_SELF", strlen("PHP_SELF"), client->request.vpath, client->request.vpath_len);
748 }
749 if (client->request.query_string) {
750 /* Use sapi_cli_server_register_variable() to pass query string through SAPI input filter,
751 * and check keys are proper PHP var names */
752 sapi_cli_server_register_variable(track_vars_array, "QUERY_STRING", client->request.query_string);
753 }
754 /* Use sapi_cli_server_register_variable() to pass header values through SAPI input filter,
755 * and check keys are proper PHP var names */
756 zend_hash_apply_with_arguments(&client->request.headers, (apply_func_args_t)sapi_cli_server_register_entry_cb, 1, track_vars_array);
757 } /* }}} */
758
sapi_cli_server_log_write(int type,const char * msg)759 static void sapi_cli_server_log_write(int type, const char *msg) /* {{{ */
760 {
761 char buf[52];
762
763 if (php_cli_server_log_level < type) {
764 return;
765 }
766
767 if (!php_cli_server_get_system_time(buf)) {
768 memmove(buf, "unknown time, can't be fetched", sizeof("unknown time, can't be fetched"));
769 } else {
770 size_t l = strlen(buf);
771 if (l > 0) {
772 buf[l - 1] = '\0';
773 } else {
774 memmove(buf, "unknown", sizeof("unknown"));
775 }
776 }
777 #ifdef HAVE_FORK
778 if (php_cli_server_workers_max > 1) {
779 fprintf(stderr, "[%ld] [%s] %s\n", (long) getpid(), buf, msg);
780 } else {
781 fprintf(stderr, "[%s] %s\n", buf, msg);
782 }
783 #else
784 fprintf(stderr, "[%s] %s\n", buf, msg);
785 #endif
786 } /* }}} */
787
sapi_cli_server_log_message(const char * msg,int syslog_type_int)788 static void sapi_cli_server_log_message(const char *msg, int syslog_type_int) /* {{{ */
789 {
790 sapi_cli_server_log_write(PHP_CLI_SERVER_LOG_MESSAGE, msg);
791 } /* }}} */
792
793 /* {{{ sapi_module_struct cli_server_sapi_module */
794 sapi_module_struct cli_server_sapi_module = {
795 "cli-server", /* name */
796 "Built-in HTTP server", /* pretty name */
797
798 sapi_cli_server_startup, /* startup */
799 php_module_shutdown_wrapper, /* shutdown */
800
801 NULL, /* activate */
802 NULL, /* deactivate */
803
804 sapi_cli_server_ub_write, /* unbuffered write */
805 sapi_cli_server_flush, /* flush */
806 NULL, /* get uid */
807 NULL, /* getenv */
808
809 php_error, /* error handler */
810
811 NULL, /* header handler */
812 sapi_cli_server_send_headers, /* send headers handler */
813 NULL, /* send header handler */
814
815 sapi_cli_server_read_post, /* read POST data */
816 sapi_cli_server_read_cookies, /* read Cookies */
817
818 sapi_cli_server_register_variables, /* register server variables */
819 sapi_cli_server_log_message, /* Log message */
820 NULL, /* Get request time */
821 NULL, /* Child terminate */
822
823 STANDARD_SAPI_MODULE_PROPERTIES
824 }; /* }}} */
825
php_cli_server_poller_ctor(php_cli_server_poller * poller)826 static void php_cli_server_poller_ctor(php_cli_server_poller *poller) /* {{{ */
827 {
828 FD_ZERO(&poller->rfds);
829 FD_ZERO(&poller->wfds);
830 poller->max_fd = -1;
831 } /* }}} */
832
php_cli_server_poller_add(php_cli_server_poller * poller,int mode,php_socket_t fd)833 static void php_cli_server_poller_add(php_cli_server_poller *poller, int mode, php_socket_t fd) /* {{{ */
834 {
835 if (mode & POLLIN) {
836 PHP_SAFE_FD_SET(fd, &poller->rfds);
837 }
838 if (mode & POLLOUT) {
839 PHP_SAFE_FD_SET(fd, &poller->wfds);
840 }
841 if (fd > poller->max_fd) {
842 poller->max_fd = fd;
843 }
844 } /* }}} */
845
php_cli_server_poller_remove(php_cli_server_poller * poller,int mode,php_socket_t fd)846 static void php_cli_server_poller_remove(php_cli_server_poller *poller, int mode, php_socket_t fd) /* {{{ */
847 {
848 if (mode & POLLIN) {
849 PHP_SAFE_FD_CLR(fd, &poller->rfds);
850 }
851 if (mode & POLLOUT) {
852 PHP_SAFE_FD_CLR(fd, &poller->wfds);
853 }
854 #ifndef PHP_WIN32
855 if (fd == poller->max_fd) {
856 while (fd > 0) {
857 fd--;
858 if (PHP_SAFE_FD_ISSET(fd, &poller->rfds) || PHP_SAFE_FD_ISSET(fd, &poller->wfds)) {
859 break;
860 }
861 }
862 poller->max_fd = fd;
863 }
864 #endif
865 } /* }}} */
866
php_cli_server_poller_poll(php_cli_server_poller * poller,struct timeval * tv)867 static int php_cli_server_poller_poll(php_cli_server_poller *poller, struct timeval *tv) /* {{{ */
868 {
869 memmove(&poller->active.rfds, &poller->rfds, sizeof(poller->rfds));
870 memmove(&poller->active.wfds, &poller->wfds, sizeof(poller->wfds));
871 return php_select(poller->max_fd + 1, &poller->active.rfds, &poller->active.wfds, NULL, tv);
872 } /* }}} */
873
874 // TODO Return value is unused, refactor?
php_cli_server_poller_iter_on_active(php_cli_server_poller * poller,void * opaque,zend_result (* callback)(void *,php_socket_t fd,int events))875 static zend_result php_cli_server_poller_iter_on_active(php_cli_server_poller *poller, void *opaque, zend_result(*callback)(void *, php_socket_t fd, int events)) /* {{{ */
876 {
877 zend_result retval = SUCCESS;
878 #ifdef PHP_WIN32
879 struct socket_entry {
880 SOCKET fd;
881 int events;
882 } entries[FD_SETSIZE * 2];
883 size_t i;
884 struct socket_entry *n = entries, *m;
885
886 for (i = 0; i < poller->active.rfds.fd_count; i++) {
887 n->events = POLLIN;
888 n->fd = poller->active.rfds.fd_array[i];
889 n++;
890 }
891
892 m = n;
893 for (i = 0; i < poller->active.wfds.fd_count; i++) {
894 struct socket_entry *e;
895 SOCKET fd = poller->active.wfds.fd_array[i];
896 for (e = entries; e < m; e++) {
897 if (e->fd == fd) {
898 e->events |= POLLOUT;
899 }
900 }
901 if (e == m) {
902 assert(n < entries + FD_SETSIZE * 2);
903 n->events = POLLOUT;
904 n->fd = fd;
905 n++;
906 }
907 }
908
909 {
910 struct socket_entry *e = entries;
911 for (; e < n; e++) {
912 if (SUCCESS != callback(opaque, e->fd, e->events)) {
913 retval = FAILURE;
914 }
915 }
916 }
917
918 #else
919 php_socket_t fd;
920 const php_socket_t max_fd = poller->max_fd;
921
922 for (fd=0 ; fd<=max_fd ; fd++) {
923 if (PHP_SAFE_FD_ISSET(fd, &poller->active.rfds)) {
924 if (SUCCESS != callback(opaque, fd, POLLIN)) {
925 retval = FAILURE;
926 }
927 }
928 if (PHP_SAFE_FD_ISSET(fd, &poller->active.wfds)) {
929 if (SUCCESS != callback(opaque, fd, POLLOUT)) {
930 retval = FAILURE;
931 }
932 }
933 }
934 #endif
935 return retval;
936 } /* }}} */
937
php_cli_server_chunk_size(const php_cli_server_chunk * chunk)938 static size_t php_cli_server_chunk_size(const php_cli_server_chunk *chunk) /* {{{ */
939 {
940 switch (chunk->type) {
941 case PHP_CLI_SERVER_CHUNK_HEAP:
942 return chunk->data.heap.len;
943 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
944 return chunk->data.immortal.len;
945 }
946 return 0;
947 } /* }}} */
948
php_cli_server_chunk_dtor(php_cli_server_chunk * chunk)949 static void php_cli_server_chunk_dtor(php_cli_server_chunk *chunk) /* {{{ */
950 {
951 switch (chunk->type) {
952 case PHP_CLI_SERVER_CHUNK_HEAP:
953 if (chunk->data.heap.block != chunk) {
954 pefree(chunk->data.heap.block, 1);
955 }
956 break;
957 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
958 break;
959 }
960 } /* }}} */
961
php_cli_server_buffer_dtor(php_cli_server_buffer * buffer)962 static void php_cli_server_buffer_dtor(php_cli_server_buffer *buffer) /* {{{ */
963 {
964 php_cli_server_chunk *chunk, *next;
965 for (chunk = buffer->first; chunk; chunk = next) {
966 next = chunk->next;
967 php_cli_server_chunk_dtor(chunk);
968 pefree(chunk, 1);
969 }
970 } /* }}} */
971
php_cli_server_buffer_ctor(php_cli_server_buffer * buffer)972 static void php_cli_server_buffer_ctor(php_cli_server_buffer *buffer) /* {{{ */
973 {
974 buffer->first = NULL;
975 buffer->last = NULL;
976 } /* }}} */
977
php_cli_server_buffer_append(php_cli_server_buffer * buffer,php_cli_server_chunk * chunk)978 static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */
979 {
980 php_cli_server_chunk *last;
981 for (last = chunk; last->next; last = last->next);
982 if (!buffer->last) {
983 buffer->first = chunk;
984 } else {
985 buffer->last->next = chunk;
986 }
987 buffer->last = last;
988 } /* }}} */
989
php_cli_server_buffer_prepend(php_cli_server_buffer * buffer,php_cli_server_chunk * chunk)990 static void php_cli_server_buffer_prepend(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */
991 {
992 php_cli_server_chunk *last;
993 for (last = chunk; last->next; last = last->next);
994 last->next = buffer->first;
995 if (!buffer->last) {
996 buffer->last = last;
997 }
998 buffer->first = chunk;
999 } /* }}} */
1000
php_cli_server_buffer_size(const php_cli_server_buffer * buffer)1001 static size_t php_cli_server_buffer_size(const php_cli_server_buffer *buffer) /* {{{ */
1002 {
1003 php_cli_server_chunk *chunk;
1004 size_t retval = 0;
1005 for (chunk = buffer->first; chunk; chunk = chunk->next) {
1006 retval += php_cli_server_chunk_size(chunk);
1007 }
1008 return retval;
1009 } /* }}} */
1010
php_cli_server_chunk_immortal_new(const char * buf,size_t len)1011 static php_cli_server_chunk *php_cli_server_chunk_immortal_new(const char *buf, size_t len) /* {{{ */
1012 {
1013 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1);
1014
1015 chunk->type = PHP_CLI_SERVER_CHUNK_IMMORTAL;
1016 chunk->next = NULL;
1017 chunk->data.immortal.p = buf;
1018 chunk->data.immortal.len = len;
1019 return chunk;
1020 } /* }}} */
1021
php_cli_server_chunk_heap_new(void * block,char * buf,size_t len)1022 static php_cli_server_chunk *php_cli_server_chunk_heap_new(void *block, char *buf, size_t len) /* {{{ */
1023 {
1024 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1);
1025
1026 chunk->type = PHP_CLI_SERVER_CHUNK_HEAP;
1027 chunk->next = NULL;
1028 chunk->data.heap.block = block;
1029 chunk->data.heap.p = buf;
1030 chunk->data.heap.len = len;
1031 return chunk;
1032 } /* }}} */
1033
php_cli_server_chunk_heap_new_self_contained(size_t len)1034 static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len) /* {{{ */
1035 {
1036 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk) + len, 1);
1037
1038 chunk->type = PHP_CLI_SERVER_CHUNK_HEAP;
1039 chunk->next = NULL;
1040 chunk->data.heap.block = chunk;
1041 chunk->data.heap.p = (char *)(chunk + 1);
1042 chunk->data.heap.len = len;
1043 return chunk;
1044 } /* }}} */
1045
php_cli_server_content_sender_dtor(php_cli_server_content_sender * sender)1046 static void php_cli_server_content_sender_dtor(php_cli_server_content_sender *sender) /* {{{ */
1047 {
1048 php_cli_server_buffer_dtor(&sender->buffer);
1049 } /* }}} */
1050
php_cli_server_content_sender_ctor(php_cli_server_content_sender * sender)1051 static void php_cli_server_content_sender_ctor(php_cli_server_content_sender *sender) /* {{{ */
1052 {
1053 php_cli_server_buffer_ctor(&sender->buffer);
1054 } /* }}} */
1055
php_cli_server_content_sender_send(php_cli_server_content_sender * sender,php_socket_t fd,size_t * nbytes_sent_total)1056 static int php_cli_server_content_sender_send(php_cli_server_content_sender *sender, php_socket_t fd, size_t *nbytes_sent_total) /* {{{ */
1057 {
1058 php_cli_server_chunk *chunk, *next;
1059 size_t _nbytes_sent_total = 0;
1060
1061 for (chunk = sender->buffer.first; chunk; chunk = next) {
1062 #ifdef PHP_WIN32
1063 int nbytes_sent;
1064 #else
1065 ssize_t nbytes_sent;
1066 #endif
1067 next = chunk->next;
1068
1069 switch (chunk->type) {
1070 case PHP_CLI_SERVER_CHUNK_HEAP:
1071 #ifdef PHP_WIN32
1072 nbytes_sent = send(fd, chunk->data.heap.p, (int)chunk->data.heap.len, 0);
1073 #else
1074 nbytes_sent = send(fd, chunk->data.heap.p, chunk->data.heap.len, 0);
1075 #endif
1076 if (nbytes_sent < 0) {
1077 *nbytes_sent_total = _nbytes_sent_total;
1078 return php_socket_errno();
1079 #ifdef PHP_WIN32
1080 } else if (nbytes_sent == chunk->data.heap.len) {
1081 #else
1082 } else if (nbytes_sent == (ssize_t)chunk->data.heap.len) {
1083 #endif
1084 php_cli_server_chunk_dtor(chunk);
1085 pefree(chunk, 1);
1086 sender->buffer.first = next;
1087 if (!next) {
1088 sender->buffer.last = NULL;
1089 }
1090 } else {
1091 chunk->data.heap.p += nbytes_sent;
1092 chunk->data.heap.len -= nbytes_sent;
1093 }
1094 _nbytes_sent_total += nbytes_sent;
1095 break;
1096
1097 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
1098 #ifdef PHP_WIN32
1099 nbytes_sent = send(fd, chunk->data.immortal.p, (int)chunk->data.immortal.len, 0);
1100 #else
1101 nbytes_sent = send(fd, chunk->data.immortal.p, chunk->data.immortal.len, 0);
1102 #endif
1103 if (nbytes_sent < 0) {
1104 *nbytes_sent_total = _nbytes_sent_total;
1105 return php_socket_errno();
1106 #ifdef PHP_WIN32
1107 } else if (nbytes_sent == chunk->data.immortal.len) {
1108 #else
1109 } else if (nbytes_sent == (ssize_t)chunk->data.immortal.len) {
1110 #endif
1111 php_cli_server_chunk_dtor(chunk);
1112 pefree(chunk, 1);
1113 sender->buffer.first = next;
1114 if (!next) {
1115 sender->buffer.last = NULL;
1116 }
1117 } else {
1118 chunk->data.immortal.p += nbytes_sent;
1119 chunk->data.immortal.len -= nbytes_sent;
1120 }
1121 _nbytes_sent_total += nbytes_sent;
1122 break;
1123 }
1124 }
1125 *nbytes_sent_total = _nbytes_sent_total;
1126 return 0;
1127 } /* }}} */
1128
php_cli_server_content_sender_pull(php_cli_server_content_sender * sender,int fd,size_t * nbytes_read)1129 static bool php_cli_server_content_sender_pull(php_cli_server_content_sender *sender, int fd, size_t *nbytes_read) /* {{{ */
1130 {
1131 #ifdef PHP_WIN32
1132 int _nbytes_read;
1133 #else
1134 ssize_t _nbytes_read;
1135 #endif
1136 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(131072);
1137
1138 #ifdef PHP_WIN32
1139 _nbytes_read = read(fd, chunk->data.heap.p, (unsigned int)chunk->data.heap.len);
1140 #else
1141 _nbytes_read = read(fd, chunk->data.heap.p, chunk->data.heap.len);
1142 #endif
1143 if (_nbytes_read < 0) {
1144 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
1145 char *errstr = get_last_error();
1146 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s", errstr);
1147 pefree(errstr, 1);
1148 }
1149 php_cli_server_chunk_dtor(chunk);
1150 pefree(chunk, 1);
1151 return false;
1152 }
1153 chunk->data.heap.len = _nbytes_read;
1154 php_cli_server_buffer_append(&sender->buffer, chunk);
1155 *nbytes_read = _nbytes_read;
1156 return true;
1157 } /* }}} */
1158
1159 #if HAVE_UNISTD_H
php_cli_is_output_tty(void)1160 static int php_cli_is_output_tty(void) /* {{{ */
1161 {
1162 if (php_cli_output_is_tty == OUTPUT_NOT_CHECKED) {
1163 php_cli_output_is_tty = isatty(STDOUT_FILENO);
1164 }
1165 return php_cli_output_is_tty;
1166 } /* }}} */
1167 #elif defined(PHP_WIN32)
php_cli_is_output_tty()1168 static int php_cli_is_output_tty() /* {{{ */
1169 {
1170 if (php_cli_output_is_tty == OUTPUT_NOT_CHECKED) {
1171 php_cli_output_is_tty = php_win32_console_fileno_is_console(STDOUT_FILENO) && php_win32_console_fileno_has_vt100(STDOUT_FILENO);
1172 }
1173 return php_cli_output_is_tty;
1174 } /* }}} */
1175 #endif
1176
php_cli_server_log_response(php_cli_server_client * client,int status,const char * message)1177 static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message) /* {{{ */
1178 {
1179 int color = 0, effective_status = status;
1180 char *basic_buf, *message_buf = "", *error_buf = "";
1181 bool append_error_message = 0;
1182
1183 if (PG(last_error_message)) {
1184 if (PG(last_error_type) & E_FATAL_ERRORS) {
1185 if (status == 200) {
1186 /* the status code isn't changed by a fatal error, so fake it */
1187 effective_status = 500;
1188 }
1189
1190 append_error_message = 1;
1191 }
1192 }
1193
1194 #if HAVE_UNISTD_H || defined(PHP_WIN32)
1195 if (CLI_SERVER_G(color) && php_cli_is_output_tty() == OUTPUT_IS_TTY) {
1196 if (effective_status >= 500) {
1197 /* server error: red */
1198 color = 1;
1199 } else if (effective_status >= 400) {
1200 /* client error: yellow */
1201 color = 3;
1202 } else if (effective_status >= 200) {
1203 /* success: green */
1204 color = 2;
1205 }
1206 }
1207 #endif
1208
1209 /* basic */
1210 spprintf(&basic_buf, 0, "%s [%d]: %s %s", ZSTR_VAL(client->addr_str), status,
1211 php_http_method_str(client->request.request_method), ZSTR_VAL(client->request.request_uri));
1212 if (!basic_buf) {
1213 return;
1214 }
1215
1216 /* message */
1217 if (message) {
1218 spprintf(&message_buf, 0, " - %s", message);
1219 if (!message_buf) {
1220 efree(basic_buf);
1221 return;
1222 }
1223 }
1224
1225 /* error */
1226 if (append_error_message) {
1227 spprintf(&error_buf, 0, " - %s in %s on line %d",
1228 ZSTR_VAL(PG(last_error_message)), ZSTR_VAL(PG(last_error_file)), PG(last_error_lineno));
1229 if (!error_buf) {
1230 efree(basic_buf);
1231 if (message) {
1232 efree(message_buf);
1233 }
1234 return;
1235 }
1236 }
1237
1238 if (color) {
1239 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "\x1b[3%dm%s%s%s\x1b[0m", color, basic_buf, message_buf, error_buf);
1240 } else {
1241 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "%s%s%s", basic_buf, message_buf, error_buf);
1242 }
1243
1244 efree(basic_buf);
1245 if (message) {
1246 efree(message_buf);
1247 }
1248 if (append_error_message) {
1249 efree(error_buf);
1250 }
1251 } /* }}} */
1252
php_cli_server_logf(int type,const char * format,...)1253 static void php_cli_server_logf(int type, const char *format, ...) /* {{{ */
1254 {
1255 char *buf = NULL;
1256 va_list ap;
1257
1258 if (php_cli_server_log_level < type) {
1259 return;
1260 }
1261
1262 va_start(ap, format);
1263 vspprintf(&buf, 0, format, ap);
1264 va_end(ap);
1265
1266 if (!buf) {
1267 return;
1268 }
1269
1270 sapi_cli_server_log_write(type, buf);
1271
1272 efree(buf);
1273 } /* }}} */
1274
php_network_listen_socket(const char * host,int * port,int socktype,int * af,socklen_t * socklen,zend_string ** errstr)1275 static php_socket_t php_network_listen_socket(const char *host, int *port, int socktype, int *af, socklen_t *socklen, zend_string **errstr) /* {{{ */
1276 {
1277 php_socket_t retval = SOCK_ERR;
1278 int err = 0;
1279 struct sockaddr *sa = NULL, **p, **sal;
1280
1281 int num_addrs = php_network_getaddresses(host, socktype, &sal, errstr);
1282 if (num_addrs == 0) {
1283 return -1;
1284 }
1285 for (p = sal; *p; p++) {
1286 if (sa) {
1287 pefree(sa, 1);
1288 sa = NULL;
1289 }
1290
1291 retval = socket((*p)->sa_family, socktype, 0);
1292 if (retval == SOCK_ERR) {
1293 continue;
1294 }
1295
1296 switch ((*p)->sa_family) {
1297 #if HAVE_GETADDRINFO && HAVE_IPV6
1298 case AF_INET6:
1299 sa = pemalloc(sizeof(struct sockaddr_in6), 1);
1300 *(struct sockaddr_in6 *)sa = *(struct sockaddr_in6 *)*p;
1301 ((struct sockaddr_in6 *)sa)->sin6_port = htons(*port);
1302 *socklen = sizeof(struct sockaddr_in6);
1303 break;
1304 #endif
1305 case AF_INET:
1306 sa = pemalloc(sizeof(struct sockaddr_in), 1);
1307 *(struct sockaddr_in *)sa = *(struct sockaddr_in *)*p;
1308 ((struct sockaddr_in *)sa)->sin_port = htons(*port);
1309 *socklen = sizeof(struct sockaddr_in);
1310 break;
1311 default:
1312 /* Unknown family */
1313 *socklen = 0;
1314 closesocket(retval);
1315 continue;
1316 }
1317
1318 #ifdef SO_REUSEADDR
1319 {
1320 int val = 1;
1321 setsockopt(retval, SOL_SOCKET, SO_REUSEADDR, (char*)&val, sizeof(val));
1322 }
1323 #endif
1324
1325 if (bind(retval, sa, *socklen) == SOCK_CONN_ERR) {
1326 err = php_socket_errno();
1327 if (err == SOCK_EINVAL || err == SOCK_EADDRINUSE) {
1328 goto out;
1329 }
1330 closesocket(retval);
1331 retval = SOCK_ERR;
1332 continue;
1333 }
1334 err = 0;
1335
1336 *af = sa->sa_family;
1337 if (*port == 0) {
1338 if (getsockname(retval, sa, socklen)) {
1339 err = php_socket_errno();
1340 goto out;
1341 }
1342 switch (sa->sa_family) {
1343 #if HAVE_GETADDRINFO && HAVE_IPV6
1344 case AF_INET6:
1345 *port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
1346 break;
1347 #endif
1348 case AF_INET:
1349 *port = ntohs(((struct sockaddr_in *)sa)->sin_port);
1350 break;
1351 }
1352 }
1353
1354 break;
1355 }
1356
1357 if (retval == SOCK_ERR) {
1358 goto out;
1359 }
1360
1361 if (listen(retval, SOMAXCONN)) {
1362 err = php_socket_errno();
1363 goto out;
1364 }
1365
1366 out:
1367 if (sa) {
1368 pefree(sa, 1);
1369 }
1370 if (sal) {
1371 php_network_freeaddresses(sal);
1372 }
1373 if (err) {
1374 if (ZEND_VALID_SOCKET(retval)) {
1375 closesocket(retval);
1376 }
1377 if (errstr) {
1378 *errstr = php_socket_error_str(err);
1379 }
1380 return SOCK_ERR;
1381 }
1382 return retval;
1383 } /* }}} */
1384
php_cli_server_request_ctor(php_cli_server_request * req)1385 static void php_cli_server_request_ctor(php_cli_server_request *req) /* {{{ */
1386 {
1387 req->protocol_version = 0;
1388 req->request_uri = NULL;
1389 req->vpath = NULL;
1390 req->vpath_len = 0;
1391 req->path_translated = NULL;
1392 req->path_translated_len = 0;
1393 req->path_info = NULL;
1394 req->path_info_len = 0;
1395 req->query_string = NULL;
1396 req->query_string_len = 0;
1397 zend_hash_init(&req->headers, 0, NULL, cli_header_value_dtor, 1);
1398 /* No destructor is registered as the value pointed by is the same as for &req->headers */
1399 GC_MAKE_PERSISTENT_LOCAL(&req->headers);
1400 zend_hash_init(&req->headers_original_case, 0, NULL, NULL, 1);
1401 GC_MAKE_PERSISTENT_LOCAL(&req->headers_original_case);
1402 req->content = NULL;
1403 req->content_len = 0;
1404 req->ext = NULL;
1405 req->ext_len = 0;
1406 } /* }}} */
1407
php_cli_server_request_dtor(php_cli_server_request * req)1408 static void php_cli_server_request_dtor(php_cli_server_request *req) /* {{{ */
1409 {
1410 if (req->request_uri) {
1411 zend_string_release_ex(req->request_uri, /* persistent */ true);
1412 }
1413 if (req->vpath) {
1414 pefree(req->vpath, 1);
1415 }
1416 if (req->path_translated) {
1417 pefree(req->path_translated, 1);
1418 }
1419 if (req->path_info) {
1420 pefree(req->path_info, 1);
1421 }
1422 if (req->query_string) {
1423 pefree(req->query_string, 1);
1424 }
1425 zend_hash_destroy(&req->headers);
1426 zend_hash_destroy(&req->headers_original_case);
1427 if (req->content) {
1428 pefree(req->content, 1);
1429 }
1430 } /* }}} */
1431
php_cli_server_request_translate_vpath(php_cli_server_request * request,const char * document_root,size_t document_root_len)1432 static void php_cli_server_request_translate_vpath(php_cli_server_request *request, const char *document_root, size_t document_root_len) /* {{{ */
1433 {
1434 zend_stat_t sb = {0};
1435 static const char *index_files[] = { "index.php", "index.html", NULL };
1436 char *buf = safe_pemalloc(1, request->vpath_len, 1 + document_root_len + 1 + sizeof("index.html"), 1);
1437 char *p = buf, *prev_path = NULL, *q, *vpath;
1438 size_t prev_path_len = 0;
1439 int is_static_file = 0;
1440
1441 memmove(p, document_root, document_root_len);
1442 p += document_root_len;
1443 vpath = p;
1444 if (request->vpath_len != 0) {
1445 if (request->vpath[0] != '/') {
1446 *p++ = DEFAULT_SLASH;
1447 }
1448 q = request->vpath + request->vpath_len;
1449 while (q > request->vpath) {
1450 if (*q-- == '.') {
1451 is_static_file = 1;
1452 break;
1453 }
1454 }
1455 memmove(p, request->vpath, request->vpath_len);
1456 #ifdef PHP_WIN32
1457 q = p + request->vpath_len;
1458 do {
1459 if (*q == '/') {
1460 *q = '\\';
1461 }
1462 } while (q-- > p);
1463 #endif
1464 p += request->vpath_len;
1465 }
1466 *p = '\0';
1467 q = p;
1468 while (q > buf) {
1469 if (!php_sys_stat(buf, &sb)) {
1470 if (sb.st_mode & S_IFDIR) {
1471 const char **file = index_files;
1472 if (q[-1] != DEFAULT_SLASH) {
1473 *q++ = DEFAULT_SLASH;
1474 }
1475 while (*file) {
1476 size_t l = strlen(*file);
1477 memmove(q, *file, l + 1);
1478 if (!php_sys_stat(buf, &sb) && (sb.st_mode & S_IFREG)) {
1479 q += l;
1480 break;
1481 }
1482 file++;
1483 }
1484 if (!*file || is_static_file) {
1485 if (prev_path) {
1486 pefree(prev_path, 1);
1487 }
1488 pefree(buf, 1);
1489 return;
1490 }
1491 }
1492 break; /* regular file */
1493 }
1494 if (prev_path) {
1495 pefree(prev_path, 1);
1496 *q = DEFAULT_SLASH;
1497 }
1498 while (q > buf && *(--q) != DEFAULT_SLASH);
1499 prev_path_len = p - q;
1500 prev_path = pestrndup(q, prev_path_len, 1);
1501 *q = '\0';
1502 }
1503 if (prev_path) {
1504 request->path_info_len = prev_path_len;
1505 #ifdef PHP_WIN32
1506 while (prev_path_len--) {
1507 if (prev_path[prev_path_len] == '\\') {
1508 prev_path[prev_path_len] = '/';
1509 }
1510 }
1511 #endif
1512 request->path_info = prev_path;
1513 pefree(request->vpath, 1);
1514 request->vpath = pestrndup(vpath, q - vpath, 1);
1515 request->vpath_len = q - vpath;
1516 request->path_translated = buf;
1517 request->path_translated_len = q - buf;
1518 } else {
1519 pefree(request->vpath, 1);
1520 request->vpath = pestrndup(vpath, q - vpath, 1);
1521 request->vpath_len = q - vpath;
1522 request->path_translated = buf;
1523 request->path_translated_len = q - buf;
1524 }
1525 #ifdef PHP_WIN32
1526 {
1527 uint32_t i = 0;
1528 for (;i<request->vpath_len;i++) {
1529 if (request->vpath[i] == '\\') {
1530 request->vpath[i] = '/';
1531 }
1532 }
1533 }
1534 #endif
1535 request->sb = sb;
1536 } /* }}} */
1537
normalize_vpath(char ** retval,size_t * retval_len,const char * vpath,size_t vpath_len,int persistent)1538 static void normalize_vpath(char **retval, size_t *retval_len, const char *vpath, size_t vpath_len, int persistent) /* {{{ */
1539 {
1540 char *decoded_vpath = NULL;
1541 char *decoded_vpath_end;
1542 char *p;
1543
1544 *retval = NULL;
1545 *retval_len = 0;
1546
1547 decoded_vpath = pestrndup(vpath, vpath_len, persistent);
1548 if (!decoded_vpath) {
1549 return;
1550 }
1551
1552 decoded_vpath_end = decoded_vpath + php_raw_url_decode(decoded_vpath, (int)vpath_len);
1553
1554 #ifdef PHP_WIN32
1555 {
1556 char *p = decoded_vpath;
1557
1558 do {
1559 if (*p == '\\') {
1560 *p = '/';
1561 }
1562 } while (*p++);
1563 }
1564 #endif
1565
1566 p = decoded_vpath;
1567
1568 if (p < decoded_vpath_end && *p == '/') {
1569 char *n = p;
1570 while (n < decoded_vpath_end && *n == '/') n++;
1571 memmove(++p, n, decoded_vpath_end - n);
1572 decoded_vpath_end -= n - p;
1573 }
1574
1575 while (p < decoded_vpath_end) {
1576 char *n = p;
1577 while (n < decoded_vpath_end && *n != '/') n++;
1578 if (n - p == 2 && p[0] == '.' && p[1] == '.') {
1579 if (p > decoded_vpath) {
1580 --p;
1581 for (;;) {
1582 if (p == decoded_vpath) {
1583 if (*p == '/') {
1584 p++;
1585 }
1586 break;
1587 }
1588 if (*(--p) == '/') {
1589 p++;
1590 break;
1591 }
1592 }
1593 }
1594 while (n < decoded_vpath_end && *n == '/') n++;
1595 memmove(p, n, decoded_vpath_end - n);
1596 decoded_vpath_end -= n - p;
1597 } else if (n - p == 1 && p[0] == '.') {
1598 while (n < decoded_vpath_end && *n == '/') n++;
1599 memmove(p, n, decoded_vpath_end - n);
1600 decoded_vpath_end -= n - p;
1601 } else {
1602 if (n < decoded_vpath_end) {
1603 char *nn = n;
1604 while (nn < decoded_vpath_end && *nn == '/') nn++;
1605 p = n + 1;
1606 memmove(p, nn, decoded_vpath_end - nn);
1607 decoded_vpath_end -= nn - p;
1608 } else {
1609 p = n;
1610 }
1611 }
1612 }
1613
1614 *decoded_vpath_end = '\0';
1615 *retval = decoded_vpath;
1616 *retval_len = decoded_vpath_end - decoded_vpath;
1617 } /* }}} */
1618
1619 // TODO Update these functions and php_http_parser.h
1620 /* {{{ php_cli_server_client_read_request */
php_cli_server_client_read_request_on_message_begin(php_http_parser * parser)1621 static int php_cli_server_client_read_request_on_message_begin(php_http_parser *parser)
1622 {
1623 return 0;
1624 }
1625
php_cli_server_client_read_request_on_path(php_http_parser * parser,const char * at,size_t length)1626 static int php_cli_server_client_read_request_on_path(php_http_parser *parser, const char *at, size_t length)
1627 {
1628 php_cli_server_client *client = parser->data;
1629 {
1630 char *vpath;
1631 size_t vpath_len;
1632 if (UNEXPECTED(client->request.vpath != NULL)) {
1633 return 1;
1634 }
1635 normalize_vpath(&vpath, &vpath_len, at, length, 1);
1636 client->request.vpath = vpath;
1637 client->request.vpath_len = vpath_len;
1638 }
1639 return 0;
1640 }
1641
php_cli_server_client_read_request_on_query_string(php_http_parser * parser,const char * at,size_t length)1642 static int php_cli_server_client_read_request_on_query_string(php_http_parser *parser, const char *at, size_t length)
1643 {
1644 php_cli_server_client *client = parser->data;
1645 if (EXPECTED(client->request.query_string == NULL)) {
1646 client->request.query_string = pestrndup(at, length, 1);
1647 client->request.query_string_len = length;
1648 } else {
1649 ZEND_ASSERT(length <= PHP_HTTP_MAX_HEADER_SIZE && PHP_HTTP_MAX_HEADER_SIZE - length >= client->request.query_string_len);
1650 client->request.query_string = perealloc(client->request.query_string, client->request.query_string_len + length + 1, 1);
1651 memcpy(client->request.query_string + client->request.query_string_len, at, length);
1652 client->request.query_string_len += length;
1653 client->request.query_string[client->request.query_string_len] = '\0';
1654 }
1655 return 0;
1656 }
1657
php_cli_server_client_read_request_on_url(php_http_parser * parser,const char * at,size_t length)1658 static int php_cli_server_client_read_request_on_url(php_http_parser *parser, const char *at, size_t length)
1659 {
1660 php_cli_server_client *client = parser->data;
1661 if (EXPECTED(client->request.request_uri == NULL)) {
1662 client->request.request_method = parser->method;
1663 client->request.request_uri = zend_string_init(at, length, /* persistent */ true);
1664 GC_MAKE_PERSISTENT_LOCAL(client->request.request_uri);
1665 } else {
1666 ZEND_ASSERT(client->request.request_method == parser->method);
1667 ZEND_ASSERT(length <= PHP_HTTP_MAX_HEADER_SIZE && PHP_HTTP_MAX_HEADER_SIZE - length >= client->request.query_string_len);
1668 /* Extend URI, append content to it */
1669 client->request.request_uri = cli_concat_persistent_zstr_with_char(client->request.request_uri, at, length);
1670 }
1671 return 0;
1672 }
1673
php_cli_server_client_read_request_on_fragment(php_http_parser * parser,const char * at,size_t length)1674 static int php_cli_server_client_read_request_on_fragment(php_http_parser *parser, const char *at, size_t length)
1675 {
1676 return 0;
1677 }
1678
php_cli_server_client_save_header(php_cli_server_client * client)1679 static void php_cli_server_client_save_header(php_cli_server_client *client)
1680 {
1681 /* Wrap header value in a zval to add is to the HashTable which acts as an array */
1682 zval tmp;
1683 ZVAL_STR(&tmp, client->current_header_value);
1684 /* strip off the colon */
1685 zend_string *lc_header_name = zend_string_tolower_ex(client->current_header_name, /* persistent */ true);
1686 GC_MAKE_PERSISTENT_LOCAL(lc_header_name);
1687
1688 /* Add the wrapped zend_string to the HashTable */
1689 zend_hash_add(&client->request.headers, lc_header_name, &tmp);
1690 zend_hash_add(&client->request.headers_original_case, client->current_header_name, &tmp);
1691
1692 zend_string_release_ex(lc_header_name, /* persistent */ true);
1693 zend_string_release_ex(client->current_header_name, /* persistent */ true);
1694
1695 client->current_header_name = NULL;
1696 client->current_header_value = NULL;
1697 }
1698
cli_concat_persistent_zstr_with_char(zend_string * old_str,const char * at,size_t length)1699 static zend_string* cli_concat_persistent_zstr_with_char(zend_string *old_str, const char *at, size_t length)
1700 {
1701 /* Assert that there is only one reference to the string, as then zend_string_extends()
1702 * will reallocate it such that we do not need to release the old value. */
1703 ZEND_ASSERT(GC_REFCOUNT(old_str) == 1);
1704 /* Previous element was part of header value, append content to it */
1705 size_t old_length = ZSTR_LEN(old_str);
1706 zend_string *str = zend_string_extend(old_str, old_length + length, /* persistent */ true);
1707 memcpy(ZSTR_VAL(str) + old_length, at, length);
1708 // Null terminate
1709 ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0';
1710 return str;
1711 }
1712
php_cli_server_client_read_request_on_header_field(php_http_parser * parser,const char * at,size_t length)1713 static int php_cli_server_client_read_request_on_header_field(php_http_parser *parser, const char *at, size_t length)
1714 {
1715 php_cli_server_client *client = parser->data;
1716 switch (client->last_header_element) {
1717 case HEADER_VALUE:
1718 /* Save previous header before creating new one */
1719 php_cli_server_client_save_header(client);
1720 ZEND_FALLTHROUGH;
1721 case HEADER_NONE:
1722 /* Create new header field */
1723 client->current_header_name = zend_string_init(at, length, /* persistent */ true);
1724 GC_MAKE_PERSISTENT_LOCAL(client->current_header_name);
1725 break;
1726 case HEADER_FIELD: {
1727 /* Append header name to the previous value of it */
1728 client->current_header_name = cli_concat_persistent_zstr_with_char(client->current_header_name, at, length);
1729 break;
1730 }
1731 }
1732
1733 client->last_header_element = HEADER_FIELD;
1734 return 0;
1735 }
1736
php_cli_server_client_read_request_on_header_value(php_http_parser * parser,const char * at,size_t length)1737 static int php_cli_server_client_read_request_on_header_value(php_http_parser *parser, const char *at, size_t length)
1738 {
1739 php_cli_server_client *client = parser->data;
1740 switch (client->last_header_element) {
1741 case HEADER_FIELD:
1742 /* Previous element was the header field, create the header value */
1743 client->current_header_value = zend_string_init(at, length, /* persistent */ true);
1744 GC_MAKE_PERSISTENT_LOCAL(client->current_header_value);
1745 break;
1746 case HEADER_VALUE: {
1747 /* Append header value to the previous value of it */
1748 client->current_header_value = cli_concat_persistent_zstr_with_char(client->current_header_value, at, length);
1749 break;
1750 }
1751 case HEADER_NONE:
1752 /* Cannot happen as a header field must have been found before */
1753 assert(0);
1754 break;
1755 }
1756 client->last_header_element = HEADER_VALUE;
1757 return 0;
1758 }
1759
php_cli_server_client_read_request_on_headers_complete(php_http_parser * parser)1760 static int php_cli_server_client_read_request_on_headers_complete(php_http_parser *parser)
1761 {
1762 php_cli_server_client *client = parser->data;
1763 switch (client->last_header_element) {
1764 case HEADER_NONE:
1765 break;
1766 case HEADER_FIELD:
1767 /* Previous element was the header field, set it's value to an empty string */
1768 client->current_header_value = ZSTR_EMPTY_ALLOC();
1769 ZEND_FALLTHROUGH;
1770 case HEADER_VALUE:
1771 /* Save last header value */
1772 php_cli_server_client_save_header(client);
1773 break;
1774 }
1775 client->last_header_element = HEADER_NONE;
1776 return 0;
1777 }
1778
php_cli_server_client_read_request_on_body(php_http_parser * parser,const char * at,size_t length)1779 static int php_cli_server_client_read_request_on_body(php_http_parser *parser, const char *at, size_t length)
1780 {
1781 php_cli_server_client *client = parser->data;
1782 if (!client->request.content) {
1783 client->request.content = pemalloc(parser->content_length, 1);
1784 client->request.content_len = 0;
1785 }
1786 client->request.content = perealloc(client->request.content, client->request.content_len + length, 1);
1787 memmove(client->request.content + client->request.content_len, at, length);
1788 client->request.content_len += length;
1789 return 0;
1790 }
1791
php_cli_server_client_read_request_on_message_complete(php_http_parser * parser)1792 static int php_cli_server_client_read_request_on_message_complete(php_http_parser *parser)
1793 {
1794 php_cli_server_client *client = parser->data;
1795 client->request.protocol_version = parser->http_major * 100 + parser->http_minor;
1796 php_cli_server_request_translate_vpath(&client->request, client->server->document_root, client->server->document_root_len);
1797 {
1798 const char *vpath = client->request.vpath, *end = vpath + client->request.vpath_len, *p = end;
1799 client->request.ext = end;
1800 client->request.ext_len = 0;
1801 while (p > vpath) {
1802 --p;
1803 if (*p == '.') {
1804 ++p;
1805 client->request.ext = p;
1806 client->request.ext_len = end - p;
1807 break;
1808 }
1809 }
1810 }
1811 client->request_read = true;
1812 return 0;
1813 }
1814
1815 /* Returns:
1816 -1 when an error occurs
1817 0 when the request has not been read (try again?)
1818 1 when the request has been read
1819 */
php_cli_server_client_read_request(php_cli_server_client * client,char ** errstr)1820 static int php_cli_server_client_read_request(php_cli_server_client *client, char **errstr)
1821 {
1822 char buf[16384];
1823 static const php_http_parser_settings settings = {
1824 php_cli_server_client_read_request_on_message_begin,
1825 php_cli_server_client_read_request_on_path,
1826 php_cli_server_client_read_request_on_query_string,
1827 php_cli_server_client_read_request_on_url,
1828 php_cli_server_client_read_request_on_fragment,
1829 php_cli_server_client_read_request_on_header_field,
1830 php_cli_server_client_read_request_on_header_value,
1831 php_cli_server_client_read_request_on_headers_complete,
1832 php_cli_server_client_read_request_on_body,
1833 php_cli_server_client_read_request_on_message_complete
1834 };
1835 size_t nbytes_consumed;
1836 int nbytes_read;
1837 if (client->request_read) {
1838 return 1;
1839 }
1840 nbytes_read = recv(client->sock, buf, sizeof(buf) - 1, 0);
1841 if (nbytes_read < 0) {
1842 int err = php_socket_errno();
1843 if (err == SOCK_EAGAIN) {
1844 return 0;
1845 }
1846
1847 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
1848 *errstr = php_socket_strerror(err, NULL, 0);
1849 }
1850
1851 return -1;
1852 } else if (nbytes_read == 0) {
1853 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
1854 *errstr = estrdup(php_cli_server_request_error_unexpected_eof);
1855 }
1856
1857 return -1;
1858 }
1859 client->parser.data = client;
1860 nbytes_consumed = php_http_parser_execute(&client->parser, &settings, buf, nbytes_read);
1861 if (nbytes_consumed != (size_t)nbytes_read) {
1862 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
1863 if ((buf[0] & 0x80) /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) {
1864 *errstr = estrdup("Unsupported SSL request");
1865 } else {
1866 *errstr = estrdup("Malformed HTTP request");
1867 }
1868 }
1869
1870 return -1;
1871 }
1872
1873 return client->request_read ? 1: 0;
1874 }
1875 /* }}} */
1876
php_cli_server_client_send_through(php_cli_server_client * client,const char * str,size_t str_len)1877 static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len) /* {{{ */
1878 {
1879 struct timeval tv = { 10, 0 };
1880 #ifdef PHP_WIN32
1881 int nbytes_left = (int)str_len;
1882 #else
1883 ssize_t nbytes_left = (ssize_t)str_len;
1884 #endif
1885 do {
1886 #ifdef PHP_WIN32
1887 int nbytes_sent;
1888 #else
1889 ssize_t nbytes_sent;
1890 #endif
1891
1892 nbytes_sent = send(client->sock, str + str_len - nbytes_left, nbytes_left, 0);
1893 if (nbytes_sent < 0) {
1894 int err = php_socket_errno();
1895 if (err == SOCK_EAGAIN) {
1896 int nfds = php_pollfd_for(client->sock, POLLOUT, &tv);
1897 if (nfds > 0) {
1898 continue;
1899 } else {
1900 /* error or timeout */
1901 php_handle_aborted_connection();
1902 return nbytes_left;
1903 }
1904 } else {
1905 php_handle_aborted_connection();
1906 return nbytes_left;
1907 }
1908 }
1909 nbytes_left -= nbytes_sent;
1910 } while (nbytes_left > 0);
1911
1912 return str_len;
1913 } /* }}} */
1914
php_cli_server_client_populate_request_info(const php_cli_server_client * client,sapi_request_info * request_info)1915 static void php_cli_server_client_populate_request_info(const php_cli_server_client *client, sapi_request_info *request_info) /* {{{ */
1916 {
1917 zval *val;
1918
1919 request_info->request_method = php_http_method_str(client->request.request_method);
1920 request_info->proto_num = client->request.protocol_version;
1921 request_info->request_uri = ZSTR_VAL(client->request.request_uri);
1922 request_info->path_translated = client->request.path_translated;
1923 request_info->query_string = client->request.query_string;
1924 request_info->content_length = client->request.content_len;
1925 request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL;
1926 if (NULL != (val = zend_hash_str_find(&client->request.headers, "content-type", sizeof("content-type")-1))) {
1927 request_info->content_type = Z_STRVAL_P(val);
1928 }
1929 } /* }}} */
1930
1931 // TODO Remove?
destroy_request_info(sapi_request_info * request_info)1932 static void destroy_request_info(sapi_request_info *request_info) /* {{{ */
1933 {
1934 } /* }}} */
1935
php_cli_server_client_ctor(php_cli_server_client * client,php_cli_server * server,php_socket_t client_sock,struct sockaddr * addr,socklen_t addr_len)1936 static void php_cli_server_client_ctor(php_cli_server_client *client, php_cli_server *server, php_socket_t client_sock, struct sockaddr *addr, socklen_t addr_len) /* {{{ */
1937 {
1938 client->server = server;
1939 client->sock = client_sock;
1940 client->addr = addr;
1941 client->addr_len = addr_len;
1942
1943 // TODO To prevent the reallocation need to retrieve a persistent string
1944 // Create a new php_network_populate_name_from_sockaddr_ex() API with a persistent flag?
1945 zend_string *tmp_addr = NULL;
1946 php_network_populate_name_from_sockaddr(addr, addr_len, &tmp_addr, NULL, 0);
1947 client->addr_str = zend_string_dup(tmp_addr, /* persistent */ true);
1948 GC_MAKE_PERSISTENT_LOCAL(client->addr_str);
1949 zend_string_release_ex(tmp_addr, /* persistent */ false);
1950
1951 php_http_parser_init(&client->parser, PHP_HTTP_REQUEST);
1952 client->request_read = false;
1953
1954 client->last_header_element = HEADER_NONE;
1955 client->current_header_name = NULL;
1956 client->current_header_value = NULL;
1957
1958 client->post_read_offset = 0;
1959
1960 php_cli_server_request_ctor(&client->request);
1961
1962 client->content_sender_initialized = false;
1963 client->file_fd = -1;
1964 } /* }}} */
1965
php_cli_server_client_dtor(php_cli_server_client * client)1966 static void php_cli_server_client_dtor(php_cli_server_client *client) /* {{{ */
1967 {
1968 php_cli_server_request_dtor(&client->request);
1969 if (client->file_fd >= 0) {
1970 close(client->file_fd);
1971 client->file_fd = -1;
1972 }
1973 pefree(client->addr, 1);
1974 zend_string_release_ex(client->addr_str, /* persistent */ true);
1975
1976 if (client->content_sender_initialized) {
1977 /* Headers must be set if we reached the content initialisation */
1978 assert(client->current_header_name == NULL);
1979 assert(client->current_header_value == NULL);
1980 php_cli_server_content_sender_dtor(&client->content_sender);
1981 }
1982 } /* }}} */
1983
php_cli_server_close_connection(php_cli_server * server,php_cli_server_client * client)1984 static void php_cli_server_close_connection(php_cli_server *server, php_cli_server_client *client) /* {{{ */
1985 {
1986 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "%s Closing", ZSTR_VAL(client->addr_str));
1987
1988 zend_hash_index_del(&server->clients, client->sock);
1989 } /* }}} */
1990
php_cli_server_send_error_page(php_cli_server * server,php_cli_server_client * client,int status)1991 static zend_result php_cli_server_send_error_page(php_cli_server *server, php_cli_server_client *client, int status) /* {{{ */
1992 {
1993 zend_string *escaped_request_uri = NULL;
1994 const char *status_string = get_status_string(status);
1995 const char *content_template = get_template_string(status);
1996 char *errstr = get_last_error();
1997 assert(status_string && content_template);
1998
1999 php_cli_server_content_sender_ctor(&client->content_sender);
2000 client->content_sender_initialized = true;
2001
2002 if (client->request.request_method != PHP_HTTP_HEAD) {
2003 escaped_request_uri = php_escape_html_entities_ex((const unsigned char *) ZSTR_VAL(client->request.request_uri), ZSTR_LEN(client->request.request_uri), 0, ENT_QUOTES, NULL, /* double_encode */ 0, /* quiet */ 0);
2004
2005 {
2006 static const char prologue_template[] = "<!doctype html><html><head><title>%d %s</title>";
2007 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(prologue_template) + 3 + strlen(status_string) + 1);
2008 if (!chunk) {
2009 goto fail;
2010 }
2011 snprintf(chunk->data.heap.p, chunk->data.heap.len, prologue_template, status, status_string);
2012 chunk->data.heap.len = strlen(chunk->data.heap.p);
2013 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2014 }
2015 {
2016 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(php_cli_server_css, sizeof(php_cli_server_css) - 1);
2017 if (!chunk) {
2018 goto fail;
2019 }
2020 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2021 }
2022 {
2023 static const char template[] = "</head><body>";
2024 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(template, sizeof(template) - 1);
2025 if (!chunk) {
2026 goto fail;
2027 }
2028 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2029 }
2030 {
2031 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(content_template) + ZSTR_LEN(escaped_request_uri) + 3 + strlen(status_string) + 1);
2032 if (!chunk) {
2033 goto fail;
2034 }
2035 snprintf(chunk->data.heap.p, chunk->data.heap.len, content_template, status_string, ZSTR_VAL(escaped_request_uri));
2036 chunk->data.heap.len = strlen(chunk->data.heap.p);
2037 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2038 }
2039 {
2040 static const char epilogue_template[] = "</body></html>";
2041 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(epilogue_template, sizeof(epilogue_template) - 1);
2042 if (!chunk) {
2043 goto fail;
2044 }
2045 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2046 }
2047 }
2048
2049 {
2050 php_cli_server_chunk *chunk;
2051 smart_str buffer = { 0 };
2052 append_http_status_line(&buffer, client->request.protocol_version, status, 1);
2053 if (!buffer.s) {
2054 /* out of memory */
2055 goto fail;
2056 }
2057 append_essential_headers(&buffer, client, 1, NULL);
2058 smart_str_appends_ex(&buffer, "Content-Type: text/html; charset=UTF-8\r\n", 1);
2059 smart_str_appends_ex(&buffer, "Content-Length: ", 1);
2060 smart_str_append_unsigned_ex(&buffer, php_cli_server_buffer_size(&client->content_sender.buffer), 1);
2061 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2062 if (status == 405) {
2063 smart_str_appends_ex(&buffer, "Allow: ", 1);
2064 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_GET), 1);
2065 smart_str_appends_ex(&buffer, ", ", 1);
2066 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_HEAD), 1);
2067 smart_str_appends_ex(&buffer, ", ", 1);
2068 smart_str_appends_ex(&buffer, php_http_method_str(PHP_HTTP_POST), 1);
2069 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2070 }
2071 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2072
2073 chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
2074 if (!chunk) {
2075 smart_str_free_ex(&buffer, 1);
2076 goto fail;
2077 }
2078 php_cli_server_buffer_prepend(&client->content_sender.buffer, chunk);
2079 }
2080
2081 php_cli_server_log_response(client, status, errstr ? errstr : "?");
2082 php_cli_server_poller_add(&server->poller, POLLOUT, client->sock);
2083 if (errstr) {
2084 pefree(errstr, 1);
2085 }
2086 if (escaped_request_uri) {
2087 zend_string_free(escaped_request_uri);
2088 }
2089 return SUCCESS;
2090
2091 fail:
2092 if (errstr) {
2093 pefree(errstr, 1);
2094 }
2095 if (escaped_request_uri) {
2096 zend_string_free(escaped_request_uri);
2097 }
2098 return FAILURE;
2099 } /* }}} */
2100
php_cli_server_dispatch_script(php_cli_server * server,php_cli_server_client * client)2101 static zend_result php_cli_server_dispatch_script(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2102 {
2103 if (strlen(client->request.path_translated) != client->request.path_translated_len) {
2104 /* can't handle paths that contain nul bytes */
2105 return php_cli_server_send_error_page(server, client, 400);
2106 }
2107
2108 zend_file_handle zfd;
2109 zend_stream_init_filename(&zfd, SG(request_info).path_translated);
2110 zfd.primary_script = 1;
2111 zend_try {
2112 php_execute_script(&zfd);
2113 } zend_end_try();
2114 zend_destroy_file_handle(&zfd);
2115
2116 php_cli_server_log_response(client, SG(sapi_headers).http_response_code, NULL);
2117 return SUCCESS;
2118 } /* }}} */
2119
php_cli_server_begin_send_static(php_cli_server * server,php_cli_server_client * client)2120 static zend_result php_cli_server_begin_send_static(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2121 {
2122 int fd;
2123 int status = 200;
2124
2125 if (client->request.request_method == PHP_HTTP_DELETE
2126 || client->request.request_method == PHP_HTTP_PUT
2127 || client->request.request_method == PHP_HTTP_PATCH) {
2128 return php_cli_server_send_error_page(server, client, 405);
2129 }
2130
2131 if (client->request.path_translated && strlen(client->request.path_translated) != client->request.path_translated_len) {
2132 /* can't handle paths that contain nul bytes */
2133 return php_cli_server_send_error_page(server, client, 400);
2134 }
2135
2136 #ifdef PHP_WIN32
2137 /* The win32 namespace will cut off trailing dots and spaces. Since the
2138 VCWD functionality isn't used here, a sophisticated functionality
2139 would have to be reimplemented to know ahead there are no files
2140 with invalid names there. The simplest is just to forbid invalid
2141 filenames, which is done here. */
2142 if (client->request.path_translated &&
2143 ('.' == client->request.path_translated[client->request.path_translated_len-1] ||
2144 ' ' == client->request.path_translated[client->request.path_translated_len-1])) {
2145 return php_cli_server_send_error_page(server, client, 500);
2146 }
2147
2148 fd = client->request.path_translated ? php_win32_ioutil_open(client->request.path_translated, O_RDONLY): -1;
2149 #else
2150 fd = client->request.path_translated ? open(client->request.path_translated, O_RDONLY): -1;
2151 #endif
2152 if (fd < 0) {
2153 return php_cli_server_send_error_page(server, client, 404);
2154 }
2155
2156 php_cli_server_content_sender_ctor(&client->content_sender);
2157 client->content_sender_initialized = true;
2158 if (client->request.request_method != PHP_HTTP_HEAD) {
2159 client->file_fd = fd;
2160 }
2161
2162 {
2163 php_cli_server_chunk *chunk;
2164 smart_str buffer = { 0 };
2165 const char *mime_type = get_mime_type(server, client->request.ext, client->request.ext_len);
2166
2167 append_http_status_line(&buffer, client->request.protocol_version, status, 1);
2168 if (!buffer.s) {
2169 /* out of memory */
2170 php_cli_server_log_response(client, 500, NULL);
2171 return FAILURE;
2172 }
2173 append_essential_headers(&buffer, client, 1, NULL);
2174 if (mime_type) {
2175 smart_str_appendl_ex(&buffer, "Content-Type: ", sizeof("Content-Type: ") - 1, 1);
2176 smart_str_appends_ex(&buffer, mime_type, 1);
2177 if (strncmp(mime_type, "text/", 5) == 0) {
2178 smart_str_appends_ex(&buffer, "; charset=UTF-8", 1);
2179 }
2180 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2181 }
2182 smart_str_appends_ex(&buffer, "Content-Length: ", 1);
2183 smart_str_append_unsigned_ex(&buffer, client->request.sb.st_size, 1);
2184 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2185 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2186 chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
2187 if (!chunk) {
2188 smart_str_free_ex(&buffer, 1);
2189 php_cli_server_log_response(client, 500, NULL);
2190 return FAILURE;
2191 }
2192 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2193 }
2194 php_cli_server_log_response(client, 200, NULL);
2195 php_cli_server_poller_add(&server->poller, POLLOUT, client->sock);
2196 return SUCCESS;
2197 }
2198 /* }}} */
2199
php_cli_server_request_startup(php_cli_server * server,php_cli_server_client * client)2200 static zend_result php_cli_server_request_startup(php_cli_server *server, php_cli_server_client *client) { /* {{{ */
2201 zval *auth;
2202 php_cli_server_client_populate_request_info(client, &SG(request_info));
2203 if (NULL != (auth = zend_hash_str_find(&client->request.headers, "authorization", sizeof("authorization")-1))) {
2204 php_handle_auth_data(Z_STRVAL_P(auth));
2205 }
2206 SG(sapi_headers).http_response_code = 200;
2207 if (FAILURE == php_request_startup()) {
2208 return FAILURE;
2209 }
2210 PG(during_request_startup) = 0;
2211
2212 return SUCCESS;
2213 }
2214 /* }}} */
2215
php_cli_server_request_shutdown(php_cli_server * server,php_cli_server_client * client)2216 static void php_cli_server_request_shutdown(php_cli_server *server, php_cli_server_client *client) { /* {{{ */
2217 php_request_shutdown(0);
2218 php_cli_server_close_connection(server, client);
2219 destroy_request_info(&SG(request_info));
2220 SG(server_context) = NULL;
2221 SG(rfc1867_uploaded_files) = NULL;
2222 }
2223 /* }}} */
2224
2225 // TODO Use bool, return value is strange
php_cli_server_dispatch_router(php_cli_server * server,php_cli_server_client * client)2226 static int php_cli_server_dispatch_router(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2227 {
2228 int decline = 0;
2229 zend_file_handle zfd;
2230 char *old_cwd;
2231
2232 ALLOCA_FLAG(use_heap)
2233 old_cwd = do_alloca(MAXPATHLEN, use_heap);
2234 old_cwd[0] = '\0';
2235 php_ignore_value(VCWD_GETCWD(old_cwd, MAXPATHLEN - 1));
2236
2237 zend_stream_init_filename(&zfd, server->router);
2238 zfd.primary_script = 1;
2239
2240 zend_try {
2241 zval retval;
2242
2243 /* Normally php_execute_script restarts the timer with max_execution_time if it has
2244 * previously been initialized with max_input_time. We're not using php_execute_script here
2245 * because it does not provide a way to get the return value of the main script, so we need
2246 * to restart the timer manually. */
2247 if (PG(max_input_time) != -1) {
2248 #ifdef PHP_WIN32
2249 zend_unset_timeout();
2250 #endif
2251 zend_set_timeout(INI_INT("max_execution_time"), 0);
2252 }
2253
2254 ZVAL_UNDEF(&retval);
2255 if (SUCCESS == zend_execute_scripts(ZEND_REQUIRE, &retval, 1, &zfd)) {
2256 if (Z_TYPE(retval) != IS_UNDEF) {
2257 decline = Z_TYPE(retval) == IS_FALSE;
2258 zval_ptr_dtor(&retval);
2259 }
2260 }
2261 } zend_end_try();
2262
2263 zend_destroy_file_handle(&zfd);
2264
2265 if (old_cwd[0] != '\0') {
2266 php_ignore_value(VCWD_CHDIR(old_cwd));
2267 }
2268
2269 free_alloca(old_cwd, use_heap);
2270
2271 return decline;
2272 }
2273 /* }}} */
2274
2275 // TODO Return FAILURE on error? Or voidify as return value of function not checked
php_cli_server_dispatch(php_cli_server * server,php_cli_server_client * client)2276 static zend_result php_cli_server_dispatch(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2277 {
2278 int is_static_file = 0;
2279 const char *ext = client->request.ext;
2280
2281 SG(server_context) = client;
2282 if (client->request.ext_len != 3
2283 || (ext[0] != 'p' && ext[0] != 'P') || (ext[1] != 'h' && ext[1] != 'H') || (ext[2] != 'p' && ext[2] != 'P')
2284 || !client->request.path_translated) {
2285 is_static_file = 1;
2286 }
2287
2288 if (server->router || !is_static_file) {
2289 if (FAILURE == php_cli_server_request_startup(server, client)) {
2290 php_cli_server_request_shutdown(server, client);
2291 return SUCCESS;
2292 }
2293 }
2294
2295 if (server->router) {
2296 if (!php_cli_server_dispatch_router(server, client)) {
2297 php_cli_server_request_shutdown(server, client);
2298 return SUCCESS;
2299 }
2300 }
2301
2302 if (!is_static_file) {
2303 // TODO What?
2304 if (SUCCESS == php_cli_server_dispatch_script(server, client)
2305 || FAILURE == php_cli_server_send_error_page(server, client, 500)) {
2306 if (SG(sapi_headers).http_response_code == 304) {
2307 SG(sapi_headers).send_default_content_type = 0;
2308 }
2309 php_cli_server_request_shutdown(server, client);
2310 return SUCCESS;
2311 }
2312 } else {
2313 if (server->router) {
2314 static int (*send_header_func)(sapi_headers_struct *);
2315 send_header_func = sapi_module.send_headers;
2316 /* do not generate default content type header */
2317 SG(sapi_headers).send_default_content_type = 0;
2318 /* we don't want headers to be sent */
2319 sapi_module.send_headers = sapi_cli_server_discard_headers;
2320 php_request_shutdown(0);
2321 sapi_module.send_headers = send_header_func;
2322 SG(sapi_headers).send_default_content_type = 1;
2323 SG(rfc1867_uploaded_files) = NULL;
2324 }
2325 if (FAILURE == php_cli_server_begin_send_static(server, client)) {
2326 php_cli_server_close_connection(server, client);
2327 }
2328 SG(server_context) = NULL;
2329 return SUCCESS;
2330 }
2331
2332 SG(server_context) = NULL;
2333 destroy_request_info(&SG(request_info));
2334 return SUCCESS;
2335 }
2336 /* }}} */
2337
php_cli_server_mime_type_ctor(php_cli_server * server,const php_cli_server_ext_mime_type_pair * mime_type_map)2338 static void php_cli_server_mime_type_ctor(php_cli_server *server, const php_cli_server_ext_mime_type_pair *mime_type_map) /* {{{ */
2339 {
2340 const php_cli_server_ext_mime_type_pair *pair;
2341
2342 zend_hash_init(&server->extension_mime_types, 0, NULL, NULL, 1);
2343 GC_MAKE_PERSISTENT_LOCAL(&server->extension_mime_types);
2344
2345 for (pair = mime_type_map; pair->ext; pair++) {
2346 size_t ext_len = strlen(pair->ext);
2347 zend_hash_str_add_ptr(&server->extension_mime_types, pair->ext, ext_len, (void*)pair->mime_type);
2348 }
2349 } /* }}} */
2350
php_cli_server_dtor(php_cli_server * server)2351 static void php_cli_server_dtor(php_cli_server *server) /* {{{ */
2352 {
2353 zend_hash_destroy(&server->clients);
2354 zend_hash_destroy(&server->extension_mime_types);
2355 if (ZEND_VALID_SOCKET(server->server_sock)) {
2356 closesocket(server->server_sock);
2357 }
2358 if (server->host) {
2359 pefree(server->host, 1);
2360 }
2361 if (server->document_root) {
2362 pefree(server->document_root, 1);
2363 }
2364 if (server->router) {
2365 pefree(server->router, 1);
2366 }
2367 #if HAVE_FORK
2368 if (php_cli_server_workers_max > 1 &&
2369 php_cli_server_workers &&
2370 getpid() == php_cli_server_master) {
2371 zend_long php_cli_server_worker;
2372
2373 for (php_cli_server_worker = 0;
2374 php_cli_server_worker < php_cli_server_workers_max;
2375 php_cli_server_worker++) {
2376 int php_cli_server_worker_status;
2377
2378 do {
2379 if (waitpid(php_cli_server_workers[php_cli_server_worker],
2380 &php_cli_server_worker_status,
2381 0) == FAILURE) {
2382 /* an extremely bad thing happened */
2383 break;
2384 }
2385
2386 } while (!WIFEXITED(php_cli_server_worker_status) &&
2387 !WIFSIGNALED(php_cli_server_worker_status));
2388 }
2389
2390 pefree(php_cli_server_workers, 1);
2391 }
2392 #endif
2393 } /* }}} */
2394
php_cli_server_client_dtor_wrapper(zval * zv)2395 static void php_cli_server_client_dtor_wrapper(zval *zv) /* {{{ */
2396 {
2397 php_cli_server_client *p = Z_PTR_P(zv);
2398
2399 shutdown(p->sock, SHUT_RDWR);
2400 closesocket(p->sock);
2401 php_cli_server_poller_remove(&p->server->poller, POLLIN | POLLOUT, p->sock);
2402 php_cli_server_client_dtor(p);
2403 pefree(p, 1);
2404 } /* }}} */
2405
2406 /**
2407 * Parse the host and port portions of an address specifier in
2408 * one of the following forms:
2409 * - hostOrIP:port
2410 * - [hostOrIP]:port
2411 */
php_cli_server_parse_addr(const char * addr,int * pport)2412 static char *php_cli_server_parse_addr(const char *addr, int *pport) {
2413 const char *p, *end;
2414 long port;
2415
2416 if (addr[0] == '[') {
2417 /* Encapsulated [hostOrIP]:port */
2418 const char *start = addr + 1;
2419 end = strchr(start, ']');
2420 if (!end) {
2421 /* No ending ] delimiter to match [ */
2422 return NULL;
2423 }
2424
2425 p = end + 1;
2426 if (*p != ':') {
2427 /* Invalid char following address/missing port */
2428 return NULL;
2429 }
2430
2431 port = strtol(p + 1, (char**)&p, 10);
2432 if (p && *p) {
2433 /* Non-numeric in port */
2434 return NULL;
2435 }
2436 if (port < 0 || port > 65535) {
2437 /* Invalid port */
2438 return NULL;
2439 }
2440
2441 /* Full [hostOrIP]:port provided */
2442 *pport = (int)port;
2443 return pestrndup(start, end - start, 1);
2444 }
2445
2446 end = strchr(addr, ':');
2447 if (!end) {
2448 /* Missing port */
2449 return NULL;
2450 }
2451
2452 port = strtol(end + 1, (char**)&p, 10);
2453 if (p && *p) {
2454 /* Non-numeric port */
2455 return NULL;
2456 }
2457 if (port < 0 || port > 65535) {
2458 /* Invalid port */
2459 return NULL;
2460 }
2461 *pport = (int)port;
2462 return pestrndup(addr, end - addr, 1);
2463 }
2464
php_cli_server_startup_workers(void)2465 static void php_cli_server_startup_workers(void) {
2466 char *workers = getenv("PHP_CLI_SERVER_WORKERS");
2467 if (!workers) {
2468 return;
2469 }
2470
2471 #if HAVE_FORK
2472 php_cli_server_workers_max = ZEND_ATOL(workers);
2473 if (php_cli_server_workers_max > 1) {
2474 zend_long php_cli_server_worker;
2475
2476 php_cli_server_workers = pecalloc(
2477 php_cli_server_workers_max, sizeof(pid_t), 1);
2478
2479 php_cli_server_master = getpid();
2480
2481 for (php_cli_server_worker = 0;
2482 php_cli_server_worker < php_cli_server_workers_max;
2483 php_cli_server_worker++) {
2484 pid_t pid = fork();
2485
2486 if (pid < 0) {
2487 /* no more forks allowed, work with what we have ... */
2488 php_cli_server_workers_max =
2489 php_cli_server_worker + 1;
2490 return;
2491 } else if (pid == 0) {
2492 return;
2493 } else {
2494 php_cli_server_workers[php_cli_server_worker] = pid;
2495 }
2496 }
2497 } else {
2498 fprintf(stderr, "number of workers must be larger than 1\n");
2499 }
2500 #else
2501 fprintf(stderr, "forking is not supported on this platform\n");
2502 #endif
2503 }
2504
php_cli_server_ctor(php_cli_server * server,const char * addr,const char * document_root,const char * router)2505 static zend_result php_cli_server_ctor(php_cli_server *server, const char *addr, const char *document_root, const char *router) /* {{{ */
2506 {
2507 zend_result retval = SUCCESS;
2508 char *host = NULL;
2509 zend_string *errstr = NULL;
2510 char *_document_root = NULL;
2511 char *_router = NULL;
2512 int port = 3000;
2513 php_socket_t server_sock = SOCK_ERR;
2514
2515 host = php_cli_server_parse_addr(addr, &port);
2516 if (!host) {
2517 fprintf(stderr, "Invalid address: %s\n", addr);
2518 retval = FAILURE;
2519 goto out;
2520 }
2521
2522 server_sock = php_network_listen_socket(host, &port, SOCK_STREAM, &server->address_family, &server->socklen, &errstr);
2523 if (server_sock == SOCK_ERR) {
2524 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "Failed to listen on %s:%d (reason: %s)", host, port, errstr ? ZSTR_VAL(errstr) : "?");
2525 if (errstr) {
2526 zend_string_release_ex(errstr, 0);
2527 }
2528 retval = FAILURE;
2529 goto out;
2530 }
2531 // server_sock needs to be non-blocking when using multiple processes. Without it, the first process would
2532 // successfully accept the connection but the others would block, causing client sockets of the same select
2533 // call not to be handled.
2534 if (SUCCESS != php_set_sock_blocking(server_sock, 0)) {
2535 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "Failed to make server socket non-blocking");
2536 retval = FAILURE;
2537 goto out;
2538 }
2539 server->server_sock = server_sock;
2540
2541 php_cli_server_startup_workers();
2542
2543 php_cli_server_poller_ctor(&server->poller);
2544
2545 php_cli_server_poller_add(&server->poller, POLLIN, server_sock);
2546
2547 server->host = host;
2548 server->port = port;
2549
2550 zend_hash_init(&server->clients, 0, NULL, php_cli_server_client_dtor_wrapper, 1);
2551
2552 {
2553 size_t document_root_len = strlen(document_root);
2554 _document_root = pestrndup(document_root, document_root_len, 1);
2555 server->document_root = _document_root;
2556 server->document_root_len = document_root_len;
2557 }
2558
2559 if (router) {
2560 size_t router_len = strlen(router);
2561 _router = pestrndup(router, router_len, 1);
2562 server->router = _router;
2563 server->router_len = router_len;
2564 } else {
2565 server->router = NULL;
2566 server->router_len = 0;
2567 }
2568
2569 php_cli_server_mime_type_ctor(server, mime_type_map);
2570
2571 server->is_running = 1;
2572 out:
2573 if (retval == FAILURE) {
2574 if (host) {
2575 pefree(host, 1);
2576 }
2577 if (_document_root) {
2578 pefree(_document_root, 1);
2579 }
2580 if (_router) {
2581 pefree(_router, 1);
2582 }
2583 if (server_sock > -1) {
2584 closesocket(server_sock);
2585 }
2586 }
2587 return retval;
2588 } /* }}} */
2589
php_cli_server_recv_event_read_request(php_cli_server * server,php_cli_server_client * client)2590 static zend_result php_cli_server_recv_event_read_request(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2591 {
2592 char *errstr = NULL;
2593
2594 switch (php_cli_server_client_read_request(client, &errstr)) {
2595 case -1:
2596 if (errstr) {
2597 if (strcmp(errstr, php_cli_server_request_error_unexpected_eof) == 0 && client->parser.state == s_start_req) {
2598 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE,
2599 "%s Closed without sending a request; it was probably just an unused speculative preconnection", ZSTR_VAL(client->addr_str));
2600 } else {
2601 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s Invalid request (%s)", ZSTR_VAL(client->addr_str), errstr);
2602 }
2603 efree(errstr);
2604 }
2605 php_cli_server_close_connection(server, client);
2606 return FAILURE;
2607 case 1:
2608 if (client->request.request_method == PHP_HTTP_NOT_IMPLEMENTED) {
2609 return php_cli_server_send_error_page(server, client, 501);
2610 }
2611 php_cli_server_poller_remove(&server->poller, POLLIN, client->sock);
2612 php_cli_server_dispatch(server, client);
2613 return SUCCESS;
2614 case 0:
2615 php_cli_server_poller_add(&server->poller, POLLIN, client->sock);
2616 return SUCCESS;
2617 EMPTY_SWITCH_DEFAULT_CASE();
2618 }
2619 /* Under ASAN the compiler somehow doesn't realise that the switch block always returns */
2620 return FAILURE;
2621 } /* }}} */
2622
php_cli_server_send_event(php_cli_server * server,php_cli_server_client * client)2623 static zend_result php_cli_server_send_event(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2624 {
2625 if (client->content_sender_initialized) {
2626 if (client->file_fd >= 0 && !client->content_sender.buffer.first) {
2627 size_t nbytes_read;
2628 if (!php_cli_server_content_sender_pull(&client->content_sender, client->file_fd, &nbytes_read)) {
2629 php_cli_server_close_connection(server, client);
2630 return FAILURE;
2631 }
2632 if (nbytes_read == 0) {
2633 close(client->file_fd);
2634 client->file_fd = -1;
2635 }
2636 }
2637
2638 size_t nbytes_sent;
2639 int err = php_cli_server_content_sender_send(&client->content_sender, client->sock, &nbytes_sent);
2640 if (err && err != SOCK_EAGAIN) {
2641 php_cli_server_close_connection(server, client);
2642 return FAILURE;
2643 }
2644
2645 if (!client->content_sender.buffer.first && client->file_fd < 0) {
2646 php_cli_server_close_connection(server, client);
2647 }
2648 }
2649 return SUCCESS;
2650 }
2651 /* }}} */
2652
2653 typedef struct php_cli_server_do_event_for_each_fd_callback_params {
2654 php_cli_server *server;
2655 zend_result(*rhandler)(php_cli_server*, php_cli_server_client*);
2656 zend_result(*whandler)(php_cli_server*, php_cli_server_client*);
2657 } php_cli_server_do_event_for_each_fd_callback_params;
2658
2659 // TODO return FAILURE on failure???
php_cli_server_do_event_for_each_fd_callback(void * _params,php_socket_t fd,int event)2660 static zend_result php_cli_server_do_event_for_each_fd_callback(void *_params, php_socket_t fd, int event) /* {{{ */
2661 {
2662 php_cli_server_do_event_for_each_fd_callback_params *params = _params;
2663 php_cli_server *server = params->server;
2664 if (server->server_sock == fd) {
2665 php_cli_server_client *client = NULL;
2666 php_socket_t client_sock;
2667 socklen_t socklen = server->socklen;
2668 struct sockaddr *sa = pemalloc(server->socklen, 1);
2669 client_sock = accept(server->server_sock, sa, &socklen);
2670 if (!ZEND_VALID_SOCKET(client_sock)) {
2671 int err = php_socket_errno();
2672 if (err != SOCK_EAGAIN && php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
2673 char *errstr = php_socket_strerror(php_socket_errno(), NULL, 0);
2674 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR,
2675 "Failed to accept a client (reason: %s)", errstr);
2676 efree(errstr);
2677 }
2678 pefree(sa, 1);
2679 return SUCCESS;
2680 }
2681 if (SUCCESS != php_set_sock_blocking(client_sock, 0)) {
2682 pefree(sa, 1);
2683 closesocket(client_sock);
2684 return SUCCESS;
2685 }
2686 client = pemalloc(sizeof(php_cli_server_client), 1);
2687
2688 php_cli_server_client_ctor(client, server, client_sock, sa, socklen);
2689
2690 php_cli_server_logf(PHP_CLI_SERVER_LOG_MESSAGE, "%s Accepted", ZSTR_VAL(client->addr_str));
2691
2692 zend_hash_index_update_ptr(&server->clients, client_sock, client);
2693
2694 php_cli_server_poller_add(&server->poller, POLLIN, client->sock);
2695 } else {
2696 php_cli_server_client *client;
2697 if (NULL != (client = zend_hash_index_find_ptr(&server->clients, fd))) {
2698 if (event & POLLIN) {
2699 params->rhandler(server, client);
2700 }
2701 if (event & POLLOUT) {
2702 params->whandler(server, client);
2703 }
2704 }
2705 }
2706 return SUCCESS;
2707 } /* }}} */
2708
php_cli_server_do_event_for_each_fd(php_cli_server * server,zend_result (* rhandler)(php_cli_server *,php_cli_server_client *),zend_result (* whandler)(php_cli_server *,php_cli_server_client *))2709 static void php_cli_server_do_event_for_each_fd(php_cli_server *server,
2710 zend_result(*rhandler)(php_cli_server*, php_cli_server_client*),
2711 zend_result(*whandler)(php_cli_server*, php_cli_server_client*)) /* {{{ */
2712 {
2713 php_cli_server_do_event_for_each_fd_callback_params params = {
2714 server,
2715 rhandler,
2716 whandler
2717 };
2718
2719 php_cli_server_poller_iter_on_active(&server->poller, ¶ms, php_cli_server_do_event_for_each_fd_callback);
2720 } /* }}} */
2721
2722 // TODO Return value of function is not used
php_cli_server_do_event_loop(php_cli_server * server)2723 static zend_result php_cli_server_do_event_loop(php_cli_server *server) /* {{{ */
2724 {
2725 zend_result retval = SUCCESS;
2726 while (server->is_running) {
2727 struct timeval tv = { 1, 0 };
2728 int n = php_cli_server_poller_poll(&server->poller, &tv);
2729 if (n > 0) {
2730 php_cli_server_do_event_for_each_fd(server,
2731 php_cli_server_recv_event_read_request,
2732 php_cli_server_send_event);
2733 } else if (n == 0) {
2734 /* do nothing */
2735 } else {
2736 int err = php_socket_errno();
2737 if (err != SOCK_EINTR) {
2738 if (php_cli_server_log_level >= PHP_CLI_SERVER_LOG_ERROR) {
2739 char *errstr = php_socket_strerror(err, NULL, 0);
2740 php_cli_server_logf(PHP_CLI_SERVER_LOG_ERROR, "%s", errstr);
2741 efree(errstr);
2742 }
2743 retval = FAILURE;
2744 goto out;
2745 }
2746 }
2747 }
2748 out:
2749 return retval;
2750 } /* }}} */
2751
2752 static php_cli_server server;
2753
php_cli_server_sigint_handler(int sig)2754 static void php_cli_server_sigint_handler(int sig) /* {{{ */
2755 {
2756 server.is_running = 0;
2757 }
2758 /* }}} */
2759
2760 /* Returns status code */
do_cli_server(int argc,char ** argv)2761 int do_cli_server(int argc, char **argv) /* {{{ */
2762 {
2763 char *php_optarg = NULL;
2764 int php_optind = 1;
2765 int c;
2766 const char *server_bind_address = NULL;
2767 extern const opt_struct OPTIONS[];
2768 const char *document_root = NULL;
2769 #ifdef PHP_WIN32
2770 char document_root_tmp[MAXPATHLEN];
2771 size_t k;
2772 #endif
2773 const char *router = NULL;
2774 char document_root_buf[MAXPATHLEN];
2775
2776 while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2))!=-1) {
2777 switch (c) {
2778 case 'S':
2779 server_bind_address = php_optarg;
2780 break;
2781 case 't':
2782 #ifndef PHP_WIN32
2783 document_root = php_optarg;
2784 #else
2785 k = strlen(php_optarg);
2786 if (k + 1 > MAXPATHLEN) {
2787 fprintf(stderr, "Document root path is too long.\n");
2788 return 1;
2789 }
2790 memmove(document_root_tmp, php_optarg, k + 1);
2791 /* Clean out any trailing garbage that might have been passed
2792 from a batch script. */
2793 do {
2794 document_root_tmp[k] = '\0';
2795 k--;
2796 } while ('"' == document_root_tmp[k] || ' ' == document_root_tmp[k]);
2797 document_root = document_root_tmp;
2798 #endif
2799 break;
2800 case 'q':
2801 if (php_cli_server_log_level > 1) {
2802 php_cli_server_log_level--;
2803 }
2804 break;
2805 }
2806 }
2807
2808 if (document_root) {
2809 zend_stat_t sb = {0};
2810
2811 if (php_sys_stat(document_root, &sb)) {
2812 fprintf(stderr, "Directory %s does not exist.\n", document_root);
2813 return 1;
2814 }
2815 if (!S_ISDIR(sb.st_mode)) {
2816 fprintf(stderr, "%s is not a directory.\n", document_root);
2817 return 1;
2818 }
2819 if (VCWD_REALPATH(document_root, document_root_buf)) {
2820 document_root = document_root_buf;
2821 }
2822 } else {
2823 char *ret = NULL;
2824
2825 #if HAVE_GETCWD
2826 ret = VCWD_GETCWD(document_root_buf, MAXPATHLEN);
2827 #elif HAVE_GETWD
2828 ret = VCWD_GETWD(document_root_buf);
2829 #endif
2830 document_root = ret ? document_root_buf: ".";
2831 }
2832
2833 if (argc > php_optind) {
2834 router = argv[php_optind];
2835 }
2836
2837 if (FAILURE == php_cli_server_ctor(&server, server_bind_address, document_root, router)) {
2838 return 1;
2839 }
2840 sapi_module.phpinfo_as_text = 0;
2841
2842 {
2843 bool ipv6 = strchr(server.host, ':');
2844 php_cli_server_logf(
2845 PHP_CLI_SERVER_LOG_PROCESS,
2846 "PHP %s Development Server (http://%s%s%s:%d) started",
2847 PHP_VERSION, ipv6 ? "[" : "", server.host,
2848 ipv6 ? "]" : "", server.port);
2849 }
2850
2851 #if defined(SIGINT)
2852 signal(SIGINT, php_cli_server_sigint_handler);
2853 #endif
2854
2855 #if defined(SIGPIPE)
2856 signal(SIGPIPE, SIG_IGN);
2857 #endif
2858
2859 zend_signal_init();
2860
2861 php_cli_server_do_event_loop(&server);
2862 php_cli_server_dtor(&server);
2863 return 0;
2864 } /* }}} */
2865