2 +--------------------------------------------------------------------+
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the conditions mentioned |
7 | in the accompanying LICENSE file are met. |
8 +--------------------------------------------------------------------+
9 | Copyright (c) 2004-2010, Michael Wallner <mike@php.net> |
10 +--------------------------------------------------------------------+
15 #define HTTP_WANT_SAPI
18 #include "php_output.h"
19 #include "ext/standard/url.h"
20 #include "ext/standard/php_lcg.h"
22 #include "php_http_api.h"
23 #include "php_http_send_api.h"
26 # include "php_http_exception_object.h"
29 PHP_MINIT_FUNCTION(http_support
)
31 HTTP_LONG_CONSTANT("HTTP_SUPPORT", HTTP_SUPPORT
);
32 HTTP_LONG_CONSTANT("HTTP_SUPPORT_REQUESTS", HTTP_SUPPORT_REQUESTS
);
33 HTTP_LONG_CONSTANT("HTTP_SUPPORT_MAGICMIME", HTTP_SUPPORT_MAGICMIME
);
34 HTTP_LONG_CONSTANT("HTTP_SUPPORT_ENCODINGS", HTTP_SUPPORT_ENCODINGS
);
35 HTTP_LONG_CONSTANT("HTTP_SUPPORT_SSLREQUESTS", HTTP_SUPPORT_SSLREQUESTS
);
36 HTTP_LONG_CONSTANT("HTTP_SUPPORT_EVENTS", HTTP_SUPPORT_EVENTS
);
38 HTTP_LONG_CONSTANT("HTTP_PARAMS_ALLOW_COMMA", HTTP_PARAMS_ALLOW_COMMA
);
39 HTTP_LONG_CONSTANT("HTTP_PARAMS_ALLOW_FAILURE", HTTP_PARAMS_ALLOW_FAILURE
);
40 HTTP_LONG_CONSTANT("HTTP_PARAMS_RAISE_ERROR", HTTP_PARAMS_RAISE_ERROR
);
41 HTTP_LONG_CONSTANT("HTTP_PARAMS_DEFAULT", HTTP_PARAMS_DEFAULT
);
46 PHP_HTTP_API
long _http_support(long feature
)
48 long support
= HTTP_SUPPORT
;
51 support
|= HTTP_SUPPORT_REQUESTS
;
53 support
|= HTTP_SUPPORT_SSLREQUESTS
;
55 # ifdef HTTP_HAVE_EVENT
56 support
|= HTTP_SUPPORT_EVENTS
;
59 #ifdef HTTP_HAVE_MAGIC
60 support
|= HTTP_SUPPORT_MAGICMIME
;
63 support
|= HTTP_SUPPORT_ENCODINGS
;
67 return (feature
== (support
& feature
));
72 /* char *pretty_key(char *, size_t, zend_bool, zend_bool) */
73 char *_http_pretty_key(char *key
, size_t key_len
, zend_bool uctitle
, zend_bool xhyphen
)
79 if ((wasalpha
= HTTP_IS_CTYPE(alpha
, key
[0]))) {
80 key
[0] = (char) (uctitle
? HTTP_TO_CTYPE(upper
, key
[0]) : HTTP_TO_CTYPE(lower
, key
[0]));
82 for (i
= 1; i
< key_len
; i
++) {
83 if (HTTP_IS_CTYPE(alpha
, key
[i
])) {
84 key
[i
] = (char) (((!wasalpha
) && uctitle
) ? HTTP_TO_CTYPE(upper
, key
[i
]) : HTTP_TO_CTYPE(lower
, key
[i
]));
87 if (xhyphen
&& (key
[i
] == '_')) {
98 /* {{{ http_boundary(char *, size_t) */
99 size_t _http_boundary(char *buf
, size_t buf_len TSRMLS_DC
)
101 return snprintf(buf
, buf_len
, "%lu%0.9f", (ulong
) HTTP_G
->request
.time
, (float) php_combined_lcg(TSRMLS_C
));
105 /* {{{ void http_error(long, long, char*) */
106 void _http_error_ex(long type TSRMLS_DC
, long code
, const char *format
, ...)
110 va_start(args
, format
);
112 if ((type
== E_THROW
) || (GLOBAL_ERROR_HANDLING
== EH_THROW
)) {
114 zend_class_entry
*ce
= http_exception_get_for_code(code
);
117 vspprintf(&message
, 0, format
, args
);
118 zend_throw_exception(ce
, message
, code TSRMLS_CC
);
120 } http_catch(GLOBAL_EXCEPTION_CLASS
? GLOBAL_EXCEPTION_CLASS
: HTTP_EX_DEF_CE
);
123 php_verror(NULL
, "", type
, format
, args TSRMLS_CC
);
129 static inline void copy_bt_args(zval
*from
, zval
*to TSRMLS_DC
)
131 zval
**args
, **trace_0
, *old_trace_0
, *trace
= NULL
;
133 if ((trace
= zend_read_property(ZEND_EXCEPTION_GET_DEFAULT(), from
, "trace", lenof("trace"), 0 TSRMLS_CC
))) {
134 if (Z_TYPE_P(trace
) == IS_ARRAY
&& SUCCESS
== zend_hash_index_find(Z_ARRVAL_P(trace
), 0, (void *) &trace_0
)) {
135 old_trace_0
= *trace_0
;
136 if (Z_TYPE_PP(trace_0
) == IS_ARRAY
&& SUCCESS
== zend_hash_find(Z_ARRVAL_PP(trace_0
), "args", sizeof("args"), (void *) &args
)) {
137 if ((trace
= zend_read_property(ZEND_EXCEPTION_GET_DEFAULT(), to
, "trace", lenof("trace"), 0 TSRMLS_CC
))) {
138 if (Z_TYPE_P(trace
) == IS_ARRAY
&& SUCCESS
== zend_hash_index_find(Z_ARRVAL_P(trace
), 0, (void *) &trace_0
)) {
140 add_assoc_zval(*trace_0
, "args", *args
);
148 /* {{{ zval *http_exception_wrap(zval *, zval *, zend_class_entry *) */
149 zval
*_http_exception_wrap(zval
*old_exception
, zval
*new_exception
, zend_class_entry
*ce TSRMLS_DC
)
153 zval
*sub_exception
, *tmp_exception
;
155 if (!new_exception
) {
156 MAKE_STD_ZVAL(new_exception
);
157 object_init_ex(new_exception
, ce
);
159 zend_update_property(ce
, new_exception
, "innerException", lenof("innerException"), old_exception TSRMLS_CC
);
160 copy_bt_args(old_exception
, new_exception TSRMLS_CC
);
162 sub_exception
= old_exception
;
164 while ((sub_exception
= zend_read_property(Z_OBJCE_P(sub_exception
), sub_exception
, "innerException", lenof("innerException"), 0 TSRMLS_CC
)) && Z_TYPE_P(sub_exception
) == IS_OBJECT
) {
168 spprintf(&message
, 0, "Exception caused by %d inner exception(s)", inner
);
169 zend_update_property_string(ZEND_EXCEPTION_GET_DEFAULT(), new_exception
, "message", lenof("message"), message TSRMLS_CC
);
172 sub_exception
= new_exception
;
173 tmp_exception
= new_exception
;
175 while ((tmp_exception
= zend_read_property(Z_OBJCE_P(tmp_exception
), tmp_exception
, "innerException", lenof("innerException"), 0 TSRMLS_CC
)) && Z_TYPE_P(tmp_exception
) == IS_OBJECT
) {
176 sub_exception
= tmp_exception
;
179 zend_update_property(Z_OBJCE_P(sub_exception
), sub_exception
, "innerException", lenof("innerException"), old_exception TSRMLS_CC
);
180 copy_bt_args(old_exception
, new_exception TSRMLS_CC
);
181 copy_bt_args(old_exception
, sub_exception TSRMLS_CC
);
183 #if PHP_MAJOR_VERSION == 5 && PHP_MINOR_VERSION >= 3
184 Z_ADDREF_P(old_exception
);
185 zend_exception_set_previous(new_exception
, old_exception TSRMLS_CC
);
187 zval_ptr_dtor(&old_exception
);
188 return new_exception
;
192 /* {{{ STATUS http_object_new(zend_object_value *, const char *, uint, http_object_new_t, zend_class_entry *, void *, void **) */
193 STATUS
_http_object_new(zend_object_value
*ov
, const char *cname_str
, uint cname_len
, http_object_new_t create
, zend_class_entry
*parent_ce
, void *intern_ptr
, void **obj_ptr TSRMLS_DC
)
195 zend_class_entry
*ce
= parent_ce
;
197 if (cname_str
&& cname_len
) {
198 if (!(ce
= zend_fetch_class(HTTP_ZAPI_CONST_CAST(char *) cname_str
, cname_len
, ZEND_FETCH_CLASS_DEFAULT TSRMLS_CC
))) {
201 if (!instanceof_function(ce
, parent_ce TSRMLS_CC
)) {
202 http_error_ex(HE_WARNING
, HTTP_E_RUNTIME
, "Class %s does not extend %s", cname_str
, parent_ce
->name
);
207 *ov
= create(ce
, intern_ptr
, obj_ptr TSRMLS_CC
);
211 #endif /* ZEND_ENGINE_2 */
213 /* {{{ void http_log(char *, char *, char *) */
214 void _http_log_ex(char *file
, const char *ident
, const char *message TSRMLS_DC
)
218 char datetime
[20] = {0};
220 now
= HTTP_G
->request
.time
;
221 strftime(datetime
, sizeof(datetime
), "%Y-%m-%d %H:%M:%S", php_localtime_r(&now
, &nowtm
));
223 #define HTTP_LOG_WRITE(file, type, msg) \
224 if (file && *file) { \
225 php_stream *log = php_stream_open_wrapper_ex(file, "ab", REPORT_ERRORS|ENFORCE_SAFE_MODE, NULL, HTTP_DEFAULT_STREAM_CONTEXT); \
228 php_stream_printf(log TSRMLS_CC, "%s\t[%s]\t%s\t<%s>%s", datetime, type, msg, SG(request_info).request_uri, PHP_EOL); \
229 php_stream_close(log); \
234 HTTP_LOG_WRITE(file
, ident
, message
);
235 HTTP_LOG_WRITE(HTTP_G
->log
.composite
, ident
, message
);
239 static void http_ob_blackhole(char *output
, uint output_len
, char **handled_output
, uint
*handled_output_len
, int mode TSRMLS_DC
)
241 *handled_output
= ecalloc(1,1);
242 *handled_output_len
= 0;
245 /* {{{ STATUS http_exit(int, char*, char*) */
246 STATUS
_http_exit_ex(int status
, char *header
, char *body
, zend_bool send_header TSRMLS_DC
)
248 if ( (send_header
&& (SUCCESS
!= http_send_status_header(status
, header
))) ||
249 (status
&& (SUCCESS
!= http_send_status(status
)))) {
250 http_error_ex(HE_WARNING
, HTTP_E_HEADER
, "Failed to exit with status/header: %d - %s", status
, STR_PTR(header
));
256 if (!php_ob_handler_used("zlib output compression") && !php_ob_handler_used("ob_gzhandler") && !OG(ob_lock
)) {
257 php_end_ob_buffers(0 TSRMLS_CC
);
259 if ((SUCCESS
== sapi_send_headers(TSRMLS_C
)) && body
) {
260 PHPWRITE(body
, strlen(body
));
264 case 301: http_log(HTTP_G
->log
.redirect
, "301-REDIRECT", header
); break;
265 case 302: http_log(HTTP_G
->log
.redirect
, "302-REDIRECT", header
); break;
266 case 303: http_log(HTTP_G
->log
.redirect
, "303-REDIRECT", header
); break;
267 case 305: http_log(HTTP_G
->log
.redirect
, "305-REDIRECT", header
); break;
268 case 307: http_log(HTTP_G
->log
.redirect
, "307-REDIRECT", header
); break;
269 case 304: http_log(HTTP_G
->log
.cache
, "304-CACHE", header
); break;
270 case 404: http_log(HTTP_G
->log
.not_found
, "404-NOTFOUND", NULL
); break;
271 case 405: http_log(HTTP_G
->log
.allowed_methods
, "405-ALLOWED", header
); break;
272 default: http_log(NULL
, header
, body
); break;
278 if (HTTP_G
->force_exit
) {
281 php_ob_set_internal_handler(http_ob_blackhole
, 4096, "blackhole", 0 TSRMLS_CC
);
288 /* {{{ STATUS http_check_method(char *) */
289 STATUS
_http_check_method_ex(const char *method
, const char *methods
)
293 if ( (found
= strstr(methods
, method
)) &&
294 (found
== method
|| !HTTP_IS_CTYPE(alpha
, found
[-1])) &&
295 (strlen(found
) >= strlen(method
) && !HTTP_IS_CTYPE(alpha
, found
[strlen(method
)]))) {
302 /* {{{ zval *http_get_server_var_ex(char *, size_t) */
303 PHP_HTTP_API zval
*_http_get_server_var_ex(const char *key
, size_t key_len
, zend_bool check TSRMLS_DC
)
308 /* if available, this is a lot faster than accessing $_SERVER */
309 if (sapi_module
.getenv
) {
310 if ((!(env
= sapi_module
.getenv((char *) key
, key_len TSRMLS_CC
))) || (check
&& !*env
)) {
313 if (HTTP_G
->server_var
) {
314 zval_ptr_dtor(&HTTP_G
->server_var
);
316 MAKE_STD_ZVAL(HTTP_G
->server_var
);
317 ZVAL_STRING(HTTP_G
->server_var
, env
, 1);
318 return HTTP_G
->server_var
;
322 zend_is_auto_global("_SERVER", lenof("_SERVER") TSRMLS_CC
);
325 if ((SUCCESS
!= zend_hash_find(&EG(symbol_table
), "_SERVER", sizeof("_SERVER"), (void *) &hsv
)) || (Z_TYPE_PP(hsv
) != IS_ARRAY
)) {
328 if ((SUCCESS
!= zend_hash_find(Z_ARRVAL_PP(hsv
), HTTP_ZAPI_CONST_CAST(char *) key
, key_len
+ 1, (void *) &var
))) {
331 if (check
&& !((Z_TYPE_PP(var
) == IS_STRING
) && Z_STRVAL_PP(var
) && Z_STRLEN_PP(var
))) {
338 /* {{{ STATUS http_get_request_body(char **, size_t *) */
339 PHP_HTTP_API STATUS
_http_get_request_body_ex(char **body
, size_t *length
, zend_bool dup TSRMLS_DC
)
344 if (SG(request_info
).raw_post_data
) {
345 *length
= SG(request_info
).raw_post_data_length
;
346 *body
= SG(request_info
).raw_post_data
;
349 *body
= estrndup(*body
, *length
);
352 } else if (sapi_module
.read_post
&& !HTTP_G
->read_post_data
) {
353 char *buf
= emalloc(4096);
356 HTTP_G
->read_post_data
= 1;
358 while (0 < (len
= sapi_module
.read_post(buf
, 4096 TSRMLS_CC
))) {
359 SG(read_post_bytes
) += len
;
360 *body
= erealloc(*body
, *length
+ len
+ 1);
361 memcpy(*body
+ *length
, buf
, len
);
363 (*body
)[*length
] = '\0';
370 /* check for error */
377 SG(request_info
).raw_post_data
= *body
;
378 SG(request_info
).raw_post_data_length
= *length
;
381 *body
= estrndup(*body
, *length
);
390 /* {{{ php_stream *http_get_request_body_stream(void) */
391 PHP_HTTP_API php_stream
*_http_get_request_body_stream(TSRMLS_D
)
393 php_stream
*s
= NULL
;
395 if (SG(request_info
).raw_post_data
) {
396 s
= php_stream_open_wrapper("php://input", "rb", 0, NULL
);
397 } else if (sapi_module
.read_post
&& !HTTP_G
->read_post_data
) {
398 HTTP_G
->read_post_data
= 1;
400 if ((s
= php_stream_temp_new())) {
401 char *buf
= emalloc(4096);
404 while (0 < (len
= sapi_module
.read_post(buf
, 4096 TSRMLS_CC
))) {
405 php_stream_write(s
, buf
, len
);
416 php_stream_rewind(s
);
425 /* {{{ void http_parse_params_default_callback(...) */
426 PHP_HTTP_API
void _http_parse_params_default_callback(void *arg
, const char *key
, int keylen
, const char *val
, int vallen TSRMLS_DC
)
430 HashTable
*ht
= (HashTable
*) arg
;
436 MAKE_STD_ZVAL(entry
);
439 kdup
= estrndup(key
, keylen
);
440 add_assoc_stringl_ex(entry
, kdup
, keylen
+ 1, (char *) val
, vallen
, 1);
443 add_next_index_stringl(entry
, (char *) val
, vallen
, 1);
445 add_next_index_zval(&tmp
, entry
);
447 add_next_index_stringl(&tmp
, (char *) key
, keylen
, 1);
453 /* {{{ STATUS http_parse_params(const char *, HashTable *) */
454 PHP_HTTP_API STATUS
_http_parse_params_ex(const char *param
, int flags
, http_parse_params_callback cb
, void *cb_arg TSRMLS_DC
)
462 int st
= ST_KEY
, keylen
= 0, vallen
= 0;
463 char *s
, *c
, *key
= NULL
, *val
= NULL
;
465 for(c
= s
= estrdup(param
);;) {
469 char *tk
= NULL
, *tv
= NULL
;
473 tk
= estrndup(key
, keylen
);
477 tk
[3]='.'; tk
[4]='.'; tk
[5]='.';
482 tv
= estrndup(val
, vallen
);
486 tv
[3]='.'; tv
[4]='.'; tv
[5]='.';
489 fprintf(stderr
, "[%6s] %c \"%s=%s\"\n",
491 st
== ST_QUOTE
? "QUOTE" :
492 st
== ST_VALUE
? "VALUE" :
493 st
== ST_KEY
? "KEY" :
494 st
== ST_ASSIGN
? "ASSIGN" :
495 st
== ST_ADD
? "ADD":
499 STR_FREE(tk
); STR_FREE(tv
);
506 if (*(c
-1) == '\\') {
507 memmove(c
-1, c
, strlen(c
)+1);
539 if (flags
& HTTP_PARAMS_ALLOW_COMMA
) {
553 if (flags
& HTTP_PARAMS_ALLOW_COMMA
) {
581 if (!(flags
& HTTP_PARAMS_COLON_SEPARATOR
)) {
593 if (flags
& HTTP_PARAMS_COLON_SEPARATOR
) {
616 } else if (!*c
|| *c
== ';' || ((flags
& HTTP_PARAMS_ALLOW_COMMA
) && *c
== ',')) {
618 } else if (*c
!= ' ') {
627 if (st
!= ST_QUOTE
) {
628 while (val
[vallen
-1] == ' ') --vallen
;
635 cb(cb_arg
, key
, keylen
, val
, vallen TSRMLS_CC
);
644 } else if (st
== ST_ADD
) {
655 if (flags
& HTTP_PARAMS_RAISE_ERROR
) {
656 http_error_ex(HE_WARNING
, HTTP_E_INVALID_PARAM
, "Unexpected character (%c) at pos %tu of %zu", *c
, c
-s
, strlen(s
));
658 if (flags
& HTTP_PARAMS_ALLOW_FAILURE
) {
677 int apply_array_append_func(void *pDest HTTP_ZAPI_HASH_TSRMLS_DC
, int num_args
, va_list args
, zend_hash_key
*hash_key
)
682 zval
**data
= NULL
, **value
= (zval
**) pDest
;
684 dst
= va_arg(args
, HashTable
*);
685 flags
= va_arg(args
, int);
687 if ((!(flags
& ARRAY_JOIN_STRONLY
)) || hash_key
->nKeyLength
) {
688 if ((flags
& ARRAY_JOIN_PRETTIFY
) && hash_key
->nKeyLength
) {
689 key
= pretty_key(estrndup(hash_key
->arKey
, hash_key
->nKeyLength
- 1), hash_key
->nKeyLength
- 1, 1, 1);
690 zend_hash_find(dst
, key
, hash_key
->nKeyLength
, (void *) &data
);
692 zend_hash_quick_find(dst
, hash_key
->arKey
, hash_key
->nKeyLength
, hash_key
->h
, (void *) &data
);
697 add_next_index_zval(http_zset(IS_ARRAY
, *data
), *value
);
699 zend_hash_add(dst
, key
, hash_key
->nKeyLength
, value
, sizeof(zval
*), NULL
);
701 zend_hash_quick_add(dst
, hash_key
->arKey
, hash_key
->nKeyLength
, hash_key
->h
, value
, sizeof(zval
*), NULL
);
709 return ZEND_HASH_APPLY_KEEP
;
712 int apply_array_merge_func(void *pDest HTTP_ZAPI_HASH_TSRMLS_DC
, int num_args
, va_list args
, zend_hash_key
*hash_key
)
717 zval
**value
= (zval
**) pDest
;
719 dst
= va_arg(args
, HashTable
*);
720 flags
= va_arg(args
, int);
722 if ((!(flags
& ARRAY_JOIN_STRONLY
)) || hash_key
->nKeyLength
) {
724 if ((flags
& ARRAY_JOIN_PRETTIFY
) && hash_key
->nKeyLength
) {
725 key
= pretty_key(estrndup(hash_key
->arKey
, hash_key
->nKeyLength
- 1), hash_key
->nKeyLength
- 1, 1, 1);
726 zend_hash_update(dst
, key
, hash_key
->nKeyLength
, (void *) value
, sizeof(zval
*), NULL
);
729 zend_hash_quick_update(dst
, hash_key
->arKey
, hash_key
->nKeyLength
, hash_key
->h
, (void *) value
, sizeof(zval
*), NULL
);
733 return ZEND_HASH_APPLY_KEEP
;
742 * vim600: noet sw=4 ts=4 fdm=marker
743 * vim<600: noet sw=4 ts=4