2
+----------------------------------------------------------------------+
4
+----------------------------------------------------------------------+
5
| Copyright (c) 1997-2004 The PHP Group |
6
+----------------------------------------------------------------------+
7
| This source file is subject to version 3.0 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_0.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: Sascha Schumann <sascha@schumann.cx> |
16
+----------------------------------------------------------------------+
19
/* $Id: thttpd.c,v 1.92 2004/01/08 08:18:18 andi Exp $ */
24
#include "php_thttpd.h"
25
#include "php_variables.h"
28
#include "zend_highlight.h"
30
#include "ext/standard/php_smart_str.h"
33
#include <sys/types.h>
38
#ifdef HAVE_GETNAMEINFO
39
#include <sys/socket.h>
45
void (*on_close)(int);
47
size_t unconsumed_length;
53
#define PHP_SYS_CALL(x) do { x } while (n == -1 && errno == EINTR)
56
# define do_keep_alive persistent
60
static int thttpd_globals_id;
61
#define TG(v) TSRMG(thttpd_globals_id, php_thttpd_globals *, v)
63
static php_thttpd_globals thttpd_globals;
64
#define TG(v) (thttpd_globals.v)
67
static int sapi_thttpd_ub_write(const char *str, uint str_length TSRMLS_DC)
72
if (TG(sbuf).c != 0) {
73
smart_str_appendl_ex(&TG(sbuf), str, str_length, 1);
77
while (str_length > 0) {
78
PHP_SYS_CALL(n = send(TG(hc)->conn_fd, str, str_length, 0););
81
if (errno == EAGAIN) {
82
smart_str_appendl_ex(&TG(sbuf), str, str_length, 1);
84
return sent + str_length;
86
php_handle_aborted_connection();
89
TG(hc)->bytes_sent += n;
98
#define COMBINE_HEADERS 64
101
# if IOV_MAX - 64 <= 0
102
# define SERIALIZE_HEADERS
106
static int do_writev(struct iovec *vec, int nvec, int len TSRMLS_DC)
110
assert(nvec <= IOV_MAX);
112
if (TG(sbuf).c == 0) {
113
PHP_SYS_CALL(n = writev(TG(hc)->conn_fd, vec, nvec););
116
if (errno == EAGAIN) {
119
php_handle_aborted_connection();
124
TG(hc)->bytes_sent += n;
132
/* merge all unwritten data into sbuf */
133
for (i = 0; i < nvec; vec++, i++) {
134
/* has this vector been written completely? */
135
if (n >= vec->iov_len) {
143
vec->iov_base = (char *) vec->iov_base + n;
148
smart_str_appendl_ex(&TG(sbuf), vec->iov_base, vec->iov_len, 1);
155
#ifdef SERIALIZE_HEADERS
156
# define ADD_VEC(str,l) smart_str_appendl(&vec_str, (str), (l))
157
# define VEC_BASE() smart_str vec_str = {0}
158
# define VEC_FREE() smart_str_free(&vec_str)
160
# define ADD_VEC(str,l) vec[n].iov_base=str;len += (vec[n].iov_len=l); n++
161
# define VEC_BASE() struct iovec vec[COMBINE_HEADERS]
162
# define VEC_FREE() do {} while (0)
165
#define ADD_VEC_S(str) ADD_VEC((str), sizeof(str)-1)
167
#define CL_TOKEN "Content-length: "
168
#define CN_TOKEN "Connection: "
169
#define KA_DO "Connection: keep-alive\r\n"
170
#define KA_NO "Connection: close\r\n"
171
#define DEF_CT "Content-Type: text/html\r\n"
173
static int sapi_thttpd_send_headers(sapi_headers_struct *sapi_headers TSRMLS_DC)
178
zend_llist_position pos;
179
sapi_header_struct *h;
182
if (!SG(sapi_headers).http_status_line) {
183
ADD_VEC_S("HTTP/1.1 ");
184
p = smart_str_print_long(buf+sizeof(buf)-1,
185
SG(sapi_headers).http_response_code);
186
ADD_VEC(p, strlen(p));
187
ADD_VEC_S(" HTTP\r\n");
189
ADD_VEC(SG(sapi_headers).http_status_line,
190
strlen(SG(sapi_headers).http_status_line));
193
TG(hc)->status = SG(sapi_headers).http_response_code;
195
if (SG(sapi_headers).send_default_content_type) {
196
ADD_VEC(DEF_CT, strlen(DEF_CT));
199
h = zend_llist_get_first_ex(&sapi_headers->headers, &pos);
202
switch (h->header[0]) {
204
if (!TG(seen_cl) && strncasecmp(h->header, CL_TOKEN, sizeof(CL_TOKEN)-1) == 0) {
206
} else if (!TG(seen_cn) && strncasecmp(h->header, CN_TOKEN, sizeof(CN_TOKEN)-1) == 0) {
211
ADD_VEC(h->header, h->header_len);
212
#ifndef SERIALIZE_HEADERS
213
if (n >= COMBINE_HEADERS - 1) {
214
len = do_writev(vec, n, len TSRMLS_CC);
220
h = zend_llist_get_next_ex(&sapi_headers->headers, &pos);
223
if (TG(seen_cl) && !TG(seen_cn) && TG(hc)->do_keep_alive) {
224
ADD_VEC(KA_DO, sizeof(KA_DO)-1);
226
TG(hc)->do_keep_alive = 0;
227
ADD_VEC(KA_NO, sizeof(KA_NO)-1);
232
#ifdef SERIALIZE_HEADERS
233
sapi_thttpd_ub_write(vec_str.c, vec_str.len TSRMLS_CC);
235
do_writev(vec, n, len TSRMLS_CC);
240
return SAPI_HEADER_SENT_SUCCESSFULLY;
243
/* to understand this, read cgi_interpose_input() in libhttpd.c */
244
#define SIZEOF_UNCONSUMED_BYTES() (TG(hc)->read_idx - TG(hc)->checked_idx)
245
#define CONSUME_BYTES(n) do { TG(hc)->checked_idx += (n); } while (0)
248
static int sapi_thttpd_read_post(char *buffer, uint count_bytes TSRMLS_DC)
250
size_t read_bytes = 0;
252
if (TG(unconsumed_length) > 0) {
253
read_bytes = MIN(TG(unconsumed_length), count_bytes);
254
memcpy(buffer, TG(hc)->read_buf + TG(hc)->checked_idx, read_bytes);
255
TG(unconsumed_length) -= read_bytes;
256
CONSUME_BYTES(read_bytes);
262
static char *sapi_thttpd_read_cookies(TSRMLS_D)
264
return TG(hc)->cookie;
268
#define ADD_STRING_EX(name,buf) \
269
php_register_variable(name, buf, track_vars_array TSRMLS_CC)
270
#define ADD_STRING(name) ADD_STRING_EX((name), buf)
272
static void sapi_thttpd_register_variables(zval *track_vars_array TSRMLS_DC)
274
char buf[BUF_SIZE + 1];
277
php_register_variable("PHP_SELF", SG(request_info).request_uri, track_vars_array TSRMLS_CC);
278
php_register_variable("SERVER_SOFTWARE", SERVER_SOFTWARE, track_vars_array TSRMLS_CC);
279
php_register_variable("GATEWAY_INTERFACE", "CGI/1.1", track_vars_array TSRMLS_CC);
280
php_register_variable("REQUEST_METHOD", (char *) SG(request_info).request_method, track_vars_array TSRMLS_CC);
281
php_register_variable("REQUEST_URI", SG(request_info).request_uri, track_vars_array TSRMLS_CC);
282
php_register_variable("PATH_TRANSLATED", SG(request_info).path_translated, track_vars_array TSRMLS_CC);
284
if (TG(hc)->one_one) {
285
php_register_variable("SERVER_PROTOCOL", "HTTP/1.1", track_vars_array TSRMLS_CC);
287
php_register_variable("SERVER_PROTOCOL", "HTTP/1.0", track_vars_array TSRMLS_CC);
290
p = httpd_ntoa(&TG(hc)->client_addr);
292
ADD_STRING_EX("REMOTE_ADDR", p);
293
ADD_STRING_EX("REMOTE_HOST", p);
295
ADD_STRING_EX("SERVER_PORT",
296
smart_str_print_long(buf + sizeof(buf) - 1,
300
memcpy(buf + 1, TG(hc)->pathinfo, strlen(TG(hc)->pathinfo) + 1);
301
ADD_STRING("PATH_INFO");
304
memcpy(buf + 1, TG(hc)->origfilename, strlen(TG(hc)->origfilename) + 1);
305
ADD_STRING("SCRIPT_NAME");
307
#define CONDADD(name, field) \
308
if (TG(hc)->field[0]) { \
309
php_register_variable(#name, TG(hc)->field, track_vars_array TSRMLS_CC); \
312
CONDADD(QUERY_STRING, query);
313
CONDADD(HTTP_HOST, hdrhost);
314
CONDADD(HTTP_REFERER, referer);
315
CONDADD(HTTP_USER_AGENT, useragent);
316
CONDADD(HTTP_ACCEPT, accept);
317
CONDADD(HTTP_ACCEPT_LANGUAGE, acceptl);
318
CONDADD(HTTP_ACCEPT_ENCODING, accepte);
319
CONDADD(HTTP_COOKIE, cookie);
320
CONDADD(CONTENT_TYPE, contenttype);
321
CONDADD(REMOTE_USER, remoteuser);
322
CONDADD(SERVER_PROTOCOL, protocol);
324
if (TG(hc)->contentlength != -1) {
325
ADD_STRING_EX("CONTENT_LENGTH",
326
smart_str_print_long(buf + sizeof(buf) - 1,
327
TG(hc)->contentlength));
330
if (TG(hc)->authorization[0])
331
php_register_variable("AUTH_TYPE", "Basic", track_vars_array TSRMLS_CC);
334
static PHP_MINIT_FUNCTION(thttpd)
339
static zend_module_entry php_thttpd_module = {
340
STANDARD_MODULE_HEADER,
349
STANDARD_MODULE_PROPERTIES
352
static int php_thttpd_startup(sapi_module_struct *sapi_module)
354
#if PHP_API_VERSION >= 20020918
355
if (php_module_startup(sapi_module, &php_thttpd_module, 1) == FAILURE) {
357
if (php_module_startup(sapi_module) == FAILURE
358
|| zend_startup_module(&php_thttpd_module) == FAILURE) {
365
static int sapi_thttpd_get_fd(int *nfd TSRMLS_DC)
367
if (nfd) *nfd = TG(hc)->conn_fd;
371
static sapi_module_struct thttpd_sapi_module = {
376
php_module_shutdown_wrapper,
379
NULL, /* deactivate */
381
sapi_thttpd_ub_write,
389
sapi_thttpd_send_headers,
391
sapi_thttpd_read_post,
392
sapi_thttpd_read_cookies,
394
sapi_thttpd_register_variables,
395
NULL, /* Log message */
397
NULL, /* php.ini path override */
398
NULL, /* Block interruptions */
399
NULL, /* Unblock interruptions */
408
static void thttpd_module_main(int show_source TSRMLS_DC)
410
zend_file_handle file_handle;
412
if (php_request_startup(TSRMLS_C) == FAILURE) {
417
zend_syntax_highlighter_ini syntax_highlighter_ini;
419
php_get_highlight_struct(&syntax_highlighter_ini);
420
highlight_file(SG(request_info).path_translated, &syntax_highlighter_ini TSRMLS_CC);
422
file_handle.type = ZEND_HANDLE_FILENAME;
423
file_handle.filename = SG(request_info).path_translated;
424
file_handle.free_filename = 0;
425
file_handle.opened_path = NULL;
427
php_execute_script(&file_handle TSRMLS_CC);
430
php_request_shutdown(NULL);
433
static void thttpd_request_ctor(TSRMLS_D)
440
SG(request_info).query_string = TG(hc)->query?strdup(TG(hc)->query):NULL;
442
smart_str_appends_ex(&s, TG(hc)->hs->cwd, 1);
443
smart_str_appends_ex(&s, TG(hc)->expnfilename, 1);
445
SG(request_info).path_translated = s.c;
448
smart_str_appendc_ex(&s, '/', 1);
449
smart_str_appends_ex(&s, TG(hc)->origfilename, 1);
451
SG(request_info).request_uri = s.c;
452
SG(request_info).request_method = httpd_method_str(TG(hc)->method);
453
SG(sapi_headers).http_response_code = 200;
454
if (TG(hc)->contenttype)
455
SG(request_info).content_type = strdup(TG(hc)->contenttype);
456
SG(request_info).content_length = TG(hc)->contentlength == -1 ? 0
457
: TG(hc)->contentlength;
459
TG(unconsumed_length) = SG(request_info).content_length;
461
php_handle_auth_data(TG(hc)->authorization TSRMLS_CC);
464
static void thttpd_request_dtor(TSRMLS_D)
466
smart_str_free_ex(&TG(sbuf), 1);
467
if (SG(request_info).query_string)
468
free(SG(request_info).query_string);
469
free(SG(request_info).request_uri);
470
free(SG(request_info).path_translated);
471
if (SG(request_info).content_type)
472
free(SG(request_info).content_type);
478
#define thread_create_simple_detached(n) st_thread_create(n, NULL, 0, 0)
479
#define thread_usleep(n) st_usleep(n)
480
#define thread_exit() st_thread_exit(NULL)
481
/* No preemption, simple operations are safe */
482
#define thread_atomic_inc(n) (++n)
483
#define thread_atomic_dec(n) (--n)
485
#error No thread primitives available
488
/* We might want to replace this with a STAILQ */
489
typedef struct qreq {
494
static MUTEX_T qr_lock;
495
static qreq_t *queued_requests;
496
static qreq_t *last_qr;
497
static int nr_free_threads;
498
static int nr_threads;
499
static int max_threads = 50;
501
#define HANDLE_STRINGS() { \
502
HANDLE_STR(encodedurl); \
503
HANDLE_STR(decodedurl); \
504
HANDLE_STR(origfilename); \
505
HANDLE_STR(expnfilename); \
506
HANDLE_STR(pathinfo); \
508
HANDLE_STR(referer); \
509
HANDLE_STR(useragent); \
510
HANDLE_STR(accept); \
511
HANDLE_STR(accepte); \
512
HANDLE_STR(acceptl); \
513
HANDLE_STR(cookie); \
514
HANDLE_STR(contenttype); \
515
HANDLE_STR(authorization); \
516
HANDLE_STR(remoteuser); \
519
static httpd_conn *duplicate_conn(httpd_conn *hc, httpd_conn *nhc)
521
memcpy(nhc, hc, sizeof(*nhc));
523
#define HANDLE_STR(m) nhc->m = nhc->m ? strdup(nhc->m) : NULL
530
static void destroy_conn(httpd_conn *hc)
532
#define HANDLE_STR(m) if (hc->m) free(hc->m)
537
static httpd_conn *dequeue_request(void)
539
httpd_conn *ret = NULL;
542
tsrm_mutex_lock(qr_lock);
543
if (queued_requests) {
546
if (!(queued_requests = m->next))
550
tsrm_mutex_unlock(qr_lock);
555
static void *worker_thread(void *);
557
static void queue_request(httpd_conn *hc)
562
/* Mark as long-running request */
563
hc->file_address = (char *) 1;
566
* We cannot synchronously revoke accesses to hc in the worker
567
* thread, so we need to pass a copy of hc to the worker thread.
569
nhc = malloc(sizeof *nhc);
570
duplicate_conn(hc, nhc);
572
/* Allocate request queue container */
573
m = malloc(sizeof *m);
577
tsrm_mutex_lock(qr_lock);
578
/* Create new threads when reaching a certain threshhold */
579
if (nr_threads < max_threads && nr_free_threads < 2) {
580
nr_threads++; /* protected by qr_lock */
582
thread_atomic_inc(nr_free_threads);
583
thread_create_simple_detached(worker_thread);
585
/* Insert container into request queue */
591
tsrm_mutex_unlock(qr_lock);
594
static off_t thttpd_real_php_request(httpd_conn *hc, int TSRMLS_DC);
596
static void *worker_thread(void *dummy)
602
hc = dequeue_request();
606
thread_usleep(500000);
611
thread_atomic_dec(nr_free_threads);
613
thttpd_real_php_request(hc, 0 TSRMLS_CC);
614
shutdown(hc->conn_fd, 0);
618
thread_atomic_inc(nr_free_threads);
620
thread_atomic_dec(nr_free_threads);
621
thread_atomic_dec(nr_threads);
625
static void remove_dead_conn(int fd)
627
qreq_t *m, *prev = NULL;
629
tsrm_mutex_lock(qr_lock);
632
if (m->hc->conn_fd == fd) {
634
if (!(prev->next = m->next))
637
if (!(queued_requests = m->next))
647
tsrm_mutex_unlock(qr_lock);
652
static off_t thttpd_real_php_request(httpd_conn *hc, int show_source TSRMLS_DC)
657
if (hc->contentlength != -1) {
658
hc->should_linger = 1;
659
hc->do_keep_alive = 0;
662
if (hc->contentlength != -1
663
&& SIZEOF_UNCONSUMED_BYTES() < hc->contentlength) {
664
hc->read_body_into_mem = 1;
668
thttpd_request_ctor(TSRMLS_C);
670
thttpd_module_main(show_source TSRMLS_CC);
672
/* disable kl, if no content-length was seen or Connection: was set */
673
if (TG(seen_cl) == 0 || TG(seen_cn) == 1) {
674
TG(hc)->do_keep_alive = 0;
677
if (TG(sbuf).c != 0) {
678
if (TG(hc)->response)
679
free(TG(hc)->response);
681
TG(hc)->response = TG(sbuf).c;
682
TG(hc)->responselen = TG(sbuf).len;
683
TG(hc)->maxresponse = TG(sbuf).a;
690
thttpd_request_dtor(TSRMLS_C);
695
off_t thttpd_php_request(httpd_conn *hc, int show_source)
701
return thttpd_real_php_request(hc, show_source TSRMLS_CC);
705
void thttpd_register_on_close(void (*arg)(int))
711
void thttpd_closed_conn(int fd)
714
if (TG(on_close)) TG(on_close)(fd);
717
int thttpd_get_fd(void)
720
return TG(hc)->conn_fd;
723
void thttpd_set_dont_close(void)
726
#ifndef PREMIUM_THTTPD
727
TG(hc)->file_address = (char *) 1;
732
void thttpd_php_init(void)
737
tsrm_startup(1, 1, 0, NULL);
738
ts_allocate_id(&thttpd_globals_id, sizeof(php_thttpd_globals), NULL, NULL);
739
qr_lock = tsrm_mutex_alloc();
740
thttpd_register_on_close(remove_dead_conn);
743
if ((ini = getenv("PHP_INI_PATH"))) {
744
thttpd_sapi_module.php_ini_path_override = ini;
747
sapi_startup(&thttpd_sapi_module);
748
thttpd_sapi_module.startup(&thttpd_sapi_module);
753
SG(server_context) = (void *) 1;
757
void thttpd_php_shutdown(void)
761
if (SG(server_context) != NULL) {
762
thttpd_sapi_module.shutdown(&thttpd_sapi_module);