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