diff options
Diffstat (limited to 'sapi/thttpd/thttpd.c')
-rw-r--r-- | sapi/thttpd/thttpd.c | 772 |
1 files changed, 772 insertions, 0 deletions
diff --git a/sapi/thttpd/thttpd.c b/sapi/thttpd/thttpd.c new file mode 100644 index 0000000..1a1baa7 --- /dev/null +++ b/sapi/thttpd/thttpd.c @@ -0,0 +1,772 @@ +/* + +----------------------------------------------------------------------+ + | PHP Version 5 | + +----------------------------------------------------------------------+ + | Copyright (c) 1997-2013 The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP license, | + | that is bundled with this package in the file LICENSE, and is | + | available through the world-wide-web at the following url: | + | http://www.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Author: Sascha Schumann <sascha@schumann.cx> | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php.h" +#include "SAPI.h" +#include "php_main.h" +#include "php_thttpd.h" +#include "php_variables.h" +#include "version.h" +#include "php_ini.h" +#include "zend_highlight.h" + +#include "ext/standard/php_smart_str.h" + +#include <sys/time.h> +#include <sys/types.h> +#include <sys/uio.h> +#include <stdlib.h> +#include <unistd.h> + +#ifdef HAVE_GETNAMEINFO +#include <sys/socket.h> +#include <netdb.h> +#endif + +typedef struct { + httpd_conn *hc; + void (*on_close)(int); + + size_t unconsumed_length; + smart_str sbuf; + int seen_cl; + int seen_cn; +} php_thttpd_globals; + +#define PHP_SYS_CALL(x) do { x } while (n == -1 && errno == EINTR) + +#ifdef PREMIUM_THTTPD +# define do_keep_alive persistent +#endif + +#ifdef ZTS +static int thttpd_globals_id; +#define TG(v) TSRMG(thttpd_globals_id, php_thttpd_globals *, v) +#else +static php_thttpd_globals thttpd_globals; +#define TG(v) (thttpd_globals.v) +#endif + +static int sapi_thttpd_ub_write(const char *str, uint str_length TSRMLS_DC) +{ + int n; + uint sent = 0; + + if (TG(sbuf).c != 0) { + smart_str_appendl_ex(&TG(sbuf), str, str_length, 1); + return str_length; + } + + while (str_length > 0) { + PHP_SYS_CALL(n = send(TG(hc)->conn_fd, str, str_length, 0);); + + if (n == -1) { + if (errno == EAGAIN) { + smart_str_appendl_ex(&TG(sbuf), str, str_length, 1); + + return sent + str_length; + } else + php_handle_aborted_connection(); + } + + TG(hc)->bytes_sent += n; + str += n; + sent += n; + str_length -= n; + } + + return sent; +} + +#define COMBINE_HEADERS 64 + +#if defined(IOV_MAX) +# if IOV_MAX - 64 <= 0 +# define SERIALIZE_HEADERS +# endif +#endif + +static int do_writev(struct iovec *vec, int nvec, int len TSRMLS_DC) +{ + int n; + + assert(nvec <= IOV_MAX); + + if (TG(sbuf).c == 0) { + PHP_SYS_CALL(n = writev(TG(hc)->conn_fd, vec, nvec);); + + if (n == -1) { + if (errno == EAGAIN) { + n = 0; + } else { + php_handle_aborted_connection(); + } + } + + + TG(hc)->bytes_sent += n; + } else { + n = 0; + } + + if (n < len) { + int i; + + /* merge all unwritten data into sbuf */ + for (i = 0; i < nvec; vec++, i++) { + /* has this vector been written completely? */ + if (n >= vec->iov_len) { + /* yes, proceed */ + n -= vec->iov_len; + continue; + } + + if (n > 0) { + /* adjust vector */ + vec->iov_base = (char *) vec->iov_base + n; + vec->iov_len -= n; + n = 0; + } + + smart_str_appendl_ex(&TG(sbuf), vec->iov_base, vec->iov_len, 1); + } + } + + return 0; +} + +#ifdef SERIALIZE_HEADERS +# define ADD_VEC(str,l) smart_str_appendl(&vec_str, (str), (l)) +# define VEC_BASE() smart_str vec_str = {0} +# define VEC_FREE() smart_str_free(&vec_str) +#else +# define ADD_VEC(str,l) vec[n].iov_base=str;len += (vec[n].iov_len=l); n++ +# define VEC_BASE() struct iovec vec[COMBINE_HEADERS] +# define VEC_FREE() do {} while (0) +#endif + +#define ADD_VEC_S(str) ADD_VEC((str), sizeof(str)-1) + +#define CL_TOKEN "Content-length: " +#define CN_TOKEN "Connection: " +#define KA_DO "Connection: keep-alive\r\n" +#define KA_NO "Connection: close\r\n" +#define DEF_CT "Content-Type: text/html\r\n" + +static int sapi_thttpd_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC) +{ + char buf[1024], *p; + VEC_BASE(); + int n = 0; + zend_llist_position pos; + sapi_header_struct *h; + size_t len = 0; + + if (!SG(sapi_headers).http_status_line) { + ADD_VEC_S("HTTP/1.1 "); + p = smart_str_print_long(buf+sizeof(buf)-1, + SG(sapi_headers).http_response_code); + ADD_VEC(p, strlen(p)); + ADD_VEC_S(" HTTP\r\n"); + } else { + ADD_VEC(SG(sapi_headers).http_status_line, + strlen(SG(sapi_headers).http_status_line)); + ADD_VEC("\r\n", 2); + } + TG(hc)->status = SG(sapi_headers).http_response_code; + + if (SG(sapi_headers).send_default_content_type) { + ADD_VEC(DEF_CT, strlen(DEF_CT)); + } + + h = zend_llist_get_first_ex(&sapi_headers->headers, &pos); + while (h) { + + switch (h->header[0]) { + case 'c': case 'C': + if (!TG(seen_cl) && strncasecmp(h->header, CL_TOKEN, sizeof(CL_TOKEN)-1) == 0) { + TG(seen_cl) = 1; + } else if (!TG(seen_cn) && strncasecmp(h->header, CN_TOKEN, sizeof(CN_TOKEN)-1) == 0) { + TG(seen_cn) = 1; + } + } + + ADD_VEC(h->header, h->header_len); +#ifndef SERIALIZE_HEADERS + if (n >= COMBINE_HEADERS - 1) { + len = do_writev(vec, n, len TSRMLS_CC); + n = 0; + } +#endif + ADD_VEC("\r\n", 2); + + h = zend_llist_get_next_ex(&sapi_headers->headers, &pos); + } + + if (TG(seen_cl) && !TG(seen_cn) && TG(hc)->do_keep_alive) { + ADD_VEC(KA_DO, sizeof(KA_DO)-1); + } else { + TG(hc)->do_keep_alive = 0; + ADD_VEC(KA_NO, sizeof(KA_NO)-1); + } + + ADD_VEC("\r\n", 2); + +#ifdef SERIALIZE_HEADERS + sapi_thttpd_ub_write(vec_str.c, vec_str.len TSRMLS_CC); +#else + do_writev(vec, n, len TSRMLS_CC); +#endif + + VEC_FREE(); + + return SAPI_HEADER_SENT_SUCCESSFULLY; +} + +/* to understand this, read cgi_interpose_input() in libhttpd.c */ +#define SIZEOF_UNCONSUMED_BYTES() (TG(hc)->read_idx - TG(hc)->checked_idx) +#define CONSUME_BYTES(n) do { TG(hc)->checked_idx += (n); } while (0) + + +static int sapi_thttpd_read_post(char *buffer, uint count_bytes TSRMLS_DC) +{ + size_t read_bytes = 0; + + if (TG(unconsumed_length) > 0) { + read_bytes = MIN(TG(unconsumed_length), count_bytes); + memcpy(buffer, TG(hc)->read_buf + TG(hc)->checked_idx, read_bytes); + TG(unconsumed_length) -= read_bytes; + CONSUME_BYTES(read_bytes); + } + + return read_bytes; +} + +static char *sapi_thttpd_read_cookies(TSRMLS_D) +{ + return TG(hc)->cookie; +} + +#define BUF_SIZE 512 +#define ADD_STRING_EX(name,buf) \ + php_register_variable(name, buf, track_vars_array TSRMLS_CC) +#define ADD_STRING(name) ADD_STRING_EX((name), buf) + +static void sapi_thttpd_register_variables(zval *track_vars_array TSRMLS_DC) +{ + char buf[BUF_SIZE + 1]; + char *p; + + php_register_variable("PHP_SELF", SG(request_info).request_uri, track_vars_array TSRMLS_CC); + php_register_variable("SERVER_SOFTWARE", SERVER_SOFTWARE, track_vars_array TSRMLS_CC); + php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_METHOD", (char *) SG(request_info).request_method, track_vars_array TSRMLS_CC); + php_register_variable("REQUEST_URI", SG(request_info).request_uri, track_vars_array TSRMLS_CC); + php_register_variable("PATH_TRANSLATED", SG(request_info).path_translated, track_vars_array TSRMLS_CC); + + if (TG(hc)->one_one) { + php_register_variable("SERVER_PROTOCOL", "HTTP/1.1", track_vars_array TSRMLS_CC); + } else { + php_register_variable("SERVER_PROTOCOL", "HTTP/1.0", track_vars_array TSRMLS_CC); + } + + p = httpd_ntoa(&TG(hc)->client_addr); + + ADD_STRING_EX("REMOTE_ADDR", p); + ADD_STRING_EX("REMOTE_HOST", p); + + ADD_STRING_EX("SERVER_PORT", + smart_str_print_long(buf + sizeof(buf) - 1, + TG(hc)->hs->port)); + + buf[0] = '/'; + memcpy(buf + 1, TG(hc)->pathinfo, strlen(TG(hc)->pathinfo) + 1); + ADD_STRING("PATH_INFO"); + + buf[0] = '/'; + memcpy(buf + 1, TG(hc)->origfilename, strlen(TG(hc)->origfilename) + 1); + ADD_STRING("SCRIPT_NAME"); + +#define CONDADD(name, field) \ + if (TG(hc)->field[0]) { \ + php_register_variable(#name, TG(hc)->field, track_vars_array TSRMLS_CC); \ + } + + CONDADD(QUERY_STRING, query); + CONDADD(HTTP_HOST, hdrhost); + CONDADD(HTTP_REFERER, referer); + CONDADD(HTTP_USER_AGENT, useragent); + CONDADD(HTTP_ACCEPT, accept); + CONDADD(HTTP_ACCEPT_LANGUAGE, acceptl); + CONDADD(HTTP_ACCEPT_ENCODING, accepte); + CONDADD(HTTP_COOKIE, cookie); + CONDADD(CONTENT_TYPE, contenttype); + CONDADD(REMOTE_USER, remoteuser); + CONDADD(SERVER_PROTOCOL, protocol); + + if (TG(hc)->contentlength != -1) { + ADD_STRING_EX("CONTENT_LENGTH", + smart_str_print_long(buf + sizeof(buf) - 1, + TG(hc)->contentlength)); + } + + if (TG(hc)->authorization[0]) + php_register_variable("AUTH_TYPE", "Basic", track_vars_array TSRMLS_CC); +} + +static PHP_MINIT_FUNCTION(thttpd) +{ + return SUCCESS; +} + +static zend_module_entry php_thttpd_module = { + STANDARD_MODULE_HEADER, + "thttpd", + NULL, + PHP_MINIT(thttpd), + NULL, + NULL, + NULL, + NULL, /* info */ + NULL, + STANDARD_MODULE_PROPERTIES +}; + +static int php_thttpd_startup(sapi_module_struct *sapi_module) +{ +#if PHP_API_VERSION >= 20020918 + if (php_module_startup(sapi_module, &php_thttpd_module, 1) == FAILURE) { +#else + if (php_module_startup(sapi_module) == FAILURE + || zend_startup_module(&php_thttpd_module) == FAILURE) { +#endif + return FAILURE; + } + return SUCCESS; +} + +static int sapi_thttpd_get_fd(int *nfd TSRMLS_DC) +{ + if (nfd) *nfd = TG(hc)->conn_fd; + return SUCCESS; +} + +static sapi_module_struct thttpd_sapi_module = { + "thttpd", + "thttpd", + + php_thttpd_startup, + php_module_shutdown_wrapper, + + NULL, /* activate */ + NULL, /* deactivate */ + + sapi_thttpd_ub_write, + NULL, + NULL, /* get uid */ + NULL, /* getenv */ + + php_error, + + NULL, + sapi_thttpd_send_headers, + NULL, + sapi_thttpd_read_post, + sapi_thttpd_read_cookies, + + sapi_thttpd_register_variables, + NULL, /* Log message */ + NULL, /* Get request time */ + NULL, /* Child terminate */ + + NULL, /* php.ini path override */ + NULL, /* Block interruptions */ + NULL, /* Unblock interruptions */ + + NULL, + NULL, + NULL, + 0, + sapi_thttpd_get_fd +}; + +static void thttpd_module_main(int show_source TSRMLS_DC) +{ + zend_file_handle file_handle; + + if (php_request_startup(TSRMLS_C) == FAILURE) { + return; + } + + if (show_source) { + zend_syntax_highlighter_ini syntax_highlighter_ini; + + php_get_highlight_struct(&syntax_highlighter_ini); + highlight_file(SG(request_info).path_translated, &syntax_highlighter_ini TSRMLS_CC); + } else { + file_handle.type = ZEND_HANDLE_FILENAME; + file_handle.filename = SG(request_info).path_translated; + file_handle.free_filename = 0; + file_handle.opened_path = NULL; + + php_execute_script(&file_handle TSRMLS_CC); + } + + php_request_shutdown(NULL); +} + +static void thttpd_request_ctor(TSRMLS_D) +{ + smart_str s = {0}; + + TG(seen_cl) = 0; + TG(seen_cn) = 0; + TG(sbuf).c = 0; + SG(request_info).query_string = TG(hc)->query?strdup(TG(hc)->query):NULL; + + smart_str_appends_ex(&s, TG(hc)->hs->cwd, 1); + smart_str_appends_ex(&s, TG(hc)->expnfilename, 1); + smart_str_0(&s); + SG(request_info).path_translated = s.c; + + s.c = NULL; + smart_str_appendc_ex(&s, '/', 1); + smart_str_appends_ex(&s, TG(hc)->origfilename, 1); + smart_str_0(&s); + SG(request_info).request_uri = s.c; + SG(request_info).request_method = httpd_method_str(TG(hc)->method); + if (TG(hc)->one_one) SG(request_info).proto_num = 1001; + else SG(request_info).proto_num = 1000; + SG(sapi_headers).http_response_code = 200; + if (TG(hc)->contenttype) + SG(request_info).content_type = strdup(TG(hc)->contenttype); + SG(request_info).content_length = TG(hc)->contentlength == -1 ? 0 + : TG(hc)->contentlength; + + TG(unconsumed_length) = SG(request_info).content_length; + + php_handle_auth_data(TG(hc)->authorization TSRMLS_CC); +} + +static void thttpd_request_dtor(TSRMLS_D) +{ + smart_str_free_ex(&TG(sbuf), 1); + if (SG(request_info).query_string) + free(SG(request_info).query_string); + free(SG(request_info).request_uri); + free(SG(request_info).path_translated); + if (SG(request_info).content_type) + free(SG(request_info).content_type); +} + +#ifdef ZTS + +#ifdef TSRM_ST +#define thread_create_simple_detached(n) st_thread_create(n, NULL, 0, 0) +#define thread_usleep(n) st_usleep(n) +#define thread_exit() st_thread_exit(NULL) +/* No preemption, simple operations are safe */ +#define thread_atomic_inc(n) (++n) +#define thread_atomic_dec(n) (--n) +#else +#error No thread primitives available +#endif + +/* We might want to replace this with a STAILQ */ +typedef struct qreq { + httpd_conn *hc; + struct qreq *next; +} qreq_t; + +static MUTEX_T qr_lock; +static qreq_t *queued_requests; +static qreq_t *last_qr; +static int nr_free_threads; +static int nr_threads; +static int max_threads = 50; + +#define HANDLE_STRINGS() { \ + HANDLE_STR(encodedurl); \ + HANDLE_STR(decodedurl); \ + HANDLE_STR(origfilename); \ + HANDLE_STR(expnfilename); \ + HANDLE_STR(pathinfo); \ + HANDLE_STR(query); \ + HANDLE_STR(referer); \ + HANDLE_STR(useragent); \ + HANDLE_STR(accept); \ + HANDLE_STR(accepte); \ + HANDLE_STR(acceptl); \ + HANDLE_STR(cookie); \ + HANDLE_STR(contenttype); \ + HANDLE_STR(authorization); \ + HANDLE_STR(remoteuser); \ + } + +static httpd_conn *duplicate_conn(httpd_conn *hc, httpd_conn *nhc) +{ + memcpy(nhc, hc, sizeof(*nhc)); + +#define HANDLE_STR(m) nhc->m = nhc->m ? strdup(nhc->m) : NULL + HANDLE_STRINGS(); +#undef HANDLE_STR + + return nhc; +} + +static void destroy_conn(httpd_conn *hc) +{ +#define HANDLE_STR(m) if (hc->m) free(hc->m) + HANDLE_STRINGS(); +#undef HANDLE_STR +} + +static httpd_conn *dequeue_request(void) +{ + httpd_conn *ret = NULL; + qreq_t *m; + + tsrm_mutex_lock(qr_lock); + if (queued_requests) { + m = queued_requests; + ret = m->hc; + if (!(queued_requests = m->next)) + last_qr = NULL; + free(m); + } + tsrm_mutex_unlock(qr_lock); + + return ret; +} + +static void *worker_thread(void *); + +static void queue_request(httpd_conn *hc) +{ + qreq_t *m; + httpd_conn *nhc; + + /* Mark as long-running request */ + hc->file_address = (char *) 1; + + /* + * We cannot synchronously revoke accesses to hc in the worker + * thread, so we need to pass a copy of hc to the worker thread. + */ + nhc = malloc(sizeof *nhc); + duplicate_conn(hc, nhc); + + /* Allocate request queue container */ + m = malloc(sizeof *m); + m->hc = nhc; + m->next = NULL; + + tsrm_mutex_lock(qr_lock); + /* Create new threads when reaching a certain threshhold */ + if (nr_threads < max_threads && nr_free_threads < 2) { + nr_threads++; /* protected by qr_lock */ + + thread_atomic_inc(nr_free_threads); + thread_create_simple_detached(worker_thread); + } + /* Insert container into request queue */ + if (queued_requests) + last_qr->next = m; + else + queued_requests = m; + last_qr = m; + tsrm_mutex_unlock(qr_lock); +} + +static off_t thttpd_real_php_request(httpd_conn *hc, int TSRMLS_DC); + +static void *worker_thread(void *dummy) +{ + int do_work = 50; + httpd_conn *hc; + + while (do_work) { + hc = dequeue_request(); + + if (!hc) { +/* do_work--; */ + thread_usleep(500000); + continue; + } +/* do_work = 50; */ + + thread_atomic_dec(nr_free_threads); + + thttpd_real_php_request(hc, 0 TSRMLS_CC); + shutdown(hc->conn_fd, 0); + destroy_conn(hc); + free(hc); + + thread_atomic_inc(nr_free_threads); + } + thread_atomic_dec(nr_free_threads); + thread_atomic_dec(nr_threads); + thread_exit(); +} + +static void remove_dead_conn(int fd) +{ + qreq_t *m, *prev = NULL; + + tsrm_mutex_lock(qr_lock); + m = queued_requests; + while (m) { + if (m->hc->conn_fd == fd) { + if (prev) + if (!(prev->next = m->next)) + last_qr = prev; + else + if (!(queued_requests = m->next)) + last_qr = NULL; + destroy_conn(m->hc); + free(m->hc); + free(m); + break; + } + prev = m; + m = m->next; + } + tsrm_mutex_unlock(qr_lock); +} + +#endif + +static off_t thttpd_real_php_request(httpd_conn *hc, int show_source TSRMLS_DC) +{ + TG(hc) = hc; + hc->bytes_sent = 0; + + if (hc->contentlength != -1) { + hc->should_linger = 1; + hc->do_keep_alive = 0; + } + + if (hc->contentlength != -1 + && SIZEOF_UNCONSUMED_BYTES() < hc->contentlength) { + hc->read_body_into_mem = 1; + return 0; + } + + thttpd_request_ctor(TSRMLS_C); + + thttpd_module_main(show_source TSRMLS_CC); + + /* disable kl, if no content-length was seen or Connection: was set */ + if (TG(seen_cl) == 0 || TG(seen_cn) == 1) { + TG(hc)->do_keep_alive = 0; + } + + if (TG(sbuf).c != 0) { + if (TG(hc)->response) + free(TG(hc)->response); + + TG(hc)->response = TG(sbuf).c; + TG(hc)->responselen = TG(sbuf).len; + TG(hc)->maxresponse = TG(sbuf).a; + + TG(sbuf).c = 0; + TG(sbuf).len = 0; + TG(sbuf).a = 0; + } + + thttpd_request_dtor(TSRMLS_C); + + return 0; +} + +off_t thttpd_php_request(httpd_conn *hc, int show_source) +{ +#ifdef ZTS + queue_request(hc); +#else + TSRMLS_FETCH(); + return thttpd_real_php_request(hc, show_source TSRMLS_CC); +#endif +} + +void thttpd_register_on_close(void (*arg)(int)) +{ + TSRMLS_FETCH(); + TG(on_close) = arg; +} + +void thttpd_closed_conn(int fd) +{ + TSRMLS_FETCH(); + if (TG(on_close)) TG(on_close)(fd); +} + +int thttpd_get_fd(void) +{ + TSRMLS_FETCH(); + return TG(hc)->conn_fd; +} + +void thttpd_set_dont_close(void) +{ + TSRMLS_FETCH(); +#ifndef PREMIUM_THTTPD + TG(hc)->file_address = (char *) 1; +#endif +} + + +void thttpd_php_init(void) +{ + char *ini; + +#ifdef ZTS + tsrm_startup(1, 1, 0, NULL); + ts_allocate_id(&thttpd_globals_id, sizeof(php_thttpd_globals), NULL, NULL); + qr_lock = tsrm_mutex_alloc(); + thttpd_register_on_close(remove_dead_conn); +#endif + + if ((ini = getenv("PHP_INI_PATH"))) { + thttpd_sapi_module.php_ini_path_override = ini; + } + + sapi_startup(&thttpd_sapi_module); + thttpd_sapi_module.startup(&thttpd_sapi_module); + + { + TSRMLS_FETCH(); + + SG(server_context) = (void *) 1; + } +} + +void thttpd_php_shutdown(void) +{ + TSRMLS_FETCH(); + + if (SG(server_context) != NULL) { + thttpd_sapi_module.shutdown(&thttpd_sapi_module); + sapi_shutdown(); +#ifdef ZTS + tsrm_shutdown(); +#endif + } +} |