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