root/sapi/cli/php_cli_server.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. php_cli_server_get_system_time
  2. php_cli_server_get_system_time
  3. char_ptr_dtor_p
  4. get_last_error
  5. status_comp
  6. get_status_string
  7. get_template_string
  8. append_http_status_line
  9. append_essential_headers
  10. get_mime_type
  11. PHP_FUNCTION
  12. add_response_header
  13. PHP_FUNCTION
  14. cli_server_init_globals
  15. PHP_INI_BEGIN
  16. PHP_MSHUTDOWN_FUNCTION
  17. PHP_MINFO_FUNCTION
  18. sapi_cli_server_startup
  19. sapi_cli_server_ub_write
  20. sapi_cli_server_flush
  21. sapi_cli_server_discard_headers
  22. sapi_cli_server_send_headers
  23. sapi_cli_server_read_cookies
  24. sapi_cli_server_read_post
  25. sapi_cli_server_register_variable
  26. sapi_cli_server_register_entry_cb
  27. sapi_cli_server_register_variables
  28. sapi_cli_server_log_message
  29. php_cli_server_poller_ctor
  30. php_cli_server_poller_add
  31. php_cli_server_poller_remove
  32. php_cli_server_poller_poll
  33. php_cli_server_poller_iter_on_active
  34. php_cli_server_chunk_size
  35. php_cli_server_chunk_dtor
  36. php_cli_server_buffer_dtor
  37. php_cli_server_buffer_ctor
  38. php_cli_server_buffer_append
  39. php_cli_server_buffer_prepend
  40. php_cli_server_buffer_size
  41. php_cli_server_chunk_immortal_new
  42. php_cli_server_chunk_heap_new
  43. php_cli_server_chunk_heap_new_self_contained
  44. php_cli_server_content_sender_dtor
  45. php_cli_server_content_sender_ctor
  46. php_cli_server_content_sender_send
  47. php_cli_server_content_sender_pull
  48. php_cli_is_output_tty
  49. php_cli_server_log_response
  50. php_cli_server_logf
  51. php_network_listen_socket
  52. php_cli_server_request_ctor
  53. php_cli_server_request_dtor
  54. php_cli_server_request_translate_vpath
  55. normalize_vpath
  56. php_cli_server_client_read_request_on_message_begin
  57. php_cli_server_client_read_request_on_path
  58. php_cli_server_client_read_request_on_query_string
  59. php_cli_server_client_read_request_on_url
  60. php_cli_server_client_read_request_on_fragment
  61. php_cli_server_client_read_request_on_header_field
  62. php_cli_server_client_read_request_on_header_value
  63. php_cli_server_client_read_request_on_headers_complete
  64. php_cli_server_client_read_request_on_body
  65. php_cli_server_client_read_request_on_message_complete
  66. php_cli_server_client_read_request
  67. php_cli_server_client_send_through
  68. php_cli_server_client_populate_request_info
  69. destroy_request_info
  70. php_cli_server_client_ctor
  71. php_cli_server_client_dtor
  72. php_cli_server_close_connection
  73. php_cli_server_send_error_page
  74. php_cli_server_dispatch_script
  75. php_cli_server_begin_send_static
  76. php_cli_server_request_startup
  77. php_cli_server_request_shutdown
  78. php_cli_server_dispatch_router
  79. php_cli_server_dispatch
  80. php_cli_server_dtor
  81. php_cli_server_client_dtor_wrapper
  82. php_cli_server_ctor
  83. php_cli_server_recv_event_read_request
  84. php_cli_server_send_event
  85. php_cli_server_do_event_for_each_fd_callback
  86. php_cli_server_do_event_for_each_fd
  87. php_cli_server_do_event_loop
  88. php_cli_server_sigint_handler
  89. do_cli_server

   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
 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
 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 
 375 static void char_ptr_dtor_p(char **p) /* {{{ */
 376 {
 377         pefree(*p, 1);
 378 } /* }}} */
 379 
 380 static char *get_last_error() /* {{{ */
 381 {
 382         return pestrdup(strerror(errno), 1);
 383 } /* }}} */
 384 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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)
 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 
 569 static PHP_MSHUTDOWN_FUNCTION(cli_server)
 570 {
 571         UNREGISTER_INI_ENTRIES();
 572         return SUCCESS;
 573 }
 574 
 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 
 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 
 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 
 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 
 643 static int sapi_cli_server_discard_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) /* {{{ */{
 644         return SAPI_HEADER_SENT_SUCCESSFULLY;
 645 }
 646 /* }}} */
 647 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
 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 
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 
1012 static void php_cli_server_buffer_ctor(php_cli_server_buffer *buffer) /* {{{ */
1013 {
1014         buffer->first = NULL;
1015         buffer->last = NULL;
1016 } /* }}} */
1017 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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
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 
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 
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 
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 
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 
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 
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 
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 */
1649 static int php_cli_server_client_read_request_on_message_begin(php_http_parser *parser)
1650 {
1651         return 0;
1652 }
1653 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
1879 static void destroy_request_info(sapi_request_info *request_info) /* {{{ */
1880 {
1881 } /* }}} */
1882 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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 
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, &params, php_cli_server_do_event_for_each_fd_callback);
2514 } /* }}} */
2515 
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 
2545 static void php_cli_server_sigint_handler(int sig) /* {{{ */
2546 {
2547         server.is_running = 0;
2548 }
2549 /* }}} */
2550 
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  */

/* [<][>][^][v][top][bottom][index][help] */