php-src/sapi/thttpd/thttpd.c
Rasmus Lerdorf b911467d1d MFB
Here are the signal changes from the 5.3 branch that optimizes signal
handler registration and switches from longjmp to siglongjmp in order
to make signal mask handling consistent across different UNIX operating
systems.
2008-03-19 16:37:49 +00:00

773 lines
17 KiB
C

/*
+----------------------------------------------------------------------+
| PHP Version 5 |
+----------------------------------------------------------------------+
| Copyright (c) 1997-2008 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
}
}