1 /*
2 +----------------------------------------------------------------------+
3 | PHP Version 7 |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 1997-2017 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 /* $Id: php_cli.c 306938 2011-01-01 02:17:06Z felipe $ */
21
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <fcntl.h>
25 #include <assert.h>
26
27 #ifdef PHP_WIN32
28 # include <process.h>
29 # include <io.h>
30 # include "win32/time.h"
31 # include "win32/signal.h"
32 # include "win32/php_registry.h"
33 # include <sys/timeb.h>
34 #else
35 # include "php_config.h"
36 #endif
37
38 #ifdef __riscos__
39 #include <unixlib/local.h>
40 #endif
41
42
43 #if HAVE_TIME_H
44 #include <time.h>
45 #endif
46 #if HAVE_SYS_TIME_H
47 #include <sys/time.h>
48 #endif
49 #if HAVE_UNISTD_H
50 #include <unistd.h>
51 #endif
52 #if HAVE_SIGNAL_H
53 #include <signal.h>
54 #endif
55 #if HAVE_SETLOCALE
56 #include <locale.h>
57 #endif
58 #if HAVE_DLFCN_H
59 #include <dlfcn.h>
60 #endif
61
62 #include "SAPI.h"
63 #include "php.h"
64 #include "php_ini.h"
65 #include "php_main.h"
66 #include "php_globals.h"
67 #include "php_variables.h"
68 #include "zend_hash.h"
69 #include "zend_modules.h"
70 #include "fopen_wrappers.h"
71 #include "http_status_codes.h"
72
73 #include "zend_compile.h"
74 #include "zend_execute.h"
75 #include "zend_highlight.h"
76 #include "zend_exceptions.h"
77
78 #include "php_getopt.h"
79
80 #ifndef PHP_WIN32
81 # define php_select(m, r, w, e, t) select(m, r, w, e, t)
82 # define SOCK_EINVAL EINVAL
83 # define SOCK_EAGAIN EAGAIN
84 # define SOCK_EINTR EINTR
85 # define SOCK_EADDRINUSE EADDRINUSE
86 #else
87 # include "win32/select.h"
88 # define SOCK_EINVAL WSAEINVAL
89 # define SOCK_EAGAIN WSAEWOULDBLOCK
90 # define SOCK_EINTR WSAEINTR
91 # define SOCK_EADDRINUSE WSAEADDRINUSE
92 #endif
93
94 #include "ext/standard/file.h" /* for php_set_sock_blocking() :-( */
95 #include "zend_smart_str.h"
96 #include "ext/standard/html.h"
97 #include "ext/standard/url.h" /* for php_raw_url_decode() */
98 #include "ext/standard/php_string.h" /* for php_dirname() */
99 #include "php_network.h"
100
101 #include "php_http_parser.h"
102 #include "php_cli_server.h"
103 #include "mime_type_map.h"
104
105 #include "php_cli_process_title.h"
106
107 #define OUTPUT_NOT_CHECKED -1
108 #define OUTPUT_IS_TTY 1
109 #define OUTPUT_NOT_TTY 0
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 #if HAVE_UNISTD_H
213 static int php_cli_output_is_tty = OUTPUT_NOT_CHECKED;
214 #endif
215
216 static const char php_cli_server_request_error_unexpected_eof[] = "Unexpected EOF";
217
218 static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len);
219 static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len);
220 static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk);
221 static void php_cli_server_logf(const char *format, ...);
222 static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message);
223
224 ZEND_DECLARE_MODULE_GLOBALS(cli_server);
225
226 /* {{{ static char php_cli_server_css[]
227 * copied from ext/standard/info.c
228 */
229 static const char php_cli_server_css[] = "<style>\n" \
230 "body { background-color: #fcfcfc; color: #333333; margin: 0; padding:0; }\n" \
231 "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" \
232 "h1, p { padding-left: 10px; }\n" \
233 "code.url { background-color: #eeeeee; font-family:monospace; padding:0 2px;}\n" \
234 "</style>\n";
235 /* }}} */
236
237 #ifdef PHP_WIN32
php_cli_server_get_system_time(char * buf)238 int php_cli_server_get_system_time(char *buf) {
239 struct _timeb system_time;
240 errno_t err;
241
242 if (buf == NULL) {
243 return -1;
244 }
245
246 _ftime(&system_time);
247 err = ctime_s(buf, 52, &(system_time.time) );
248 if (err) {
249 return -1;
250 }
251 return 0;
252 }
253 #else
php_cli_server_get_system_time(char * buf)254 int php_cli_server_get_system_time(char *buf) {
255 struct timeval tv;
256 struct tm tm;
257
258 gettimeofday(&tv, NULL);
259
260 /* TODO: should be checked for NULL tm/return vaue */
261 php_localtime_r(&tv.tv_sec, &tm);
262 php_asctime_r(&tm, buf);
263 return 0;
264 }
265 #endif
266
char_ptr_dtor_p(zval * zv)267 static void char_ptr_dtor_p(zval *zv) /* {{{ */
268 {
269 pefree(Z_PTR_P(zv), 1);
270 } /* }}} */
271
get_last_error()272 static char *get_last_error() /* {{{ */
273 {
274 return pestrdup(strerror(errno), 1);
275 } /* }}} */
276
status_comp(const void * a,const void * b)277 static int status_comp(const void *a, const void *b) /* {{{ */
278 {
279 const http_response_status_code_pair *pa = (const http_response_status_code_pair *) a;
280 const http_response_status_code_pair *pb = (const http_response_status_code_pair *) b;
281
282 if (pa->code < pb->code) {
283 return -1;
284 } else if (pa->code > pb->code) {
285 return 1;
286 }
287
288 return 0;
289 } /* }}} */
290
get_status_string(int code)291 static const char *get_status_string(int code) /* {{{ */
292 {
293 http_response_status_code_pair needle = {code, NULL},
294 *result = NULL;
295
296 result = bsearch(&needle, http_status_map, http_status_map_len, sizeof(needle), status_comp);
297
298 if (result) {
299 return result->str;
300 }
301
302 /* Returning NULL would require complicating append_http_status_line() to
303 * not segfault in that case, so let's just return a placeholder, since RFC
304 * 2616 requires a reason phrase. This is basically what a lot of other Web
305 * servers do in this case anyway. */
306 return "Unknown Status Code";
307 } /* }}} */
308
get_template_string(int code)309 static const char *get_template_string(int code) /* {{{ */
310 {
311 size_t e = (sizeof(template_map) / sizeof(php_cli_server_http_response_status_code_pair));
312 size_t s = 0;
313
314 while (e != s) {
315 size_t c = MIN((e + s + 1) / 2, e - 1);
316 int d = template_map[c].code;
317 if (d > code) {
318 e = c;
319 } else if (d < code) {
320 s = c;
321 } else {
322 return template_map[c].str;
323 }
324 }
325 return NULL;
326 } /* }}} */
327
append_http_status_line(smart_str * buffer,int protocol_version,int response_code,int persistent)328 static void append_http_status_line(smart_str *buffer, int protocol_version, int response_code, int persistent) /* {{{ */
329 {
330 if (!response_code) {
331 response_code = 200;
332 }
333 smart_str_appendl_ex(buffer, "HTTP", 4, persistent);
334 smart_str_appendc_ex(buffer, '/', persistent);
335 smart_str_append_long_ex(buffer, protocol_version / 100, persistent);
336 smart_str_appendc_ex(buffer, '.', persistent);
337 smart_str_append_long_ex(buffer, protocol_version % 100, persistent);
338 smart_str_appendc_ex(buffer, ' ', persistent);
339 smart_str_append_long_ex(buffer, response_code, persistent);
340 smart_str_appendc_ex(buffer, ' ', persistent);
341 smart_str_appends_ex(buffer, get_status_string(response_code), persistent);
342 smart_str_appendl_ex(buffer, "\r\n", 2, persistent);
343 } /* }}} */
344
append_essential_headers(smart_str * buffer,php_cli_server_client * client,int persistent)345 static void append_essential_headers(smart_str* buffer, php_cli_server_client *client, int persistent) /* {{{ */
346 {
347 {
348 char *val;
349 if (NULL != (val = zend_hash_str_find_ptr(&client->request.headers, "host", sizeof("host")-1))) {
350 smart_str_appendl_ex(buffer, "Host", sizeof("Host") - 1, persistent);
351 smart_str_appendl_ex(buffer, ": ", sizeof(": ") - 1, persistent);
352 smart_str_appends_ex(buffer, val, persistent);
353 smart_str_appendl_ex(buffer, "\r\n", 2, persistent);
354 }
355 }
356 smart_str_appendl_ex(buffer, "Connection: close\r\n", sizeof("Connection: close\r\n") - 1, persistent);
357 } /* }}} */
358
get_mime_type(const php_cli_server * server,const char * ext,size_t ext_len)359 static const char *get_mime_type(const php_cli_server *server, const char *ext, size_t ext_len) /* {{{ */
360 {
361 return (const char*)zend_hash_str_find_ptr(&server->extension_mime_types, ext, ext_len);
362 } /* }}} */
363
PHP_FUNCTION(apache_request_headers)364 PHP_FUNCTION(apache_request_headers) /* {{{ */
365 {
366 php_cli_server_client *client;
367 HashTable *headers;
368 zend_string *key;
369 char *value;
370 zval tmp;
371
372 if (zend_parse_parameters_none() == FAILURE) {
373 return;
374 }
375
376 client = SG(server_context);
377 headers = &client->request.headers_original_case;
378
379 array_init_size(return_value, zend_hash_num_elements(headers));
380
381 ZEND_HASH_FOREACH_STR_KEY_PTR(headers, key, value) {
382 ZVAL_STRING(&tmp, value);
383 zend_symtable_update(Z_ARRVAL_P(return_value), key, &tmp);
384 } ZEND_HASH_FOREACH_END();
385 }
386 /* }}} */
387
add_response_header(sapi_header_struct * h,zval * return_value)388 static void add_response_header(sapi_header_struct *h, zval *return_value) /* {{{ */
389 {
390 char *s, *p;
391 ptrdiff_t len;
392 ALLOCA_FLAG(use_heap)
393
394 if (h->header_len > 0) {
395 p = strchr(h->header, ':');
396 len = p - h->header;
397 if (p && (len > 0)) {
398 while (len > 0 && (h->header[len-1] == ' ' || h->header[len-1] == '\t')) {
399 len--;
400 }
401 if (len) {
402 s = do_alloca(len + 1, use_heap);
403 memcpy(s, h->header, len);
404 s[len] = 0;
405 do {
406 p++;
407 } while (*p == ' ' || *p == '\t');
408 add_assoc_stringl_ex(return_value, s, (uint)len, p, h->header_len - (p - h->header));
409 free_alloca(s, use_heap);
410 }
411 }
412 }
413 }
414 /* }}} */
415
PHP_FUNCTION(apache_response_headers)416 PHP_FUNCTION(apache_response_headers) /* {{{ */
417 {
418 if (zend_parse_parameters_none() == FAILURE) {
419 return;
420 }
421
422 array_init(return_value);
423 zend_llist_apply_with_argument(&SG(sapi_headers).headers, (llist_apply_with_arg_func_t)add_response_header, return_value);
424 }
425 /* }}} */
426
427 /* {{{ cli_server module
428 */
429
cli_server_init_globals(zend_cli_server_globals * cg)430 static void cli_server_init_globals(zend_cli_server_globals *cg)
431 {
432 cg->color = 0;
433 }
434
435 PHP_INI_BEGIN()
436 STD_PHP_INI_BOOLEAN("cli_server.color", "0", PHP_INI_ALL, OnUpdateBool, color, zend_cli_server_globals, cli_server_globals)
PHP_INI_END()437 PHP_INI_END()
438
439 static PHP_MINIT_FUNCTION(cli_server)
440 {
441 ZEND_INIT_MODULE_GLOBALS(cli_server, cli_server_init_globals, NULL);
442 REGISTER_INI_ENTRIES();
443 return SUCCESS;
444 }
445
PHP_MSHUTDOWN_FUNCTION(cli_server)446 static PHP_MSHUTDOWN_FUNCTION(cli_server)
447 {
448 UNREGISTER_INI_ENTRIES();
449 return SUCCESS;
450 }
451
PHP_MINFO_FUNCTION(cli_server)452 static PHP_MINFO_FUNCTION(cli_server)
453 {
454 DISPLAY_INI_ENTRIES();
455 }
456
457 zend_module_entry cli_server_module_entry = {
458 STANDARD_MODULE_HEADER,
459 "cli_server",
460 NULL,
461 PHP_MINIT(cli_server),
462 PHP_MSHUTDOWN(cli_server),
463 NULL,
464 NULL,
465 PHP_MINFO(cli_server),
466 PHP_VERSION,
467 STANDARD_MODULE_PROPERTIES
468 };
469 /* }}} */
470
471 ZEND_BEGIN_ARG_INFO(arginfo_no_args, 0)
472 ZEND_END_ARG_INFO()
473
474 const zend_function_entry server_additional_functions[] = {
475 PHP_FE(cli_set_process_title, arginfo_cli_set_process_title)
476 PHP_FE(cli_get_process_title, arginfo_cli_get_process_title)
477 PHP_FE(apache_request_headers, arginfo_no_args)
478 PHP_FE(apache_response_headers, arginfo_no_args)
479 PHP_FALIAS(getallheaders, apache_request_headers, arginfo_no_args)
480 {NULL, NULL, NULL}
481 };
482
sapi_cli_server_startup(sapi_module_struct * sapi_module)483 static int sapi_cli_server_startup(sapi_module_struct *sapi_module) /* {{{ */
484 {
485 if (php_module_startup(sapi_module, &cli_server_module_entry, 1) == FAILURE) {
486 return FAILURE;
487 }
488 return SUCCESS;
489 } /* }}} */
490
sapi_cli_server_ub_write(const char * str,size_t str_length)491 static size_t sapi_cli_server_ub_write(const char *str, size_t str_length) /* {{{ */
492 {
493 php_cli_server_client *client = SG(server_context);
494 if (!client) {
495 return 0;
496 }
497 return php_cli_server_client_send_through(client, str, str_length);
498 } /* }}} */
499
sapi_cli_server_flush(void * server_context)500 static void sapi_cli_server_flush(void *server_context) /* {{{ */
501 {
502 php_cli_server_client *client = server_context;
503
504 if (!client) {
505 return;
506 }
507
508 if (!ZEND_VALID_SOCKET(client->sock)) {
509 php_handle_aborted_connection();
510 return;
511 }
512
513 if (!SG(headers_sent)) {
514 sapi_send_headers();
515 SG(headers_sent) = 1;
516 }
517 } /* }}} */
518
sapi_cli_server_discard_headers(sapi_headers_struct * sapi_headers)519 static int sapi_cli_server_discard_headers(sapi_headers_struct *sapi_headers) /* {{{ */{
520 return SAPI_HEADER_SENT_SUCCESSFULLY;
521 }
522 /* }}} */
523
sapi_cli_server_send_headers(sapi_headers_struct * sapi_headers)524 static int sapi_cli_server_send_headers(sapi_headers_struct *sapi_headers) /* {{{ */
525 {
526 php_cli_server_client *client = SG(server_context);
527 smart_str buffer = { 0 };
528 sapi_header_struct *h;
529 zend_llist_position pos;
530
531 if (client == NULL || SG(request_info).no_headers) {
532 return SAPI_HEADER_SENT_SUCCESSFULLY;
533 }
534
535 if (SG(sapi_headers).http_status_line) {
536 smart_str_appends(&buffer, SG(sapi_headers).http_status_line);
537 smart_str_appendl(&buffer, "\r\n", 2);
538 } else {
539 append_http_status_line(&buffer, client->request.protocol_version, SG(sapi_headers).http_response_code, 0);
540 }
541
542 append_essential_headers(&buffer, client, 0);
543
544 h = (sapi_header_struct*)zend_llist_get_first_ex(&sapi_headers->headers, &pos);
545 while (h) {
546 if (h->header_len) {
547 smart_str_appendl(&buffer, h->header, h->header_len);
548 smart_str_appendl(&buffer, "\r\n", 2);
549 }
550 h = (sapi_header_struct*)zend_llist_get_next_ex(&sapi_headers->headers, &pos);
551 }
552 smart_str_appendl(&buffer, "\r\n", 2);
553
554 php_cli_server_client_send_through(client, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
555
556 smart_str_free(&buffer);
557 return SAPI_HEADER_SENT_SUCCESSFULLY;
558 }
559 /* }}} */
560
sapi_cli_server_read_cookies(void)561 static char *sapi_cli_server_read_cookies(void) /* {{{ */
562 {
563 php_cli_server_client *client = SG(server_context);
564 char *val;
565 if (NULL == (val = zend_hash_str_find_ptr(&client->request.headers, "cookie", sizeof("cookie")-1))) {
566 return NULL;
567 }
568 return val;
569 } /* }}} */
570
sapi_cli_server_read_post(char * buf,size_t count_bytes)571 static size_t sapi_cli_server_read_post(char *buf, size_t count_bytes) /* {{{ */
572 {
573 php_cli_server_client *client = SG(server_context);
574 if (client->request.content) {
575 size_t content_len = client->request.content_len;
576 size_t nbytes_copied = MIN(client->post_read_offset + count_bytes, content_len) - client->post_read_offset;
577 memmove(buf, client->request.content + client->post_read_offset, nbytes_copied);
578 client->post_read_offset += nbytes_copied;
579 return nbytes_copied;
580 }
581 return 0;
582 } /* }}} */
583
sapi_cli_server_register_variable(zval * track_vars_array,const char * key,const char * val)584 static void sapi_cli_server_register_variable(zval *track_vars_array, const char *key, const char *val) /* {{{ */
585 {
586 char *new_val = (char *)val;
587 size_t new_val_len;
588
589 if (NULL == val) {
590 return;
591 }
592
593 if (sapi_module.input_filter(PARSE_SERVER, (char*)key, &new_val, strlen(val), &new_val_len)) {
594 php_register_variable_safe((char *)key, new_val, new_val_len, track_vars_array);
595 }
596 } /* }}} */
597
sapi_cli_server_register_entry_cb(char ** entry,int num_args,va_list args,zend_hash_key * hash_key)598 static int sapi_cli_server_register_entry_cb(char **entry, int num_args, va_list args, zend_hash_key *hash_key) /* {{{ */ {
599 zval *track_vars_array = va_arg(args, zval *);
600 if (hash_key->key) {
601 char *real_key, *key;
602 uint i;
603 key = estrndup(ZSTR_VAL(hash_key->key), ZSTR_LEN(hash_key->key));
604 for(i=0; i<ZSTR_LEN(hash_key->key); i++) {
605 if (key[i] == '-') {
606 key[i] = '_';
607 } else {
608 key[i] = toupper(key[i]);
609 }
610 }
611 spprintf(&real_key, 0, "%s_%s", "HTTP", key);
612 if (strcmp(key, "CONTENT_TYPE") == 0 || strcmp(key, "CONTENT_LENGTH") == 0) {
613 sapi_cli_server_register_variable(track_vars_array, key, *entry);
614 }
615 sapi_cli_server_register_variable(track_vars_array, real_key, *entry);
616 efree(key);
617 efree(real_key);
618 }
619
620 return ZEND_HASH_APPLY_KEEP;
621 }
622 /* }}} */
623
sapi_cli_server_register_variables(zval * track_vars_array)624 static void sapi_cli_server_register_variables(zval *track_vars_array) /* {{{ */
625 {
626 php_cli_server_client *client = SG(server_context);
627 sapi_cli_server_register_variable(track_vars_array, "DOCUMENT_ROOT", client->server->document_root);
628 {
629 char *tmp;
630 if ((tmp = strrchr(client->addr_str, ':'))) {
631 char addr[64], port[8];
632 strncpy(port, tmp + 1, 8);
633 port[7] = '\0';
634 strncpy(addr, client->addr_str, tmp - client->addr_str);
635 addr[tmp - client->addr_str] = '\0';
636 sapi_cli_server_register_variable(track_vars_array, "REMOTE_ADDR", addr);
637 sapi_cli_server_register_variable(track_vars_array, "REMOTE_PORT", port);
638 } else {
639 sapi_cli_server_register_variable(track_vars_array, "REMOTE_ADDR", client->addr_str);
640 }
641 }
642 {
643 char *tmp;
644 spprintf(&tmp, 0, "PHP %s Development Server", PHP_VERSION);
645 sapi_cli_server_register_variable(track_vars_array, "SERVER_SOFTWARE", tmp);
646 efree(tmp);
647 }
648 {
649 char *tmp;
650 spprintf(&tmp, 0, "HTTP/%d.%d", client->request.protocol_version / 100, client->request.protocol_version % 100);
651 sapi_cli_server_register_variable(track_vars_array, "SERVER_PROTOCOL", tmp);
652 efree(tmp);
653 }
654 sapi_cli_server_register_variable(track_vars_array, "SERVER_NAME", client->server->host);
655 {
656 char *tmp;
657 spprintf(&tmp, 0, "%i", client->server->port);
658 sapi_cli_server_register_variable(track_vars_array, "SERVER_PORT", tmp);
659 efree(tmp);
660 }
661
662 sapi_cli_server_register_variable(track_vars_array, "REQUEST_URI", client->request.request_uri);
663 sapi_cli_server_register_variable(track_vars_array, "REQUEST_METHOD", SG(request_info).request_method);
664 sapi_cli_server_register_variable(track_vars_array, "SCRIPT_NAME", client->request.vpath);
665 if (SG(request_info).path_translated) {
666 sapi_cli_server_register_variable(track_vars_array, "SCRIPT_FILENAME", SG(request_info).path_translated);
667 } else if (client->server->router) {
668 sapi_cli_server_register_variable(track_vars_array, "SCRIPT_FILENAME", client->server->router);
669 }
670 if (client->request.path_info) {
671 sapi_cli_server_register_variable(track_vars_array, "PATH_INFO", client->request.path_info);
672 }
673 if (client->request.path_info_len) {
674 char *tmp;
675 spprintf(&tmp, 0, "%s%s", client->request.vpath, client->request.path_info);
676 sapi_cli_server_register_variable(track_vars_array, "PHP_SELF", tmp);
677 efree(tmp);
678 } else {
679 sapi_cli_server_register_variable(track_vars_array, "PHP_SELF", client->request.vpath);
680 }
681 if (client->request.query_string) {
682 sapi_cli_server_register_variable(track_vars_array, "QUERY_STRING", client->request.query_string);
683 }
684 zend_hash_apply_with_arguments(&client->request.headers, (apply_func_args_t)sapi_cli_server_register_entry_cb, 1, track_vars_array);
685 } /* }}} */
686
sapi_cli_server_log_message(char * msg)687 static void sapi_cli_server_log_message(char *msg) /* {{{ */
688 {
689 char buf[52];
690
691 if (php_cli_server_get_system_time(buf) != 0) {
692 memmove(buf, "unknown time, can't be fetched", sizeof("unknown time, can't be fetched"));
693 } else {
694 size_t l = strlen(buf);
695 if (l > 0) {
696 buf[l - 1] = '\0';
697 } else {
698 memmove(buf, "unknown", sizeof("unknown"));
699 }
700 }
701 fprintf(stderr, "[%s] %s\n", buf, msg);
702 } /* }}} */
703
704 /* {{{ sapi_module_struct cli_server_sapi_module
705 */
706 sapi_module_struct cli_server_sapi_module = {
707 "cli-server", /* name */
708 "Built-in HTTP server", /* pretty name */
709
710 sapi_cli_server_startup, /* startup */
711 php_module_shutdown_wrapper, /* shutdown */
712
713 NULL, /* activate */
714 NULL, /* deactivate */
715
716 sapi_cli_server_ub_write, /* unbuffered write */
717 sapi_cli_server_flush, /* flush */
718 NULL, /* get uid */
719 NULL, /* getenv */
720
721 php_error, /* error handler */
722
723 NULL, /* header handler */
724 sapi_cli_server_send_headers, /* send headers handler */
725 NULL, /* send header handler */
726
727 sapi_cli_server_read_post, /* read POST data */
728 sapi_cli_server_read_cookies, /* read Cookies */
729
730 sapi_cli_server_register_variables, /* register server variables */
731 sapi_cli_server_log_message, /* Log message */
732 NULL, /* Get request time */
733 NULL, /* Child terminate */
734
735 STANDARD_SAPI_MODULE_PROPERTIES
736 }; /* }}} */
737
php_cli_server_poller_ctor(php_cli_server_poller * poller)738 static int php_cli_server_poller_ctor(php_cli_server_poller *poller) /* {{{ */
739 {
740 FD_ZERO(&poller->rfds);
741 FD_ZERO(&poller->wfds);
742 poller->max_fd = -1;
743 return SUCCESS;
744 } /* }}} */
745
php_cli_server_poller_add(php_cli_server_poller * poller,int mode,php_socket_t fd)746 static void php_cli_server_poller_add(php_cli_server_poller *poller, int mode, php_socket_t fd) /* {{{ */
747 {
748 if (mode & POLLIN) {
749 PHP_SAFE_FD_SET(fd, &poller->rfds);
750 }
751 if (mode & POLLOUT) {
752 PHP_SAFE_FD_SET(fd, &poller->wfds);
753 }
754 if (fd > poller->max_fd) {
755 poller->max_fd = fd;
756 }
757 } /* }}} */
758
php_cli_server_poller_remove(php_cli_server_poller * poller,int mode,php_socket_t fd)759 static void php_cli_server_poller_remove(php_cli_server_poller *poller, int mode, php_socket_t fd) /* {{{ */
760 {
761 if (mode & POLLIN) {
762 PHP_SAFE_FD_CLR(fd, &poller->rfds);
763 }
764 if (mode & POLLOUT) {
765 PHP_SAFE_FD_CLR(fd, &poller->wfds);
766 }
767 #ifndef PHP_WIN32
768 if (fd == poller->max_fd) {
769 while (fd > 0) {
770 fd--;
771 if (PHP_SAFE_FD_ISSET(fd, &poller->rfds) || PHP_SAFE_FD_ISSET(fd, &poller->wfds)) {
772 break;
773 }
774 }
775 poller->max_fd = fd;
776 }
777 #endif
778 } /* }}} */
779
php_cli_server_poller_poll(php_cli_server_poller * poller,struct timeval * tv)780 static int php_cli_server_poller_poll(php_cli_server_poller *poller, struct timeval *tv) /* {{{ */
781 {
782 memmove(&poller->active.rfds, &poller->rfds, sizeof(poller->rfds));
783 memmove(&poller->active.wfds, &poller->wfds, sizeof(poller->wfds));
784 return php_select(poller->max_fd + 1, &poller->active.rfds, &poller->active.wfds, NULL, tv);
785 } /* }}} */
786
php_cli_server_poller_iter_on_active(php_cli_server_poller * poller,void * opaque,int (* callback)(void *,php_socket_t fd,int events))787 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)) /* {{{ */
788 {
789 int retval = SUCCESS;
790 #ifdef PHP_WIN32
791 struct socket_entry {
792 SOCKET fd;
793 int events;
794 } entries[FD_SETSIZE * 2];
795 size_t i;
796 struct socket_entry *n = entries, *m;
797
798 for (i = 0; i < poller->active.rfds.fd_count; i++) {
799 n->events = POLLIN;
800 n->fd = poller->active.rfds.fd_array[i];
801 n++;
802 }
803
804 m = n;
805 for (i = 0; i < poller->active.wfds.fd_count; i++) {
806 struct socket_entry *e;
807 SOCKET fd = poller->active.wfds.fd_array[i];
808 for (e = entries; e < m; e++) {
809 if (e->fd == fd) {
810 e->events |= POLLOUT;
811 }
812 }
813 if (e == m) {
814 assert(n < entries + FD_SETSIZE * 2);
815 n->events = POLLOUT;
816 n->fd = fd;
817 n++;
818 }
819 }
820
821 {
822 struct socket_entry *e = entries;
823 for (; e < n; e++) {
824 if (SUCCESS != callback(opaque, e->fd, e->events)) {
825 retval = FAILURE;
826 }
827 }
828 }
829
830 #else
831 php_socket_t fd;
832 const php_socket_t max_fd = poller->max_fd;
833
834 for (fd=0 ; fd<=max_fd ; fd++) {
835 if (PHP_SAFE_FD_ISSET(fd, &poller->active.rfds)) {
836 if (SUCCESS != callback(opaque, fd, POLLIN)) {
837 retval = FAILURE;
838 }
839 }
840 if (PHP_SAFE_FD_ISSET(fd, &poller->active.wfds)) {
841 if (SUCCESS != callback(opaque, fd, POLLOUT)) {
842 retval = FAILURE;
843 }
844 }
845 }
846 #endif
847 return retval;
848 } /* }}} */
849
php_cli_server_chunk_size(const php_cli_server_chunk * chunk)850 static size_t php_cli_server_chunk_size(const php_cli_server_chunk *chunk) /* {{{ */
851 {
852 switch (chunk->type) {
853 case PHP_CLI_SERVER_CHUNK_HEAP:
854 return chunk->data.heap.len;
855 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
856 return chunk->data.immortal.len;
857 }
858 return 0;
859 } /* }}} */
860
php_cli_server_chunk_dtor(php_cli_server_chunk * chunk)861 static void php_cli_server_chunk_dtor(php_cli_server_chunk *chunk) /* {{{ */
862 {
863 switch (chunk->type) {
864 case PHP_CLI_SERVER_CHUNK_HEAP:
865 if (chunk->data.heap.block != chunk) {
866 pefree(chunk->data.heap.block, 1);
867 }
868 break;
869 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
870 break;
871 }
872 } /* }}} */
873
php_cli_server_buffer_dtor(php_cli_server_buffer * buffer)874 static void php_cli_server_buffer_dtor(php_cli_server_buffer *buffer) /* {{{ */
875 {
876 php_cli_server_chunk *chunk, *next;
877 for (chunk = buffer->first; chunk; chunk = next) {
878 next = chunk->next;
879 php_cli_server_chunk_dtor(chunk);
880 pefree(chunk, 1);
881 }
882 } /* }}} */
883
php_cli_server_buffer_ctor(php_cli_server_buffer * buffer)884 static void php_cli_server_buffer_ctor(php_cli_server_buffer *buffer) /* {{{ */
885 {
886 buffer->first = NULL;
887 buffer->last = NULL;
888 } /* }}} */
889
php_cli_server_buffer_append(php_cli_server_buffer * buffer,php_cli_server_chunk * chunk)890 static void php_cli_server_buffer_append(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */
891 {
892 php_cli_server_chunk *last;
893 for (last = chunk; last->next; last = last->next);
894 if (!buffer->last) {
895 buffer->first = chunk;
896 } else {
897 buffer->last->next = chunk;
898 }
899 buffer->last = last;
900 } /* }}} */
901
php_cli_server_buffer_prepend(php_cli_server_buffer * buffer,php_cli_server_chunk * chunk)902 static void php_cli_server_buffer_prepend(php_cli_server_buffer *buffer, php_cli_server_chunk *chunk) /* {{{ */
903 {
904 php_cli_server_chunk *last;
905 for (last = chunk; last->next; last = last->next);
906 last->next = buffer->first;
907 if (!buffer->last) {
908 buffer->last = last;
909 }
910 buffer->first = chunk;
911 } /* }}} */
912
php_cli_server_buffer_size(const php_cli_server_buffer * buffer)913 static size_t php_cli_server_buffer_size(const php_cli_server_buffer *buffer) /* {{{ */
914 {
915 php_cli_server_chunk *chunk;
916 size_t retval = 0;
917 for (chunk = buffer->first; chunk; chunk = chunk->next) {
918 retval += php_cli_server_chunk_size(chunk);
919 }
920 return retval;
921 } /* }}} */
922
php_cli_server_chunk_immortal_new(const char * buf,size_t len)923 static php_cli_server_chunk *php_cli_server_chunk_immortal_new(const char *buf, size_t len) /* {{{ */
924 {
925 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1);
926 if (!chunk) {
927 return NULL;
928 }
929
930 chunk->type = PHP_CLI_SERVER_CHUNK_IMMORTAL;
931 chunk->next = NULL;
932 chunk->data.immortal.p = buf;
933 chunk->data.immortal.len = len;
934 return chunk;
935 } /* }}} */
936
php_cli_server_chunk_heap_new(void * block,char * buf,size_t len)937 static php_cli_server_chunk *php_cli_server_chunk_heap_new(void *block, char *buf, size_t len) /* {{{ */
938 {
939 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk), 1);
940 if (!chunk) {
941 return NULL;
942 }
943
944 chunk->type = PHP_CLI_SERVER_CHUNK_HEAP;
945 chunk->next = NULL;
946 chunk->data.heap.block = block;
947 chunk->data.heap.p = buf;
948 chunk->data.heap.len = len;
949 return chunk;
950 } /* }}} */
951
php_cli_server_chunk_heap_new_self_contained(size_t len)952 static php_cli_server_chunk *php_cli_server_chunk_heap_new_self_contained(size_t len) /* {{{ */
953 {
954 php_cli_server_chunk *chunk = pemalloc(sizeof(php_cli_server_chunk) + len, 1);
955 if (!chunk) {
956 return NULL;
957 }
958
959 chunk->type = PHP_CLI_SERVER_CHUNK_HEAP;
960 chunk->next = NULL;
961 chunk->data.heap.block = chunk;
962 chunk->data.heap.p = (char *)(chunk + 1);
963 chunk->data.heap.len = len;
964 return chunk;
965 } /* }}} */
966
php_cli_server_content_sender_dtor(php_cli_server_content_sender * sender)967 static void php_cli_server_content_sender_dtor(php_cli_server_content_sender *sender) /* {{{ */
968 {
969 php_cli_server_buffer_dtor(&sender->buffer);
970 } /* }}} */
971
php_cli_server_content_sender_ctor(php_cli_server_content_sender * sender)972 static void php_cli_server_content_sender_ctor(php_cli_server_content_sender *sender) /* {{{ */
973 {
974 php_cli_server_buffer_ctor(&sender->buffer);
975 } /* }}} */
976
php_cli_server_content_sender_send(php_cli_server_content_sender * sender,php_socket_t fd,size_t * nbytes_sent_total)977 static int php_cli_server_content_sender_send(php_cli_server_content_sender *sender, php_socket_t fd, size_t *nbytes_sent_total) /* {{{ */
978 {
979 php_cli_server_chunk *chunk, *next;
980 size_t _nbytes_sent_total = 0;
981
982 for (chunk = sender->buffer.first; chunk; chunk = next) {
983 #ifdef PHP_WIN32
984 int nbytes_sent;
985 #else
986 ssize_t nbytes_sent;
987 #endif
988 next = chunk->next;
989
990 switch (chunk->type) {
991 case PHP_CLI_SERVER_CHUNK_HEAP:
992 #ifdef PHP_WIN32
993 nbytes_sent = send(fd, chunk->data.heap.p, (int)chunk->data.heap.len, 0);
994 #else
995 nbytes_sent = send(fd, chunk->data.heap.p, chunk->data.heap.len, 0);
996 #endif
997 if (nbytes_sent < 0) {
998 *nbytes_sent_total = _nbytes_sent_total;
999 return php_socket_errno();
1000 } else if (nbytes_sent == chunk->data.heap.len) {
1001 php_cli_server_chunk_dtor(chunk);
1002 pefree(chunk, 1);
1003 sender->buffer.first = next;
1004 if (!next) {
1005 sender->buffer.last = NULL;
1006 }
1007 } else {
1008 chunk->data.heap.p += nbytes_sent;
1009 chunk->data.heap.len -= nbytes_sent;
1010 }
1011 _nbytes_sent_total += nbytes_sent;
1012 break;
1013
1014 case PHP_CLI_SERVER_CHUNK_IMMORTAL:
1015 #ifdef PHP_WIN32
1016 nbytes_sent = send(fd, chunk->data.immortal.p, (int)chunk->data.immortal.len, 0);
1017 #else
1018 nbytes_sent = send(fd, chunk->data.immortal.p, chunk->data.immortal.len, 0);
1019 #endif
1020 if (nbytes_sent < 0) {
1021 *nbytes_sent_total = _nbytes_sent_total;
1022 return php_socket_errno();
1023 } else if (nbytes_sent == chunk->data.immortal.len) {
1024 php_cli_server_chunk_dtor(chunk);
1025 pefree(chunk, 1);
1026 sender->buffer.first = next;
1027 if (!next) {
1028 sender->buffer.last = NULL;
1029 }
1030 } else {
1031 chunk->data.immortal.p += nbytes_sent;
1032 chunk->data.immortal.len -= nbytes_sent;
1033 }
1034 _nbytes_sent_total += nbytes_sent;
1035 break;
1036 }
1037 }
1038 *nbytes_sent_total = _nbytes_sent_total;
1039 return 0;
1040 } /* }}} */
1041
php_cli_server_content_sender_pull(php_cli_server_content_sender * sender,int fd,size_t * nbytes_read)1042 static int php_cli_server_content_sender_pull(php_cli_server_content_sender *sender, int fd, size_t *nbytes_read) /* {{{ */
1043 {
1044 #ifdef PHP_WIN32
1045 int _nbytes_read;
1046 #else
1047 ssize_t _nbytes_read;
1048 #endif
1049 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(131072);
1050
1051 #ifdef PHP_WIN32
1052 _nbytes_read = read(fd, chunk->data.heap.p, (unsigned int)chunk->data.heap.len);
1053 #else
1054 _nbytes_read = read(fd, chunk->data.heap.p, chunk->data.heap.len);
1055 #endif
1056 if (_nbytes_read < 0) {
1057 char *errstr = get_last_error();
1058 php_cli_server_logf("%s", errstr);
1059 pefree(errstr, 1);
1060 php_cli_server_chunk_dtor(chunk);
1061 pefree(chunk, 1);
1062 return 1;
1063 }
1064 chunk->data.heap.len = _nbytes_read;
1065 php_cli_server_buffer_append(&sender->buffer, chunk);
1066 *nbytes_read = _nbytes_read;
1067 return 0;
1068 } /* }}} */
1069
1070 #if HAVE_UNISTD_H
php_cli_is_output_tty()1071 static int php_cli_is_output_tty() /* {{{ */
1072 {
1073 if (php_cli_output_is_tty == OUTPUT_NOT_CHECKED) {
1074 php_cli_output_is_tty = isatty(STDOUT_FILENO);
1075 }
1076 return php_cli_output_is_tty;
1077 } /* }}} */
1078 #endif
1079
php_cli_server_log_response(php_cli_server_client * client,int status,const char * message)1080 static void php_cli_server_log_response(php_cli_server_client *client, int status, const char *message) /* {{{ */
1081 {
1082 int color = 0, effective_status = status;
1083 char *basic_buf, *message_buf = "", *error_buf = "";
1084 zend_bool append_error_message = 0;
1085
1086 if (PG(last_error_message)) {
1087 switch (PG(last_error_type)) {
1088 case E_ERROR:
1089 case E_CORE_ERROR:
1090 case E_COMPILE_ERROR:
1091 case E_USER_ERROR:
1092 case E_PARSE:
1093 if (status == 200) {
1094 /* the status code isn't changed by a fatal error, so fake it */
1095 effective_status = 500;
1096 }
1097
1098 append_error_message = 1;
1099 break;
1100 }
1101 }
1102
1103 #if HAVE_UNISTD_H
1104 if (CLI_SERVER_G(color) && php_cli_is_output_tty() == OUTPUT_IS_TTY) {
1105 if (effective_status >= 500) {
1106 /* server error: red */
1107 color = 1;
1108 } else if (effective_status >= 400) {
1109 /* client error: yellow */
1110 color = 3;
1111 } else if (effective_status >= 200) {
1112 /* success: green */
1113 color = 2;
1114 }
1115 }
1116 #endif
1117
1118 /* basic */
1119 spprintf(&basic_buf, 0, "%s [%d]: %s", client->addr_str, status, client->request.request_uri);
1120 if (!basic_buf) {
1121 return;
1122 }
1123
1124 /* message */
1125 if (message) {
1126 spprintf(&message_buf, 0, " - %s", message);
1127 if (!message_buf) {
1128 efree(basic_buf);
1129 return;
1130 }
1131 }
1132
1133 /* error */
1134 if (append_error_message) {
1135 spprintf(&error_buf, 0, " - %s in %s on line %d", PG(last_error_message), PG(last_error_file), PG(last_error_lineno));
1136 if (!error_buf) {
1137 efree(basic_buf);
1138 if (message) {
1139 efree(message_buf);
1140 }
1141 return;
1142 }
1143 }
1144
1145 if (color) {
1146 php_cli_server_logf("\x1b[3%dm%s%s%s\x1b[0m", color, basic_buf, message_buf, error_buf);
1147 } else {
1148 php_cli_server_logf("%s%s%s", basic_buf, message_buf, error_buf);
1149 }
1150
1151 efree(basic_buf);
1152 if (message) {
1153 efree(message_buf);
1154 }
1155 if (append_error_message) {
1156 efree(error_buf);
1157 }
1158 } /* }}} */
1159
php_cli_server_logf(const char * format,...)1160 static void php_cli_server_logf(const char *format, ...) /* {{{ */
1161 {
1162 char *buf = NULL;
1163 va_list ap;
1164
1165 va_start(ap, format);
1166 vspprintf(&buf, 0, format, ap);
1167 va_end(ap);
1168
1169 if (!buf) {
1170 return;
1171 }
1172
1173 if (sapi_module.log_message) {
1174 sapi_module.log_message(buf);
1175 }
1176
1177 efree(buf);
1178 } /* }}} */
1179
php_network_listen_socket(const char * host,int * port,int socktype,int * af,socklen_t * socklen,zend_string ** errstr)1180 static php_socket_t php_network_listen_socket(const char *host, int *port, int socktype, int *af, socklen_t *socklen, zend_string **errstr) /* {{{ */
1181 {
1182 php_socket_t retval = SOCK_ERR;
1183 int err = 0;
1184 struct sockaddr *sa = NULL, **p, **sal;
1185
1186 int num_addrs = php_network_getaddresses(host, socktype, &sal, errstr);
1187 if (num_addrs == 0) {
1188 return -1;
1189 }
1190 for (p = sal; *p; p++) {
1191 if (sa) {
1192 pefree(sa, 1);
1193 sa = NULL;
1194 }
1195
1196 retval = socket((*p)->sa_family, socktype, 0);
1197 if (retval == SOCK_ERR) {
1198 continue;
1199 }
1200
1201 switch ((*p)->sa_family) {
1202 #if HAVE_GETADDRINFO && HAVE_IPV6
1203 case AF_INET6:
1204 sa = pemalloc(sizeof(struct sockaddr_in6), 1);
1205 if (!sa) {
1206 closesocket(retval);
1207 retval = SOCK_ERR;
1208 *errstr = NULL;
1209 goto out;
1210 }
1211 *(struct sockaddr_in6 *)sa = *(struct sockaddr_in6 *)*p;
1212 ((struct sockaddr_in6 *)sa)->sin6_port = htons(*port);
1213 *socklen = sizeof(struct sockaddr_in6);
1214 break;
1215 #endif
1216 case AF_INET:
1217 sa = pemalloc(sizeof(struct sockaddr_in), 1);
1218 if (!sa) {
1219 closesocket(retval);
1220 retval = SOCK_ERR;
1221 *errstr = NULL;
1222 goto out;
1223 }
1224 *(struct sockaddr_in *)sa = *(struct sockaddr_in *)*p;
1225 ((struct sockaddr_in *)sa)->sin_port = htons(*port);
1226 *socklen = sizeof(struct sockaddr_in);
1227 break;
1228 default:
1229 /* Unknown family */
1230 *socklen = 0;
1231 closesocket(retval);
1232 continue;
1233 }
1234
1235 #ifdef SO_REUSEADDR
1236 {
1237 int val = 1;
1238 setsockopt(retval, SOL_SOCKET, SO_REUSEADDR, (char*)&val, sizeof(val));
1239 }
1240 #endif
1241
1242 if (bind(retval, sa, *socklen) == SOCK_CONN_ERR) {
1243 err = php_socket_errno();
1244 if (err == SOCK_EINVAL || err == SOCK_EADDRINUSE) {
1245 goto out;
1246 }
1247 closesocket(retval);
1248 retval = SOCK_ERR;
1249 continue;
1250 }
1251 err = 0;
1252
1253 *af = sa->sa_family;
1254 if (*port == 0) {
1255 if (getsockname(retval, sa, socklen)) {
1256 err = php_socket_errno();
1257 goto out;
1258 }
1259 switch (sa->sa_family) {
1260 #if HAVE_GETADDRINFO && HAVE_IPV6
1261 case AF_INET6:
1262 *port = ntohs(((struct sockaddr_in6 *)sa)->sin6_port);
1263 break;
1264 #endif
1265 case AF_INET:
1266 *port = ntohs(((struct sockaddr_in *)sa)->sin_port);
1267 break;
1268 }
1269 }
1270
1271 break;
1272 }
1273
1274 if (retval == SOCK_ERR) {
1275 goto out;
1276 }
1277
1278 if (listen(retval, SOMAXCONN)) {
1279 err = php_socket_errno();
1280 goto out;
1281 }
1282
1283 out:
1284 if (sa) {
1285 pefree(sa, 1);
1286 }
1287 if (sal) {
1288 php_network_freeaddresses(sal);
1289 }
1290 if (err) {
1291 if (ZEND_VALID_SOCKET(retval)) {
1292 closesocket(retval);
1293 }
1294 if (errstr) {
1295 *errstr = php_socket_error_str(err);
1296 }
1297 return SOCK_ERR;
1298 }
1299 return retval;
1300 } /* }}} */
1301
php_cli_server_request_ctor(php_cli_server_request * req)1302 static int php_cli_server_request_ctor(php_cli_server_request *req) /* {{{ */
1303 {
1304 #ifdef ZTS
1305 ZEND_TSRMLS_CACHE_UPDATE();
1306 #endif
1307 req->protocol_version = 0;
1308 req->request_uri = NULL;
1309 req->request_uri_len = 0;
1310 req->vpath = NULL;
1311 req->vpath_len = 0;
1312 req->path_translated = NULL;
1313 req->path_translated_len = 0;
1314 req->path_info = NULL;
1315 req->path_info_len = 0;
1316 req->query_string = NULL;
1317 req->query_string_len = 0;
1318 zend_hash_init(&req->headers, 0, NULL, char_ptr_dtor_p, 1);
1319 zend_hash_init(&req->headers_original_case, 0, NULL, NULL, 1);
1320 req->content = NULL;
1321 req->content_len = 0;
1322 req->ext = NULL;
1323 req->ext_len = 0;
1324 return SUCCESS;
1325 } /* }}} */
1326
php_cli_server_request_dtor(php_cli_server_request * req)1327 static void php_cli_server_request_dtor(php_cli_server_request *req) /* {{{ */
1328 {
1329 if (req->request_uri) {
1330 pefree(req->request_uri, 1);
1331 }
1332 if (req->vpath) {
1333 pefree(req->vpath, 1);
1334 }
1335 if (req->path_translated) {
1336 pefree(req->path_translated, 1);
1337 }
1338 if (req->path_info) {
1339 pefree(req->path_info, 1);
1340 }
1341 if (req->query_string) {
1342 pefree(req->query_string, 1);
1343 }
1344 zend_hash_destroy(&req->headers);
1345 zend_hash_destroy(&req->headers_original_case);
1346 if (req->content) {
1347 pefree(req->content, 1);
1348 }
1349 } /* }}} */
1350
php_cli_server_request_translate_vpath(php_cli_server_request * request,const char * document_root,size_t document_root_len)1351 static void php_cli_server_request_translate_vpath(php_cli_server_request *request, const char *document_root, size_t document_root_len) /* {{{ */
1352 {
1353 zend_stat_t sb;
1354 static const char *index_files[] = { "index.php", "index.html", NULL };
1355 char *buf = safe_pemalloc(1, request->vpath_len, 1 + document_root_len + 1 + sizeof("index.html"), 1);
1356 char *p = buf, *prev_path = NULL, *q, *vpath;
1357 size_t prev_path_len = 0;
1358 int is_static_file = 0;
1359
1360 if (!buf) {
1361 return;
1362 }
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 (!zend_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 (!zend_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 uint 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 char *lc_header_name = zend_str_tolower_dup(client->current_header_name, client->current_header_name_len);
1585 zend_hash_str_add_ptr(&client->request.headers, lc_header_name, client->current_header_name_len, client->current_header_value);
1586 zend_hash_add_ptr(&client->request.headers_original_case, orig_header_name, client->current_header_value);
1587 efree(lc_header_name);
1588 zend_string_release(orig_header_name);
1589
1590 if (client->current_header_name_allocated) {
1591 pefree(client->current_header_name, 1);
1592 client->current_header_name_allocated = 0;
1593 }
1594 client->current_header_name = NULL;
1595 client->current_header_name_len = 0;
1596 client->current_header_value = NULL;
1597 client->current_header_value_len = 0;
1598 }
1599
php_cli_server_client_read_request_on_header_field(php_http_parser * parser,const char * at,size_t length)1600 static int php_cli_server_client_read_request_on_header_field(php_http_parser *parser, const char *at, size_t length)
1601 {
1602 php_cli_server_client *client = parser->data;
1603 switch (client->last_header_element) {
1604 case HEADER_VALUE:
1605 php_cli_server_client_save_header(client);
1606 /* break missing intentionally */
1607 case HEADER_NONE:
1608 client->current_header_name = (char *)at;
1609 client->current_header_name_len = length;
1610 break;
1611 case HEADER_FIELD:
1612 if (client->current_header_name_allocated) {
1613 size_t new_length = client->current_header_name_len + length;
1614 client->current_header_name = perealloc(client->current_header_name, new_length + 1, 1);
1615 memcpy(client->current_header_name + client->current_header_name_len, at, length);
1616 client->current_header_name[new_length] = '\0';
1617 client->current_header_name_len = new_length;
1618 } else {
1619 size_t new_length = client->current_header_name_len + length;
1620 char* field = pemalloc(new_length + 1, 1);
1621 memcpy(field, client->current_header_name, client->current_header_name_len);
1622 memcpy(field + client->current_header_name_len, at, length);
1623 field[new_length] = '\0';
1624 client->current_header_name = field;
1625 client->current_header_name_len = new_length;
1626 client->current_header_name_allocated = 1;
1627 }
1628 break;
1629 }
1630
1631 client->last_header_element = HEADER_FIELD;
1632 return 0;
1633 }
1634
php_cli_server_client_read_request_on_header_value(php_http_parser * parser,const char * at,size_t length)1635 static int php_cli_server_client_read_request_on_header_value(php_http_parser *parser, const char *at, size_t length)
1636 {
1637 php_cli_server_client *client = parser->data;
1638 switch (client->last_header_element) {
1639 case HEADER_FIELD:
1640 client->current_header_value = pestrndup(at, length, 1);
1641 client->current_header_value_len = length;
1642 break;
1643 case HEADER_VALUE:
1644 {
1645 size_t new_length = client->current_header_value_len + length;
1646 client->current_header_value = perealloc(client->current_header_value, new_length + 1, 1);
1647 memcpy(client->current_header_value + client->current_header_value_len, at, length);
1648 client->current_header_value[new_length] = '\0';
1649 client->current_header_value_len = new_length;
1650 }
1651 break;
1652 case HEADER_NONE:
1653 // can't happen
1654 assert(0);
1655 break;
1656 }
1657 client->last_header_element = HEADER_VALUE;
1658 return 0;
1659 }
1660
php_cli_server_client_read_request_on_headers_complete(php_http_parser * parser)1661 static int php_cli_server_client_read_request_on_headers_complete(php_http_parser *parser)
1662 {
1663 php_cli_server_client *client = parser->data;
1664 switch (client->last_header_element) {
1665 case HEADER_NONE:
1666 break;
1667 case HEADER_FIELD:
1668 client->current_header_value = pemalloc(1, 1);
1669 *client->current_header_value = '\0';
1670 client->current_header_value_len = 0;
1671 /* break missing intentionally */
1672 case HEADER_VALUE:
1673 php_cli_server_client_save_header(client);
1674 break;
1675 }
1676 client->last_header_element = HEADER_NONE;
1677 return 0;
1678 }
1679
php_cli_server_client_read_request_on_body(php_http_parser * parser,const char * at,size_t length)1680 static int php_cli_server_client_read_request_on_body(php_http_parser *parser, const char *at, size_t length)
1681 {
1682 php_cli_server_client *client = parser->data;
1683 if (!client->request.content) {
1684 client->request.content = pemalloc(parser->content_length, 1);
1685 if (!client->request.content) {
1686 return -1;
1687 }
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 != 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 if (!header_name) {
1764 return -1;
1765 }
1766 memmove(header_name, client->current_header_name, client->current_header_name_len);
1767 client->current_header_name = header_name;
1768 client->current_header_name_allocated = 1;
1769 }
1770 return client->request_read ? 1: 0;
1771 }
1772 /* }}} */
1773
php_cli_server_client_send_through(php_cli_server_client * client,const char * str,size_t str_len)1774 static size_t php_cli_server_client_send_through(php_cli_server_client *client, const char *str, size_t str_len) /* {{{ */
1775 {
1776 struct timeval tv = { 10, 0 };
1777 #ifdef PHP_WIN32
1778 int nbytes_left = (int)str_len;
1779 #else
1780 ssize_t nbytes_left = (ssize_t)str_len;
1781 #endif
1782 do {
1783 #ifdef PHP_WIN32
1784 int nbytes_sent;
1785 #else
1786 ssize_t nbytes_sent;
1787 #endif
1788
1789 nbytes_sent = send(client->sock, str + str_len - nbytes_left, nbytes_left, 0);
1790 if (nbytes_sent < 0) {
1791 int err = php_socket_errno();
1792 if (err == SOCK_EAGAIN) {
1793 int nfds = php_pollfd_for(client->sock, POLLOUT, &tv);
1794 if (nfds > 0) {
1795 continue;
1796 } else if (nfds < 0) {
1797 /* error */
1798 php_handle_aborted_connection();
1799 return nbytes_left;
1800 } else {
1801 /* timeout */
1802 php_handle_aborted_connection();
1803 return nbytes_left;
1804 }
1805 } else {
1806 php_handle_aborted_connection();
1807 return nbytes_left;
1808 }
1809 }
1810 nbytes_left -= nbytes_sent;
1811 } while (nbytes_left > 0);
1812
1813 return str_len;
1814 } /* }}} */
1815
php_cli_server_client_populate_request_info(const php_cli_server_client * client,sapi_request_info * request_info)1816 static void php_cli_server_client_populate_request_info(const php_cli_server_client *client, sapi_request_info *request_info) /* {{{ */
1817 {
1818 char *val;
1819
1820 request_info->request_method = php_http_method_str(client->request.request_method);
1821 request_info->proto_num = client->request.protocol_version;
1822 request_info->request_uri = client->request.request_uri;
1823 request_info->path_translated = client->request.path_translated;
1824 request_info->query_string = client->request.query_string;
1825 request_info->content_length = client->request.content_len;
1826 request_info->auth_user = request_info->auth_password = request_info->auth_digest = NULL;
1827 if (NULL != (val = zend_hash_str_find_ptr(&client->request.headers, "content-type", sizeof("content-type")-1))) {
1828 request_info->content_type = val;
1829 }
1830 } /* }}} */
1831
destroy_request_info(sapi_request_info * request_info)1832 static void destroy_request_info(sapi_request_info *request_info) /* {{{ */
1833 {
1834 } /* }}} */
1835
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)1836 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) /* {{{ */
1837 {
1838 client->server = server;
1839 client->sock = client_sock;
1840 client->addr = addr;
1841 client->addr_len = addr_len;
1842 {
1843 zend_string *addr_str = 0;
1844
1845 php_network_populate_name_from_sockaddr(addr, addr_len, &addr_str, NULL, 0);
1846 client->addr_str = pestrndup(ZSTR_VAL(addr_str), ZSTR_LEN(addr_str), 1);
1847 client->addr_str_len = ZSTR_LEN(addr_str);
1848 zend_string_release(addr_str);
1849 }
1850 php_http_parser_init(&client->parser, PHP_HTTP_REQUEST);
1851 client->request_read = 0;
1852
1853 client->last_header_element = HEADER_NONE;
1854 client->current_header_name = NULL;
1855 client->current_header_name_len = 0;
1856 client->current_header_name_allocated = 0;
1857 client->current_header_value = NULL;
1858 client->current_header_value_len = 0;
1859
1860 client->post_read_offset = 0;
1861 if (FAILURE == php_cli_server_request_ctor(&client->request)) {
1862 return FAILURE;
1863 }
1864 client->content_sender_initialized = 0;
1865 client->file_fd = -1;
1866 return SUCCESS;
1867 } /* }}} */
1868
php_cli_server_client_dtor(php_cli_server_client * client)1869 static void php_cli_server_client_dtor(php_cli_server_client *client) /* {{{ */
1870 {
1871 php_cli_server_request_dtor(&client->request);
1872 if (client->file_fd >= 0) {
1873 close(client->file_fd);
1874 client->file_fd = -1;
1875 }
1876 pefree(client->addr, 1);
1877 pefree(client->addr_str, 1);
1878 if (client->content_sender_initialized) {
1879 php_cli_server_content_sender_dtor(&client->content_sender);
1880 }
1881 } /* }}} */
1882
php_cli_server_close_connection(php_cli_server * server,php_cli_server_client * client)1883 static void php_cli_server_close_connection(php_cli_server *server, php_cli_server_client *client) /* {{{ */
1884 {
1885 #if PHP_DEBUG
1886 php_cli_server_logf("%s Closing", client->addr_str);
1887 #endif
1888 zend_hash_index_del(&server->clients, client->sock);
1889 } /* }}} */
1890
php_cli_server_send_error_page(php_cli_server * server,php_cli_server_client * client,int status)1891 static int php_cli_server_send_error_page(php_cli_server *server, php_cli_server_client *client, int status) /* {{{ */
1892 {
1893 zend_string *escaped_request_uri = NULL;
1894 const char *status_string = get_status_string(status);
1895 const char *content_template = get_template_string(status);
1896 char *errstr = get_last_error();
1897 assert(status_string && content_template);
1898
1899 php_cli_server_content_sender_ctor(&client->content_sender);
1900 client->content_sender_initialized = 1;
1901
1902 escaped_request_uri = php_escape_html_entities_ex((unsigned char *)client->request.request_uri, client->request.request_uri_len, 0, ENT_QUOTES, NULL, 0);
1903
1904 {
1905 static const char prologue_template[] = "<!doctype html><html><head><title>%d %s</title>";
1906 php_cli_server_chunk *chunk = php_cli_server_chunk_heap_new_self_contained(strlen(prologue_template) + 3 + strlen(status_string) + 1);
1907 if (!chunk) {
1908 goto fail;
1909 }
1910 snprintf(chunk->data.heap.p, chunk->data.heap.len, prologue_template, status, status_string, ZSTR_VAL(escaped_request_uri));
1911 chunk->data.heap.len = strlen(chunk->data.heap.p);
1912 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
1913 }
1914 {
1915 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(php_cli_server_css, sizeof(php_cli_server_css) - 1);
1916 if (!chunk) {
1917 goto fail;
1918 }
1919 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
1920 }
1921 {
1922 static const char template[] = "</head><body>";
1923 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(template, sizeof(template) - 1);
1924 if (!chunk) {
1925 goto fail;
1926 }
1927 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
1928 }
1929 {
1930 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);
1931 if (!chunk) {
1932 goto fail;
1933 }
1934 snprintf(chunk->data.heap.p, chunk->data.heap.len, content_template, status_string, ZSTR_VAL(escaped_request_uri));
1935 chunk->data.heap.len = strlen(chunk->data.heap.p);
1936 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
1937 }
1938 {
1939 static const char epilogue_template[] = "</body></html>";
1940 php_cli_server_chunk *chunk = php_cli_server_chunk_immortal_new(epilogue_template, sizeof(epilogue_template) - 1);
1941 if (!chunk) {
1942 goto fail;
1943 }
1944 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
1945 }
1946
1947 {
1948 php_cli_server_chunk *chunk;
1949 smart_str buffer = { 0 };
1950 append_http_status_line(&buffer, client->request.protocol_version, status, 1);
1951 if (!buffer.s) {
1952 /* out of memory */
1953 goto fail;
1954 }
1955 append_essential_headers(&buffer, client, 1);
1956 smart_str_appends_ex(&buffer, "Content-Type: text/html; charset=UTF-8\r\n", 1);
1957 smart_str_appends_ex(&buffer, "Content-Length: ", 1);
1958 smart_str_append_unsigned_ex(&buffer, php_cli_server_buffer_size(&client->content_sender.buffer), 1);
1959 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
1960 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
1961
1962 chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
1963 if (!chunk) {
1964 smart_str_free(&buffer);
1965 goto fail;
1966 }
1967 php_cli_server_buffer_prepend(&client->content_sender.buffer, chunk);
1968 }
1969
1970 php_cli_server_log_response(client, status, errstr ? errstr : "?");
1971 php_cli_server_poller_add(&server->poller, POLLOUT, client->sock);
1972 if (errstr) {
1973 pefree(errstr, 1);
1974 }
1975 zend_string_free(escaped_request_uri);
1976 return SUCCESS;
1977
1978 fail:
1979 if (errstr) {
1980 pefree(errstr, 1);
1981 }
1982 zend_string_free(escaped_request_uri);
1983 return FAILURE;
1984 } /* }}} */
1985
php_cli_server_dispatch_script(php_cli_server * server,php_cli_server_client * client)1986 static int php_cli_server_dispatch_script(php_cli_server *server, php_cli_server_client *client) /* {{{ */
1987 {
1988 if (strlen(client->request.path_translated) != client->request.path_translated_len) {
1989 /* can't handle paths that contain nul bytes */
1990 return php_cli_server_send_error_page(server, client, 400);
1991 }
1992 {
1993 zend_file_handle zfd;
1994 zfd.type = ZEND_HANDLE_FILENAME;
1995 zfd.filename = SG(request_info).path_translated;
1996 zfd.handle.fp = NULL;
1997 zfd.free_filename = 0;
1998 zfd.opened_path = NULL;
1999 zend_try {
2000 php_execute_script(&zfd);
2001 } zend_end_try();
2002 }
2003
2004 php_cli_server_log_response(client, SG(sapi_headers).http_response_code, NULL);
2005 return SUCCESS;
2006 } /* }}} */
2007
php_cli_server_begin_send_static(php_cli_server * server,php_cli_server_client * client)2008 static int php_cli_server_begin_send_static(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2009 {
2010 int fd;
2011 int status = 200;
2012
2013 if (client->request.path_translated && strlen(client->request.path_translated) != client->request.path_translated_len) {
2014 /* can't handle paths that contain nul bytes */
2015 return php_cli_server_send_error_page(server, client, 400);
2016 }
2017
2018 #ifdef PHP_WIN32
2019 /* The win32 namespace will cut off trailing dots and spaces. Since the
2020 VCWD functionality isn't used here, a sophisticated functionality
2021 would have to be reimplemented to know ahead there are no files
2022 with invalid names there. The simplest is just to forbid invalid
2023 filenames, which is done here. */
2024 if (client->request.path_translated &&
2025 ('.' == client->request.path_translated[client->request.path_translated_len-1] ||
2026 ' ' == client->request.path_translated[client->request.path_translated_len-1])) {
2027 return php_cli_server_send_error_page(server, client, 500);
2028 }
2029 #endif
2030
2031 fd = client->request.path_translated ? open(client->request.path_translated, O_RDONLY): -1;
2032 if (fd < 0) {
2033 return php_cli_server_send_error_page(server, client, 404);
2034 }
2035
2036 php_cli_server_content_sender_ctor(&client->content_sender);
2037 client->content_sender_initialized = 1;
2038 client->file_fd = fd;
2039
2040 {
2041 php_cli_server_chunk *chunk;
2042 smart_str buffer = { 0 };
2043 const char *mime_type = get_mime_type(server, client->request.ext, client->request.ext_len);
2044 if (!mime_type) {
2045 mime_type = "application/octet-stream";
2046 }
2047
2048 append_http_status_line(&buffer, client->request.protocol_version, status, 1);
2049 if (!buffer.s) {
2050 /* out of memory */
2051 php_cli_server_log_response(client, 500, NULL);
2052 return FAILURE;
2053 }
2054 append_essential_headers(&buffer, client, 1);
2055 smart_str_appendl_ex(&buffer, "Content-Type: ", sizeof("Content-Type: ") - 1, 1);
2056 smart_str_appends_ex(&buffer, mime_type, 1);
2057 if (strncmp(mime_type, "text/", 5) == 0) {
2058 smart_str_appends_ex(&buffer, "; charset=UTF-8", 1);
2059 }
2060 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2061 smart_str_appends_ex(&buffer, "Content-Length: ", 1);
2062 smart_str_append_unsigned_ex(&buffer, client->request.sb.st_size, 1);
2063 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2064 smart_str_appendl_ex(&buffer, "\r\n", 2, 1);
2065 chunk = php_cli_server_chunk_heap_new(buffer.s, ZSTR_VAL(buffer.s), ZSTR_LEN(buffer.s));
2066 if (!chunk) {
2067 smart_str_free(&buffer);
2068 php_cli_server_log_response(client, 500, NULL);
2069 return FAILURE;
2070 }
2071 php_cli_server_buffer_append(&client->content_sender.buffer, chunk);
2072 }
2073 php_cli_server_log_response(client, 200, NULL);
2074 php_cli_server_poller_add(&server->poller, POLLOUT, client->sock);
2075 return SUCCESS;
2076 }
2077 /* }}} */
2078
php_cli_server_request_startup(php_cli_server * server,php_cli_server_client * client)2079 static int php_cli_server_request_startup(php_cli_server *server, php_cli_server_client *client) { /* {{{ */
2080 char *auth;
2081 php_cli_server_client_populate_request_info(client, &SG(request_info));
2082 if (NULL != (auth = zend_hash_str_find_ptr(&client->request.headers, "authorization", sizeof("authorization")-1))) {
2083 php_handle_auth_data(auth);
2084 }
2085 SG(sapi_headers).http_response_code = 200;
2086 if (FAILURE == php_request_startup()) {
2087 /* should never be happen */
2088 destroy_request_info(&SG(request_info));
2089 return FAILURE;
2090 }
2091 PG(during_request_startup) = 0;
2092
2093 return SUCCESS;
2094 }
2095 /* }}} */
2096
php_cli_server_request_shutdown(php_cli_server * server,php_cli_server_client * client)2097 static int php_cli_server_request_shutdown(php_cli_server *server, php_cli_server_client *client) { /* {{{ */
2098 php_request_shutdown(0);
2099 php_cli_server_close_connection(server, client);
2100 destroy_request_info(&SG(request_info));
2101 SG(server_context) = NULL;
2102 SG(rfc1867_uploaded_files) = NULL;
2103 return SUCCESS;
2104 }
2105 /* }}} */
2106
php_cli_server_dispatch_router(php_cli_server * server,php_cli_server_client * client)2107 static int php_cli_server_dispatch_router(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2108 {
2109 int decline = 0;
2110 zend_file_handle zfd;
2111
2112 zfd.type = ZEND_HANDLE_FILENAME;
2113 zfd.filename = server->router;
2114 zfd.handle.fp = NULL;
2115 zfd.free_filename = 0;
2116 zfd.opened_path = NULL;
2117
2118 zend_try {
2119 zval retval;
2120
2121 ZVAL_UNDEF(&retval);
2122 if (SUCCESS == zend_execute_scripts(ZEND_REQUIRE, &retval, 1, &zfd)) {
2123 if (Z_TYPE(retval) != IS_UNDEF) {
2124 decline = Z_TYPE(retval) == IS_FALSE;
2125 zval_ptr_dtor(&retval);
2126 }
2127 } else {
2128 decline = 1;
2129 }
2130 } zend_end_try();
2131
2132 return decline;
2133 }
2134 /* }}} */
2135
php_cli_server_dispatch(php_cli_server * server,php_cli_server_client * client)2136 static int php_cli_server_dispatch(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2137 {
2138 int is_static_file = 0;
2139
2140 SG(server_context) = client;
2141 if (client->request.ext_len != 3 || memcmp(client->request.ext, "php", 3) || !client->request.path_translated) {
2142 is_static_file = 1;
2143 }
2144
2145 if (server->router || !is_static_file) {
2146 if (FAILURE == php_cli_server_request_startup(server, client)) {
2147 SG(server_context) = NULL;
2148 php_cli_server_close_connection(server, client);
2149 destroy_request_info(&SG(request_info));
2150 return SUCCESS;
2151 }
2152 }
2153
2154 if (server->router) {
2155 if (!php_cli_server_dispatch_router(server, client)) {
2156 php_cli_server_request_shutdown(server, client);
2157 return SUCCESS;
2158 }
2159 }
2160
2161 if (!is_static_file) {
2162 if (SUCCESS == php_cli_server_dispatch_script(server, client)
2163 || SUCCESS != php_cli_server_send_error_page(server, client, 500)) {
2164 if (SG(sapi_headers).http_response_code == 304) {
2165 SG(sapi_headers).send_default_content_type = 0;
2166 }
2167 php_cli_server_request_shutdown(server, client);
2168 return SUCCESS;
2169 }
2170 } else {
2171 if (server->router) {
2172 static int (*send_header_func)(sapi_headers_struct *);
2173 send_header_func = sapi_module.send_headers;
2174 /* do not generate default content type header */
2175 SG(sapi_headers).send_default_content_type = 0;
2176 /* we don't want headers to be sent */
2177 sapi_module.send_headers = sapi_cli_server_discard_headers;
2178 php_request_shutdown(0);
2179 sapi_module.send_headers = send_header_func;
2180 SG(sapi_headers).send_default_content_type = 1;
2181 SG(rfc1867_uploaded_files) = NULL;
2182 }
2183 if (SUCCESS != php_cli_server_begin_send_static(server, client)) {
2184 php_cli_server_close_connection(server, client);
2185 }
2186 SG(server_context) = NULL;
2187 return SUCCESS;
2188 }
2189
2190 SG(server_context) = NULL;
2191 destroy_request_info(&SG(request_info));
2192 return SUCCESS;
2193 }
2194 /* }}} */
2195
php_cli_server_mime_type_ctor(php_cli_server * server,const php_cli_server_ext_mime_type_pair * mime_type_map)2196 static int php_cli_server_mime_type_ctor(php_cli_server *server, const php_cli_server_ext_mime_type_pair *mime_type_map) /* {{{ */
2197 {
2198 const php_cli_server_ext_mime_type_pair *pair;
2199
2200 zend_hash_init(&server->extension_mime_types, 0, NULL, NULL, 1);
2201
2202 for (pair = mime_type_map; pair->ext; pair++) {
2203 size_t ext_len = strlen(pair->ext);
2204 zend_hash_str_add_ptr(&server->extension_mime_types, pair->ext, ext_len, (void*)pair->mime_type);
2205 }
2206
2207 return SUCCESS;
2208 } /* }}} */
2209
php_cli_server_dtor(php_cli_server * server)2210 static void php_cli_server_dtor(php_cli_server *server) /* {{{ */
2211 {
2212 zend_hash_destroy(&server->clients);
2213 zend_hash_destroy(&server->extension_mime_types);
2214 if (ZEND_VALID_SOCKET(server->server_sock)) {
2215 closesocket(server->server_sock);
2216 }
2217 if (server->host) {
2218 pefree(server->host, 1);
2219 }
2220 if (server->document_root) {
2221 pefree(server->document_root, 1);
2222 }
2223 if (server->router) {
2224 pefree(server->router, 1);
2225 }
2226 } /* }}} */
2227
php_cli_server_client_dtor_wrapper(zval * zv)2228 static void php_cli_server_client_dtor_wrapper(zval *zv) /* {{{ */
2229 {
2230 php_cli_server_client *p = Z_PTR_P(zv);
2231
2232 closesocket(p->sock);
2233 php_cli_server_poller_remove(&p->server->poller, POLLIN | POLLOUT, p->sock);
2234 php_cli_server_client_dtor(p);
2235 pefree(p, 1);
2236 } /* }}} */
2237
php_cli_server_ctor(php_cli_server * server,const char * addr,const char * document_root,const char * router)2238 static int php_cli_server_ctor(php_cli_server *server, const char *addr, const char *document_root, const char *router) /* {{{ */
2239 {
2240 int retval = SUCCESS;
2241 char *host = NULL;
2242 zend_string *errstr = NULL;
2243 char *_document_root = NULL;
2244 char *_router = NULL;
2245 int err = 0;
2246 int port = 3000;
2247 php_socket_t server_sock = SOCK_ERR;
2248 char *p = NULL;
2249
2250 if (addr[0] == '[') {
2251 host = pestrdup(addr + 1, 1);
2252 if (!host) {
2253 return FAILURE;
2254 }
2255 p = strchr(host, ']');
2256 if (p) {
2257 *p++ = '\0';
2258 if (*p == ':') {
2259 port = strtol(p + 1, &p, 10);
2260 if (port <= 0 || port > 65535) {
2261 p = NULL;
2262 }
2263 } else if (*p != '\0') {
2264 p = NULL;
2265 }
2266 }
2267 } else {
2268 host = pestrdup(addr, 1);
2269 if (!host) {
2270 return FAILURE;
2271 }
2272 p = strchr(host, ':');
2273 if (p) {
2274 *p++ = '\0';
2275 port = strtol(p, &p, 10);
2276 if (port <= 0 || port > 65535) {
2277 p = NULL;
2278 }
2279 }
2280 }
2281 if (!p) {
2282 fprintf(stderr, "Invalid address: %s\n", addr);
2283 retval = FAILURE;
2284 goto out;
2285 }
2286
2287 server_sock = php_network_listen_socket(host, &port, SOCK_STREAM, &server->address_family, &server->socklen, &errstr);
2288 if (server_sock == SOCK_ERR) {
2289 php_cli_server_logf("Failed to listen on %s:%d (reason: %s)", host, port, errstr ? ZSTR_VAL(errstr) : "?");
2290 if (errstr) {
2291 zend_string_release(errstr);
2292 }
2293 retval = FAILURE;
2294 goto out;
2295 }
2296 server->server_sock = server_sock;
2297
2298 err = php_cli_server_poller_ctor(&server->poller);
2299 if (SUCCESS != err) {
2300 goto out;
2301 }
2302
2303 php_cli_server_poller_add(&server->poller, POLLIN, server_sock);
2304
2305 server->host = host;
2306 server->port = port;
2307
2308 zend_hash_init(&server->clients, 0, NULL, php_cli_server_client_dtor_wrapper, 1);
2309
2310 {
2311 size_t document_root_len = strlen(document_root);
2312 _document_root = pestrndup(document_root, document_root_len, 1);
2313 if (!_document_root) {
2314 retval = FAILURE;
2315 goto out;
2316 }
2317 server->document_root = _document_root;
2318 server->document_root_len = document_root_len;
2319 }
2320
2321 if (router) {
2322 size_t router_len = strlen(router);
2323 if (!IS_ABSOLUTE_PATH(router, router_len)) {
2324 _router = pemalloc(server->document_root_len + router_len + 2, 1);
2325 if (!_router) {
2326 retval = FAILURE;
2327 goto out;
2328 }
2329 snprintf(_router,
2330 server->document_root_len + router_len + 2, "%s%c%s", server->document_root, DEFAULT_SLASH, router);
2331 } else {
2332 _router = pestrndup(router, router_len, 1);
2333 if (!_router) {
2334 retval = FAILURE;
2335 goto out;
2336 }
2337 }
2338 server->router = _router;
2339 server->router_len = router_len;
2340 } else {
2341 server->router = NULL;
2342 server->router_len = 0;
2343 }
2344
2345 if (php_cli_server_mime_type_ctor(server, mime_type_map) == FAILURE) {
2346 retval = FAILURE;
2347 goto out;
2348 }
2349
2350 server->is_running = 1;
2351 out:
2352 if (retval != SUCCESS) {
2353 if (host) {
2354 pefree(host, 1);
2355 }
2356 if (_document_root) {
2357 pefree(_document_root, 1);
2358 }
2359 if (_router) {
2360 pefree(_router, 1);
2361 }
2362 if (server_sock > -1) {
2363 closesocket(server_sock);
2364 }
2365 }
2366 return retval;
2367 } /* }}} */
2368
php_cli_server_recv_event_read_request(php_cli_server * server,php_cli_server_client * client)2369 static int php_cli_server_recv_event_read_request(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2370 {
2371 char *errstr = NULL;
2372 int status = php_cli_server_client_read_request(client, &errstr);
2373 if (status < 0) {
2374 if (strcmp(errstr, php_cli_server_request_error_unexpected_eof) == 0 && client->parser.state == s_start_req) {
2375 #if PHP_DEBUG
2376 php_cli_server_logf("%s Closed without sending a request; it was probably just an unused speculative preconnection", client->addr_str);
2377 #endif
2378 } else {
2379 php_cli_server_logf("%s Invalid request (%s)", client->addr_str, errstr);
2380 }
2381 efree(errstr);
2382 php_cli_server_close_connection(server, client);
2383 return FAILURE;
2384 } else if (status == 1 && client->request.request_method == PHP_HTTP_NOT_IMPLEMENTED) {
2385 return php_cli_server_send_error_page(server, client, 501);
2386 } else if (status == 1) {
2387 php_cli_server_poller_remove(&server->poller, POLLIN, client->sock);
2388 php_cli_server_dispatch(server, client);
2389 } else {
2390 php_cli_server_poller_add(&server->poller, POLLIN, client->sock);
2391 }
2392
2393 return SUCCESS;
2394 } /* }}} */
2395
php_cli_server_send_event(php_cli_server * server,php_cli_server_client * client)2396 static int php_cli_server_send_event(php_cli_server *server, php_cli_server_client *client) /* {{{ */
2397 {
2398 if (client->content_sender_initialized) {
2399 if (client->file_fd >= 0 && !client->content_sender.buffer.first) {
2400 size_t nbytes_read;
2401 if (php_cli_server_content_sender_pull(&client->content_sender, client->file_fd, &nbytes_read)) {
2402 php_cli_server_close_connection(server, client);
2403 return FAILURE;
2404 }
2405 if (nbytes_read == 0) {
2406 close(client->file_fd);
2407 client->file_fd = -1;
2408 }
2409 }
2410 {
2411 size_t nbytes_sent;
2412 int err = php_cli_server_content_sender_send(&client->content_sender, client->sock, &nbytes_sent);
2413 if (err && err != SOCK_EAGAIN) {
2414 php_cli_server_close_connection(server, client);
2415 return FAILURE;
2416 }
2417 }
2418 if (!client->content_sender.buffer.first && client->file_fd < 0) {
2419 php_cli_server_close_connection(server, client);
2420 }
2421 }
2422 return SUCCESS;
2423 }
2424 /* }}} */
2425
2426 typedef struct php_cli_server_do_event_for_each_fd_callback_params {
2427 php_cli_server *server;
2428 int(*rhandler)(php_cli_server*, php_cli_server_client*);
2429 int(*whandler)(php_cli_server*, php_cli_server_client*);
2430 } php_cli_server_do_event_for_each_fd_callback_params;
2431
php_cli_server_do_event_for_each_fd_callback(void * _params,php_socket_t fd,int event)2432 static int php_cli_server_do_event_for_each_fd_callback(void *_params, php_socket_t fd, int event) /* {{{ */
2433 {
2434 php_cli_server_do_event_for_each_fd_callback_params *params = _params;
2435 php_cli_server *server = params->server;
2436 if (server->server_sock == fd) {
2437 php_cli_server_client *client = NULL;
2438 php_socket_t client_sock;
2439 socklen_t socklen = server->socklen;
2440 struct sockaddr *sa = pemalloc(server->socklen, 1);
2441 if (!sa) {
2442 return FAILURE;
2443 }
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 if (!(client = pemalloc(sizeof(php_cli_server_client), 1)) || FAILURE == php_cli_server_client_ctor(client, server, client_sock, sa, socklen)) {
2459 php_cli_server_logf("Failed to create a new request object");
2460 pefree(sa, 1);
2461 closesocket(client_sock);
2462 return SUCCESS;
2463 }
2464 #if PHP_DEBUG
2465 php_cli_server_logf("%s Accepted", client->addr_str);
2466 #endif
2467 zend_hash_index_update_ptr(&server->clients, client_sock, client);
2468 php_cli_server_recv_event_read_request(server, client);
2469 } else {
2470 php_cli_server_client *client;
2471 if (NULL != (client = zend_hash_index_find_ptr(&server->clients, fd))) {
2472 if (event & POLLIN) {
2473 params->rhandler(server, client);
2474 }
2475 if (event & POLLOUT) {
2476 params->whandler(server, client);
2477 }
2478 }
2479 }
2480 return SUCCESS;
2481 } /* }}} */
2482
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 *))2483 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*)) /* {{{ */
2484 {
2485 php_cli_server_do_event_for_each_fd_callback_params params = {
2486 server,
2487 rhandler,
2488 whandler
2489 };
2490
2491 php_cli_server_poller_iter_on_active(&server->poller, ¶ms, php_cli_server_do_event_for_each_fd_callback);
2492 } /* }}} */
2493
php_cli_server_do_event_loop(php_cli_server * server)2494 static int php_cli_server_do_event_loop(php_cli_server *server) /* {{{ */
2495 {
2496 int retval = SUCCESS;
2497 while (server->is_running) {
2498 struct timeval tv = { 1, 0 };
2499 int n = php_cli_server_poller_poll(&server->poller, &tv);
2500 if (n > 0) {
2501 php_cli_server_do_event_for_each_fd(server,
2502 php_cli_server_recv_event_read_request,
2503 php_cli_server_send_event);
2504 } else if (n == 0) {
2505 /* do nothing */
2506 } else {
2507 int err = php_socket_errno();
2508 if (err != SOCK_EINTR) {
2509 char *errstr = php_socket_strerror(err, NULL, 0);
2510 php_cli_server_logf("%s", errstr);
2511 efree(errstr);
2512 retval = FAILURE;
2513 goto out;
2514 }
2515 }
2516 }
2517 out:
2518 return retval;
2519 } /* }}} */
2520
2521 static php_cli_server server;
2522
php_cli_server_sigint_handler(int sig)2523 static void php_cli_server_sigint_handler(int sig) /* {{{ */
2524 {
2525 server.is_running = 0;
2526 }
2527 /* }}} */
2528
do_cli_server(int argc,char ** argv)2529 int do_cli_server(int argc, char **argv) /* {{{ */
2530 {
2531 char *php_optarg = NULL;
2532 int php_optind = 1;
2533 int c;
2534 const char *server_bind_address = NULL;
2535 extern const opt_struct OPTIONS[];
2536 const char *document_root = NULL;
2537 const char *router = NULL;
2538 char document_root_buf[MAXPATHLEN];
2539
2540 while ((c = php_getopt(argc, argv, OPTIONS, &php_optarg, &php_optind, 0, 2))!=-1) {
2541 switch (c) {
2542 case 'S':
2543 server_bind_address = php_optarg;
2544 break;
2545 case 't':
2546 document_root = php_optarg;
2547 break;
2548 }
2549 }
2550
2551 if (document_root) {
2552 zend_stat_t sb;
2553
2554 if (zend_stat(document_root, &sb)) {
2555 fprintf(stderr, "Directory %s does not exist.\n", document_root);
2556 return 1;
2557 }
2558 if (!S_ISDIR(sb.st_mode)) {
2559 fprintf(stderr, "%s is not a directory.\n", document_root);
2560 return 1;
2561 }
2562 if (VCWD_REALPATH(document_root, document_root_buf)) {
2563 document_root = document_root_buf;
2564 }
2565 } else {
2566 char *ret = NULL;
2567
2568 #if HAVE_GETCWD
2569 ret = VCWD_GETCWD(document_root_buf, MAXPATHLEN);
2570 #elif HAVE_GETWD
2571 ret = VCWD_GETWD(document_root_buf);
2572 #endif
2573 document_root = ret ? document_root_buf: ".";
2574 }
2575
2576 if (argc > php_optind) {
2577 router = argv[php_optind];
2578 }
2579
2580 if (FAILURE == php_cli_server_ctor(&server, server_bind_address, document_root, router)) {
2581 return 1;
2582 }
2583 sapi_module.phpinfo_as_text = 0;
2584
2585 {
2586 char buf[52];
2587
2588 if (php_cli_server_get_system_time(buf) != 0) {
2589 memmove(buf, "unknown time, can't be fetched", sizeof("unknown time, can't be fetched"));
2590 }
2591
2592 printf("PHP %s Development Server started at %s"
2593 "Listening on http://%s\n"
2594 "Document root is %s\n"
2595 "Press Ctrl-C to quit.\n",
2596 PHP_VERSION, buf, server_bind_address, document_root);
2597 }
2598
2599 #if defined(HAVE_SIGNAL_H) && defined(SIGINT)
2600 signal(SIGINT, php_cli_server_sigint_handler);
2601 #endif
2602 php_cli_server_do_event_loop(&server);
2603 php_cli_server_dtor(&server);
2604 return 0;
2605 } /* }}} */
2606
2607 /*
2608 * Local variables:
2609 * tab-width: 4
2610 * c-basic-offset: 4
2611 * End:
2612 * vim600: noet sw=4 ts=4 fdm=marker
2613 * vim<600: noet sw=4 ts=4
2614 */
2615