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