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