summaryrefslogtreecommitdiff
path: root/sapi/thttpd/thttpd.c
blob: f6ba8585b93de8bb9435768b43f3b4af50f74fb3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
/*
   +----------------------------------------------------------------------+
   | PHP Version 5                                                        |
   +----------------------------------------------------------------------+
   | Copyright (c) 1997-2014 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 TSRMLS_DC)
{
#if PHP_API_VERSION >= 20020918
	if (php_module_startup(sapi_module, &php_thttpd_module, 1) == FAILURE) {
#else
	/* No TSRMLS_CC here to zend_startup_module() as 5.6 and older does not have that parameter */
	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 threshold */
	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
	}
}