X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=blobdiff_plain;f=http.c;h=847e7d1da1768af15e97060686105b2338d988f6;hp=41e463e0ec03f9928ae8df6af6841ae982f92362;hb=318bb3459a12dc26362978413cde00cde6616d89;hpb=0f65d4c86b76d8560f902f8d87b52fd38385d3cf diff --git a/http.c b/http.c index 41e463e..847e7d1 100644 --- a/http.c +++ b/http.c @@ -14,26 +14,62 @@ */ /* $Id$ */ +#define ZEND_ENGINE_2 +#define _WINSOCKAPI_ +#define ZEND_INCLUDE_FULL_WINDOWS_HEADERS #ifdef HAVE_CONFIG_H -#include "config.h" +# include "config.h" #endif #include "php.h" -#include "main/snprintf.h" +#include "php_ini.h" +#include "snprintf.h" #include "ext/standard/info.h" -#include "ext/standard/datetime.h" #include "ext/session/php_session.h" #include "ext/standard/php_string.h" #include "ext/standard/php_smart_str.h" +#include "SAPI.h" + #include "php_http.h" #include "php_http_api.h" -#if defined(HAVE_CURL) && HAVE_CURL -#include +#ifdef ZEND_ENGINE_2 +# include "ext/standard/php_http.h" #endif +#ifdef HTTP_HAVE_CURL + +# ifdef PHP_WIN32 +# include +# include +# endif + +# include + +/* {{{ ARG_INFO */ +# ifdef ZEND_BEGIN_ARG_INFO +ZEND_BEGIN_ARG_INFO(http_request_info_ref_3, 0) + ZEND_ARG_PASS_INFO(0) + ZEND_ARG_PASS_INFO(0) + ZEND_ARG_PASS_INFO(1) +ZEND_END_ARG_INFO(); + +ZEND_BEGIN_ARG_INFO(http_request_info_ref_4, 0) + ZEND_ARG_PASS_INFO(0) + ZEND_ARG_PASS_INFO(0) + ZEND_ARG_PASS_INFO(0) + ZEND_ARG_PASS_INFO(1) +ZEND_END_ARG_INFO(); +# else +static unsigned char http_request_info_ref_3[] = {3, BYREF_NONE, BYREF_NONE, BYREF_FORCE}; +static unsigned char http_request_info_ref_4[] = {4, BYREF_NONE, BYREF_NONE, BYREF_NONE, BYREF_FORCE}; +# endif +/* }}} ARG_INFO */ + +#endif /* HTTP_HAVE_CURL */ + ZEND_DECLARE_MODULE_GLOBALS(http) #ifdef COMPILE_DL_HTTP @@ -53,7 +89,6 @@ function_entry http_functions[] = { PHP_FE(http_match_etag, NULL) PHP_FE(http_cache_last_modified, NULL) PHP_FE(http_cache_etag, NULL) - PHP_FE(http_accept_ranges, NULL) PHP_FE(http_content_type, NULL) PHP_FE(http_content_disposition, NULL) PHP_FE(http_send_data, NULL) @@ -61,18 +96,425 @@ function_entry http_functions[] = { PHP_FE(http_send_stream, NULL) PHP_FE(http_chunked_decode, NULL) PHP_FE(http_split_response, NULL) -#if defined(HAVE_CURL) && HAVE_CURL - PHP_FE(http_get, NULL) - PHP_FE(http_head, NULL) - PHP_FE(http_post_data, NULL) - PHP_FE(http_post_array, NULL) + PHP_FE(http_parse_headers, NULL) + PHP_FE(http_get_request_headers, NULL) +#ifdef HTTP_HAVE_CURL + PHP_FE(http_get, http_request_info_ref_3) + PHP_FE(http_head, http_request_info_ref_3) + PHP_FE(http_post_data, http_request_info_ref_4) + PHP_FE(http_post_array, http_request_info_ref_4) #endif PHP_FE(http_auth_basic, NULL) PHP_FE(http_auth_basic_cb, NULL) +#ifndef ZEND_ENGINE_2 + PHP_FE(http_build_query, NULL) +#endif + PHP_FE(ob_httpetaghandler, NULL) + {NULL, NULL, NULL} +}; +/* }}} */ + +#define RETURN_SUCCESS(v) RETURN_BOOL(SUCCESS == (v)) +#define HASH_ORNULL(z) ((z) ? Z_ARRVAL_P(z) : NULL) + +#ifdef ZEND_ENGINE_2 + +# define HTTP_REGISTER_CLASS_EX(classname, name, parent, flags) \ + { \ + zend_class_entry ce; \ + INIT_CLASS_ENTRY(ce, #classname, name## _class_methods); \ + ce.create_object = name## _new_object; \ + name## _ce = zend_register_internal_class_ex(&ce, parent, NULL TSRMLS_CC); \ + name## _ce->ce_flags |= flags; \ + memcpy(& name## _object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); \ + name## _object_handlers.clone_obj = NULL; \ + name## _declare_default_properties(name## _ce); \ + } + +# define HTTP_REGISTER_CLASS(classname, name, parent, flags) \ + { \ + zend_class_entry ce; \ + INIT_CLASS_ENTRY(ce, #classname, name## _class_methods); \ + ce.create_object = NULL; \ + name## _ce = zend_register_internal_class_ex(&ce, parent, NULL TSRMLS_CC); \ + name## _ce->ce_flags |= flags; \ + } + +# define getObject(t, o) t * o = ((t *) zend_object_store_get_object(getThis() TSRMLS_CC)) +# define OBJ_PROP(o) o->zo.properties +# define DCL_PROP(a, t, n, v) zend_declare_property_ ##t(ce, (#n), sizeof(#n), (v), (ZEND_ACC_ ##a) TSRMLS_CC) +# define UPD_PROP(o, t, n, v) zend_update_property_ ##t(o->zo.ce, getThis(), (#n), sizeof(#n), (v) TSRMLS_CC) +# define GET_PROP(o, n) zend_read_property(o->zo.ce, getThis(), (#n), sizeof(#n), 0 TSRMLS_CC) + +/* {{{ HTTPi */ + +zend_class_entry *httpi_ce; + +#define HTTPi_ME(me, al, ai) ZEND_FENTRY(me, ZEND_FN(al), ai, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC) + +zend_function_entry httpi_class_methods[] = { + HTTPi_ME(date, http_date, NULL) + HTTPi_ME(absoluteURI, http_absolute_uri, NULL) + HTTPi_ME(negotiateLanguage, http_negotiate_language, NULL) + HTTPi_ME(negotiateCharset, http_negotiate_charset, NULL) + HTTPi_ME(redirect, http_redirect, NULL) + HTTPi_ME(sendStatus, http_send_status, NULL) + HTTPi_ME(sendLastModified, http_send_last_modified, NULL) + HTTPi_ME(matchModified, http_match_modified, NULL) + HTTPi_ME(matchEtag, http_match_etag, NULL) + HTTPi_ME(cacheLastModified, http_cache_last_modified, NULL) + HTTPi_ME(cacheEtag, http_cache_etag, NULL) + HTTPi_ME(chunkedDecode, http_chunked_decode, NULL) + HTTPi_ME(splitResponse, http_split_response, NULL) + HTTPi_ME(parseHeaders, http_parse_headers, NULL) + HTTPi_ME(getRequestHeaders, http_get_request_headers, NULL) +#ifdef HTTP_HAVE_CURL + HTTPi_ME(get, http_get, http_request_info_ref_3) + HTTPi_ME(head, http_head, http_request_info_ref_3) + HTTPi_ME(postData, http_post_data, http_request_info_ref_4) + HTTPi_ME(postArray, http_post_array, http_request_info_ref_4) +#endif + HTTPi_ME(authBasic, http_auth_basic, NULL) + HTTPi_ME(authBasicCallback, http_auth_basic_cb, NULL) {NULL, NULL, NULL} }; +/* }}} HTTPi */ + +/* {{{ HTTPi_Response */ + +zend_class_entry *httpi_response_ce; +static zend_object_handlers httpi_response_object_handlers; + +typedef struct { + zend_object zo; +} httpi_response_object; + +#define httpi_response_declare_default_properties(ce) _httpi_response_declare_default_properties(ce TSRMLS_CC) +static inline void _httpi_response_declare_default_properties(zend_class_entry *ce TSRMLS_DC) +{ + DCL_PROP(PROTECTED, string, contentType, "application/x-octetstream"); + DCL_PROP(PROTECTED, string, eTag, ""); + DCL_PROP(PROTECTED, string, dispoFile, ""); + DCL_PROP(PROTECTED, string, cacheControl, "public"); + DCL_PROP(PROTECTED, long, lastModified, 0); + DCL_PROP(PROTECTED, long, dispoInline, 0); + DCL_PROP(PROTECTED, long, cache, 0); + DCL_PROP(PROTECTED, long, gzip, 0); + + DCL_PROP(PRIVATE, long, raw_cache_header, 0); +} + +#define httpi_response_destroy_object _httpi_response_destroy_object +void _httpi_response_destroy_object(void *object, zend_object_handle handle TSRMLS_DC) +{ + httpi_response_object *o = object; + if (OBJ_PROP(o)) { + zend_hash_destroy(OBJ_PROP(o)); + FREE_HASHTABLE(OBJ_PROP(o)); + } +} + +#define httpi_response_new_object _httpi_response_new_object +zend_object_value _httpi_response_new_object(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value ov; + httpi_response_object *o; + + o = ecalloc(sizeof(httpi_response_object), 1); + o->zo.ce = ce; + + ALLOC_HASHTABLE(o->zo.properties); + zend_hash_init(o->zo.properties, 0, NULL, ZVAL_PTR_DTOR, 0); + + ov.handle = zend_objects_store_put(o, httpi_response_destroy_object, NULL, NULL TSRMLS_CC); + ov.handlers = &httpi_response_object_handlers; + + return ov; +} + +zend_function_entry httpi_response_class_methods[] = { + PHP_ME(HTTPi_Response, __construct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) +/* PHP_ME(HTTPi_Response, __destruct, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) +*/ + PHP_ME(HTTPi_Response, setETag, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getETag, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(HTTPi_Response, setContentDisposition, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getContentDisposition, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(HTTPi_Response, setContentType, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getContentType, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(HTTPi_Response, setCache, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getCache, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(HTTPi_Response, setCacheControl, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getCacheControl, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(HTTPi_Response, setGzip, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getGzip, NULL, ZEND_ACC_PUBLIC) +/* + PHP_ME(HTTPi_Response, setData, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getData, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(HTTPi_Response, setFile, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getFile, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(HTTPi_Response, setStream, NULL, ZEND_ACC_PUBLIC) + PHP_ME(HTTPi_Response, getStream, NULL, ZEND_ACC_PUBLIC) + + PHP_ME(HTTPi_Response, send, NULL, ZEND_ACC_PUBLIC)*/ + + {NULL, NULL, NULL} +}; + +/* {{{ proto void HTTPi_Response::__construct(bool cache, bool gzip) + * + */ +PHP_METHOD(HTTPi_Response, __construct) +{ + zend_bool do_cache = 0, do_gzip = 0; + getObject(httpi_response_object, obj); + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|bb", &do_cache, &do_gzip)) { + // throw exception + return; + } + + UPD_PROP(obj, long, cache, do_cache); + UPD_PROP(obj, long, gzip, do_gzip); +} +/* }}} */ + +/* {{{ proto bool HTTPi_Response::setCache(bool cache) + * + */ +PHP_METHOD(HTTPi_Response, setCache) +{ + zend_bool do_cache = 0; + getObject(httpi_response_object, obj); + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &do_cache)) { + RETURN_FALSE; + } + + UPD_PROP(obj, long, cache, do_cache); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool HTTPi_Response::getCache() + * + */ +PHP_METHOD(HTTPi_Response, getCache) +{ + zval *do_cache = NULL; + getObject(httpi_response_object, obj); + + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + + do_cache = GET_PROP(obj, cache); + RETURN_BOOL(Z_LVAL_P(do_cache)); +} +/* }}}*/ + +/* {{{ proto bool HTTPi_Response::setGzip(bool gzip) + * + */ +PHP_METHOD(HTTPi_Response, setGzip) +{ + zend_bool do_gzip = 0; + getObject(httpi_response_object, obj); + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "b", &do_gzip)) { + RETURN_FALSE; + } + + UPD_PROP(obj, long, gzip, do_gzip); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto bool HTTPi_Response::getGzip() + * + */ +PHP_METHOD(HTTPi_Response, getGzip) +{ + zval *do_gzip = NULL; + getObject(httpi_response_object, obj); + + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + + do_gzip = GET_PROP(obj, gzip); + RETURN_BOOL(Z_LVAL_P(do_gzip)); +} /* }}} */ +/* {{{ proto bool HTTPi_Response::setCacheControl(string control[, bool raw = false]) + * + */ +PHP_METHOD(HTTPi_Response, setCacheControl) +{ + char *ccontrol; + int cc_len; + zend_bool raw = 0; + getObject(httpi_response_object, obj); + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &ccontrol, &cc_len, &raw)) { + RETURN_FALSE; + } + + if ((!raw) && (strcmp(ccontrol, "public") && strcmp(ccontrol, "private") && strcmp(ccontrol, "no-cache"))) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cache-Control '%s' doesn't match public, private or no-cache", ccontrol); + RETURN_FALSE; + } + + UPD_PROP(obj, long, raw_cache_header, raw); + UPD_PROP(obj, string, cacheControl, ccontrol); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string HTTPi_Response::getCacheControl() + * + */ +PHP_METHOD(HTTPi_Response, getCacheControl) +{ + zval *ccontrol; + getObject(httpi_response_object, obj); + + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + + ccontrol = GET_PROP(obj, cacheControl); + RETURN_STRINGL(Z_STRVAL_P(ccontrol), Z_STRLEN_P(ccontrol), 1); +} +/* }}} */ + +/* {{{ proto bool HTTPi::setContentType(string content_type) + * + */ +PHP_METHOD(HTTPi_Response, setContentType) +{ + char *ctype; + int ctype_len; + getObject(httpi_response_object, obj); + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &ctype, &ctype_len)) { + RETURN_FALSE; + } + + if (!strchr(ctype, '/')) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Content type '%s' doesn't seem to contain a primary and secondary part", ctype); + RETURN_FALSE; + } + + UPD_PROP(obj, string, contentType, ctype); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string HTTPi_Response::getContentType() + * + */ +PHP_METHOD(HTTPi_Response, getContentType) +{ + zval *ctype; + getObject(httpi_response_object, obj); + + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + + ctype = GET_PROP(obj, contentType); + RETURN_STRINGL(Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1); +} +/* }}} */ + +/* {{{ proto bool HTTPi_Response::setContentDisposition(string filename[, bool inline = false]) + * + */ +PHP_METHOD(HTTPi_Response, setContentDisposition) +{ + char *file; + int file_len; + zend_bool is_inline = 0; + getObject(httpi_response_object, obj); + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b", &file, &file_len, &is_inline)) { + RETURN_FALSE; + } + + UPD_PROP(obj, string, dispoFile, file); + UPD_PROP(obj, long, dispoInline, is_inline); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto array HTTPi_Response::getContentDisposition() + * + */ +PHP_METHOD(HTTPi_Response, getContentDisposition) +{ + zval *file; + zval *is_inline; + getObject(httpi_response_object, obj); + + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + + file = GET_PROP(obj, dispoFile); + is_inline = GET_PROP(obj, dispoInline); + + array_init(return_value); + add_assoc_stringl(return_value, "filename", Z_STRVAL_P(file), Z_STRLEN_P(file), 1); + add_assoc_bool(return_value, "inline", Z_LVAL_P(is_inline)); +} +/* }}} */ + +/* {{{ proto bool HTTPi_Response::setETag(string etag) + * + */ +PHP_METHOD(HTTPi_Response, setETag) +{ + char *etag; + int etag_len; + getObject(httpi_response_object, obj); + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &etag, &etag_len)) { + RETURN_FALSE; + } + + UPD_PROP(obj, string, eTag, etag); + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string HTTPi_Response::getETag() + * + */ +PHP_METHOD(HTTPi_Response, getETag) +{ + zval *etag; + getObject(httpi_response_object, obj); + + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + + etag = GET_PROP(obj, eTag); + RETURN_STRINGL(Z_STRVAL_P(etag), Z_STRLEN_P(etag), 1); +} +/* }}} */ + +#endif /* ZEND_ENGINE_2 */ + /* {{{ http_module_entry */ zend_module_entry http_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 @@ -81,8 +523,8 @@ zend_module_entry http_module_entry = { "http", http_functions, PHP_MINIT(http), - NULL, - NULL, + PHP_MSHUTDOWN(http), + PHP_RINIT(http), PHP_RSHUTDOWN(http), PHP_MINFO(http), #if ZEND_MODULE_API_NO >= 20010901 @@ -92,8 +534,6 @@ zend_module_entry http_module_entry = { }; /* }}} */ -#define RETURN_SUCCESS(v) RETURN_BOOL(SUCCESS == (v)) - /* {{{ proto string http_date([int timestamp]) * * This function returns a valid HTTP date regarding RFC 822/1123 @@ -242,10 +682,6 @@ PHP_FUNCTION(http_negotiate_charset) /* {{{ proto bool http_send_status(int status) * * Send HTTP status code. - * This function sends the desired HTTP status code along with its corresponding - * status message, e.g. calling http_send_status(304) will issue a - * "Status: 304 Not Modified" with CGI-SAPIs and "HTTP/1.1 304 Not Modified" - * when PHP is run as web server module. * */ PHP_FUNCTION(http_send_status) @@ -268,7 +704,7 @@ PHP_FUNCTION(http_send_status) * * This converts the given timestamp to a valid HTTP date and * sends it as "Last-Modified" HTTP header. If timestamp is - * omitted, current time is taken. + * omitted, current time is sent. * */ PHP_FUNCTION(http_send_last_modified) @@ -289,7 +725,7 @@ PHP_FUNCTION(http_send_last_modified) /* {{{ proto bool http_match_modified([int timestamp]) * - * Matches the given timestamp against the clients "If-Modified-Since" and + * Matches the given timestamp against the clients "If-Modified-Since" resp. * "If-Unmodified-Since" HTTP headers. * */ @@ -313,7 +749,7 @@ PHP_FUNCTION(http_match_modified) /* {{{ proto bool http_match_etag(string etag) * * This matches the given ETag against the clients - * "If-Match" and "If-None-Match" HTTP headers. + * "If-Match" resp. "If-None-Match" HTTP headers. * */ PHP_FUNCTION(http_match_etag) @@ -334,7 +770,7 @@ PHP_FUNCTION(http_match_etag) * If timestamp_or_exires is greater than 0, it is handled as timestamp * and will be sent as date of last modification. If it is 0 or omitted, * the current time will be sent as Last-Modified date. If it's negative, - * it is handled as expiration tim in seconds, which means that if the + * it is handled as expiration time in seconds, which means that if the * requested last modification date is not between the calculated timespan, * the Last-Modified header is updated and the actual body will be sent. * @@ -352,12 +788,12 @@ PHP_FUNCTION(http_cache_last_modified) /* 0 or omitted */ if (!last_modified) { - /* does the client have? */ + /* does the client have? (att: caching "forever") */ if (zlm = http_get_server_var("HTTP_IF_MODIFIED_SINCE")) { last_modified = send_modified = http_parse_date(Z_STRVAL_P(zlm)); - /* use current time */ + /* send current time */ } else { - last_modified = send_modified = t; + send_modified = t; } /* negative value is supposed to be expiration time */ } else if (last_modified < 0) { @@ -402,29 +838,60 @@ PHP_FUNCTION(http_cache_etag) RETURN_FALSE; } - /* send remaining data to nirvana */ - http_send_header("Connection: close"); http_send_header("Cache-Control: private, must-revalidate, max-age=0"); - /* if no etag is given and we didn't already - * start ob_etaghandler -- start it - */ - if (!HTTP_G(etag_started) && !etag_len) { - php_ob_set_internal_handler(_http_ob_etaghandler, (uint) 4096, "etag output handler", 0 TSRMLS_CC); - HTTP_G(etag_started) = 1; - RETURN_BOOL(php_start_ob_buffer_named("etag output handler", (uint) 4096, 0 TSRMLS_CC)); + if (etag_len) { + http_send_etag(etag, etag_len); + if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) { + if (SUCCESS == http_send_status(304)) { + zend_bailout(); + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified"); + RETURN_FALSE; + } + } } - if (http_etag_match("HTTP_IF_NONE_MATCH", etag)) { - if (SUCCESS == http_send_status(304)) { - zend_bailout(); - } else { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not send 304 Not Modified"); - RETURN_FALSE; + /* if no etag is given and we didn't already start ob_etaghandler -- start it */ + if (!HTTP_G(etag_started)) { + RETURN_BOOL(HTTP_G(etag_started) = (SUCCESS == http_start_ob_handler(_http_ob_etaghandler, "ob_etaghandler", 4096, 1))); + } + RETURN_TRUE; +} +/* }}} */ + +/* {{{ proto string ob_httpetaghandler(string data, int mode) + * + * For use with ob_start(). + * Note that this has to be started as first output buffer. + * WARNING: Don't use with http_send_*(). + */ +PHP_FUNCTION(ob_httpetaghandler) +{ + char *data; + int data_len; + long mode; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sl", &data, &data_len, &mode)) { + RETURN_FALSE; + } + + if (mode & PHP_OUTPUT_HANDLER_START) { + if (HTTP_G(etag_started)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "ob_httpetaghandler can only be used once"); + RETURN_STRINGL(data, data_len, 1); } + http_send_header("Cache-Control: private, must-revalidate, max-age=0"); + HTTP_G(etag_started) = 1; } - RETURN_SUCCESS(http_send_etag(etag, etag_len)); + if (OG(ob_nesting_level) > 1) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "ob_httpetaghandler must be started prior to other output buffers"); + RETURN_STRINGL(data, data_len, 1); + } + + Z_TYPE_P(return_value) = IS_STRING; + http_ob_etaghandler(data, data_len, &Z_STRVAL_P(return_value), &Z_STRLEN_P(return_value), mode); } /* }}} */ @@ -439,6 +906,8 @@ PHP_FUNCTION(http_cache_etag) * ("301 Moved Permanently") or a temporary ("302 Found") redirection * status code. * + * To be RFC compliant, "Redirecting to URI." will be displayed, + * if the client doesn't redirect immediatly. */ PHP_FUNCTION(http_redirect) { @@ -446,9 +915,9 @@ PHP_FUNCTION(http_redirect) zend_bool session = 0, permanent = 0; zval *params = NULL; smart_str qstr = {0}; - char *url, *URI, LOC[HTTP_URI_MAXLEN + 9]; + char *url, *URI, LOC[HTTP_URI_MAXLEN + 9], RED[HTTP_URI_MAXLEN * 2 + 34]; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sa!bb", &url, &url_len, ¶ms, &session, &permanent) != SUCCESS) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sa!/bb", &url, &url_len, ¶ms, &session, &permanent) != SUCCESS) { RETURN_FALSE; } @@ -478,13 +947,19 @@ PHP_FUNCTION(http_redirect) URI = http_absolute_uri(url, NULL); if (qstr.c) { snprintf(LOC, HTTP_URI_MAXLEN + strlen("Location: "), "Location: %s?%s", URI, qstr.c); + sprintf(RED, "Redirecting to %s?%s.\n", URI, qstr.c, URI, qstr.c); efree(qstr.c); } else { snprintf(LOC, HTTP_URI_MAXLEN + strlen("Location: "), "Location: %s", URI); + sprintf(RED, "Redirecting to %s.\n", URI, URI); } efree(URI); - RETVAL_BOOL((SUCCESS == http_send_header(LOC)) && (SUCCESS == http_send_status((permanent ? 301 : 302)))); + if ((SUCCESS == http_send_header(LOC)) && (SUCCESS == http_send_status((permanent ? 301 : 302)))) { + php_body_write(RED, strlen(RED) TSRMLS_CC); + RETURN_TRUE; + } + RETURN_FALSE; } /* }}} */ @@ -502,7 +977,7 @@ PHP_FUNCTION(http_send_data) } convert_to_string_ex(&zdata); - + http_send_header("Accept-Ranges: bytes"); RETURN_SUCCESS(http_send_data(zdata)); } /* }}} */ @@ -521,7 +996,7 @@ PHP_FUNCTION(http_send_file) } convert_to_string_ex(&zfile); - + http_send_header("Accept-Ranges: bytes"); RETURN_SUCCESS(http_send_file(zfile)); } /* }}} */ @@ -541,36 +1016,11 @@ PHP_FUNCTION(http_send_stream) } php_stream_from_zval(file, &zstream); - + http_send_header("Accept-Ranges: bytes"); RETURN_SUCCESS(http_send_stream(file)); } /* }}} */ -/* {{{ proto bool http_accept_ranges(void) - * - * Issues a "Accept-Ranges: bytes" header. - * - * Example: - *
- * 
- * 
- * - */ -PHP_FUNCTION(http_accept_ranges) -{ - if (ZEND_NUM_ARGS()) { - WRONG_PARAM_COUNT; - } - RETURN_SUCCESS(http_send_header("Accept-Ranges: bytes")); -} -/* }}} */ - /* {{{ proto bool http_content_type([string content_type = 'application/x-octetstream']) * * Sets the content type. @@ -661,6 +1111,7 @@ PHP_FUNCTION(http_chunked_decode) * content body. The returned array may look simliar to the following example: * *
+ *  array(
  *         'Status' => '200 Ok',
@@ -669,6 +1120,7 @@ PHP_FUNCTION(http_chunked_decode)
  *     ),
  *     1 => "Hello World!"
  * );
+ * ?>
  * 
*/ PHP_FUNCTION(http_split_response) @@ -685,7 +1137,10 @@ PHP_FUNCTION(http_split_response) MAKE_STD_ZVAL(zheaders); array_init(zheaders); - http_split_response(zresponse, zheaders, zbody); + if (SUCCESS != http_split_response(zresponse, zheaders, zbody)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse HTTP response"); + RETURN_FALSE; + } array_init(return_value); add_index_zval(return_value, 0, zheaders); @@ -693,19 +1148,58 @@ PHP_FUNCTION(http_split_response) } /* }}} */ +/* {{{ proto array http_parse_headers(string header) + * + */ +PHP_FUNCTION(http_parse_headers) +{ + char *header, *rnrn; + int header_len; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &header, &header_len)) { + RETURN_FALSE; + } + + array_init(return_value); + + if (rnrn = strstr(header, HTTP_CRLF HTTP_CRLF)) { + header_len = rnrn - header + 2; + } + if (SUCCESS != http_parse_headers(header, header_len, return_value)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse HTTP header"); + zval_dtor(return_value); + RETURN_FALSE; + } +} +/* }}}*/ + +/* {{{ proto array http_get_request_headers(void) + * + */ +PHP_FUNCTION(http_get_request_headers) +{ + if (ZEND_NUM_ARGS()) { + WRONG_PARAM_COUNT; + } + + array_init(return_value); + http_get_request_headers(return_value); +} +/* }}} */ + /* {{{ HAVE_CURL */ -#if defined(HAVE_CURL) && HAVE_CURL +#ifdef HTTP_HAVE_CURL -/* {{{ proto string http_get(string url[, array options]) +/* {{{ proto string http_get(string url[, array options[, array &info]]) * * Performs an HTTP GET request on the supplied url. * * The second parameter is expected to be an associative * array where the following keys will be recognized: *
- *  - redirect:      	int, whether and how many redirects to follow
+ *  - redirect:         int, whether and how many redirects to follow
  *  - unrestrictedauth: bool, whether to continue sending credentials on
- * 	                    redirects to a different host
+ *                      redirects to a different host
  *  - proxyhost:        string, proxy host in "host[:port]" format
  *  - proxyport:        int, use another proxy port as specified in proxyhost
  *  - proxyauth:        string, proxy credentials in "user:pass" format
@@ -713,26 +1207,68 @@ PHP_FUNCTION(http_split_response)
  *  - httpauth:         string, http credentials in "user:pass" format
  *  - httpauthtype:     int, HTTP_AUTH_BASIC, DIGEST and/or NTLM
  *  - compress:         bool, whether to allow gzip/deflate content encoding
+ *                      (defaults to true)
  *  - port:             int, use another port as specified in the url
  *  - referer:          string, the referer to sends
  *  - useragent:        string, the user agent to send
- *  - headers:          array, list of custom headers in "header: value" format
- *  - cookies:          array, list of cookies in "name=value" format
+ *                      (defaults to PECL::HTTP/version (PHP/version)))
+ *  - headers:          array, list of custom headers as associative array
+ *                      like array("header" => "value")
+ *  - cookies:          array, list of cookies as associative array
+ *                      like array("cookie" => "value")
  *  - cookiestore:      string, path to a file where cookies are/will be stored
  * 
+ * + * The optional third parameter will be filled with some additional information + * in form af an associative array, if supplied, like the following example: + *
+ *  'http://localhost',
+ *     'response_code' => 403,
+ *     'total_time' => 0.017,
+ *     'namelookup_time' => 0.013,
+ *     'connect_time' => 0.014,
+ *     'pretransfer_time' => 0.014,
+ *     'size_upload' => 0,
+ *     'size_download' => 202,
+ *     'speed_download' => 11882,
+ *     'speed_upload' => 0,
+ *     'header_size' => 145,
+ *     'request_size' => 62,
+ *     'ssl_verifyresult' => 0,
+ *     'filetime' => -1,
+ *     'content_length_download' => 202,
+ *     'content_length_upload' => 0,
+ *     'starttransfer_time' => 0.017,
+ *     'content_type' => 'text/html; charset=iso-8859-1',
+ *     'redirect_time' => 0,
+ *     'redirect_count' => 0,
+ *     'private' => '',
+ *     'http_connectcode' => 0,
+ *     'httpauth_avail' => 0,
+ *     'proxyauth_avail' => 0,
+ * )
+ * ?>
+ * 
*/ PHP_FUNCTION(http_get) { char *URL, *data = NULL; size_t data_len = 0; int URL_len; - zval *options = NULL; + zval *options = NULL, *info = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &URL, &URL_len, &options) != SUCCESS) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a/!z", &URL, &URL_len, &options, &info) != SUCCESS) { RETURN_FALSE; } - if (SUCCESS == http_get(URL, options ? Z_ARRVAL_P(options) : NULL, &data, &data_len)) { + if (info) { + zval_dtor(info); + array_init(info); + } + + if (SUCCESS == http_get(URL, HASH_ORNULL(options), HASH_ORNULL(info), &data, &data_len)) { RETURN_STRINGL(data, data_len, 0); } else { RETURN_FALSE; @@ -740,7 +1276,7 @@ PHP_FUNCTION(http_get) } /* }}} */ -/* {{{ proto string http_head(string url[, array options]) +/* {{{ proto string http_head(string url[, array options[, array &info]]) * * Performs an HTTP HEAD request on the suppied url. * Returns the HTTP response as string. @@ -751,13 +1287,18 @@ PHP_FUNCTION(http_head) char *URL, *data = NULL; size_t data_len = 0; int URL_len; - zval *options = NULL; + zval *options = NULL, *info = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &URL, &URL_len, &options) != SUCCESS) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|a/!z", &URL, &URL_len, &options, &info) != SUCCESS) { RETURN_FALSE; } - if (SUCCESS == http_head(URL, options ? Z_ARRVAL_P(options) : NULL, &data, &data_len)) { + if (info) { + zval_dtor(info); + array_init(info); + } + + if (SUCCESS == http_head(URL, HASH_ORNULL(options), HASH_ORNULL(info), &data, &data_len)) { RETURN_STRINGL(data, data_len, 0); } else { RETURN_FALSE; @@ -765,7 +1306,7 @@ PHP_FUNCTION(http_head) } /* }}} */ -/* {{{ proto string http_post_data(string url, string data[, array options]) +/* {{{ proto string http_post_data(string url, string data[, array options[, &info]]) * * Performs an HTTP POST request, posting data. * Returns the HTTP response as string. @@ -776,13 +1317,18 @@ PHP_FUNCTION(http_post_data) char *URL, *postdata, *data = NULL; size_t data_len = 0; int postdata_len, URL_len; - zval *options = NULL; + zval *options = NULL, *info = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|a", &URL, &URL_len, &postdata, &postdata_len, &options) != SUCCESS) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|a/!z", &URL, &URL_len, &postdata, &postdata_len, &options, &info) != SUCCESS) { RETURN_FALSE; } - if (SUCCESS == http_post_data(URL, postdata, (size_t) postdata_len, options ? Z_ARRVAL_P(options) : NULL, &data, &data_len)) { + if (info) { + zval_dtor(info); + array_init(info); + } + + if (SUCCESS == http_post_data(URL, postdata, (size_t) postdata_len, HASH_ORNULL(options), HASH_ORNULL(info), &data, &data_len)) { RETURN_STRINGL(data, data_len, 0); } else { RETURN_FALSE; @@ -790,7 +1336,7 @@ PHP_FUNCTION(http_post_data) } /* }}} */ -/* {{{ proto string http_post_data(string url, array data[, array options]) +/* {{{ proto string http_post_array(string url, array data[, array options[, array &info]]) * * Performs an HTTP POST request, posting www-form-urlencoded array data. * Returns the HTTP response as string. @@ -801,13 +1347,18 @@ PHP_FUNCTION(http_post_array) char *URL, *data = NULL; size_t data_len = 0; int URL_len; - zval *options = NULL, *postdata; + zval *options = NULL, *info = NULL, *postdata; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a", &URL, &URL_len, &postdata, &options) != SUCCESS) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa|a/!z", &URL, &URL_len, &postdata, &options, &info) != SUCCESS) { RETURN_FALSE; } - if (SUCCESS == http_post_array(URL, Z_ARRVAL_P(postdata), options ? Z_ARRVAL_P(options) : NULL, &data, &data_len)) { + if (info) { + zval_dtor(info); + array_init(info); + } + + if (SUCCESS == http_post_array(URL, Z_ARRVAL_P(postdata), HASH_ORNULL(options), HASH_ORNULL(info), &data, &data_len)) { RETURN_STRINGL(data, data_len, 0); } else { RETURN_FALSE; @@ -935,7 +1486,9 @@ static void php_http_init_globals(zend_http_globals *http_globals) { http_globals->etag_started = 0; http_globals->ctype = NULL; -#if defined(HAVE_CURL) && HAVE_CURL + http_globals->etag = NULL; + http_globals->lmod = 0; +#ifdef HTTP_HAVE_CURL http_globals->curlbuf.body.data = NULL; http_globals->curlbuf.body.used = 0; http_globals->curlbuf.body.free = 0; @@ -943,18 +1496,74 @@ static void php_http_init_globals(zend_http_globals *http_globals) http_globals->curlbuf.hdrs.used = 0; http_globals->curlbuf.hdrs.free = 0; #endif + http_globals->allowed_methods = NULL; } /* }}} */ +/* {{{ static inline STATUS http_check_allowed_methods(char *, int) */ +#define http_check_allowed_methods(m, l) _http_check_allowed_methods((m), (l) TSRMLS_CC) +static inline void _http_check_allowed_methods(char *methods, int length TSRMLS_DC) +{ + if (length && SG(request_info).request_method && (!strstr(methods, SG(request_info).request_method))) { + char *allow_header = emalloc(length + sizeof("Allow: ")); + sprintf(allow_header, "Allow: %s", methods); + http_send_header(allow_header); + efree(allow_header); + http_send_status(405); + zend_bailout(); + } +} +/* }}} */ + +/* {{{ PHP_INI */ +PHP_INI_MH(update_allowed_methods) +{ + http_check_allowed_methods(new_value, new_value_length); + return OnUpdateString(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC); +} + +PHP_INI_BEGIN() + STD_PHP_INI_ENTRY("http.allowed_methods", "OPTIONS,GET,HEAD,POST,PUT,DELETE,TRACE,CONNECT", PHP_INI_ALL, update_allowed_methods, allowed_methods, zend_http_globals, http_globals) +PHP_INI_END() +/* }}} */ + /* {{{ PHP_MINIT_FUNCTION */ PHP_MINIT_FUNCTION(http) { ZEND_INIT_MODULE_GLOBALS(http, php_http_init_globals, NULL); -#if defined(HAVE_CURL) && HAVE_CURL + REGISTER_INI_ENTRIES(); + /* + REGISTER_LONG_CONSTANT("HTTP_GET", HTTP_GET, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_HEAD", HTTP_HEAD, CONST_CS | CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("HTTP_POST", HTTP_POST, CONST_CS | CONST_PERSISTENT); + */ +#ifdef HTTP_HAVE_CURL REGISTER_LONG_CONSTANT("HTTP_AUTH_BASIC", CURLAUTH_BASIC, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("HTTP_AUTH_DIGEST", CURLAUTH_DIGEST, CONST_CS | CONST_PERSISTENT); REGISTER_LONG_CONSTANT("HTTP_AUTH_NTLM", CURLAUTH_NTLM, CONST_CS | CONST_PERSISTENT); #endif + +#ifdef ZEND_ENGINE_2 + HTTP_REGISTER_CLASS(HTTPi, httpi, NULL, ZEND_ACC_FINAL_CLASS); + HTTP_REGISTER_CLASS_EX(HTTPi_Response, httpi_response, NULL, 0); +#endif + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_MSHUTDOWN_FUNCTION */ +PHP_MSHUTDOWN_FUNCTION(http) +{ + UNREGISTER_INI_ENTRIES(); + return SUCCESS; +} +/* }}} */ + +/* {{{ PHP_RINIT_FUNCTION */ +PHP_RINIT_FUNCTION(http) +{ + char *allowed_methods = INI_STR("http.allowed_methods"); + http_check_allowed_methods(allowed_methods, strlen(allowed_methods)); return SUCCESS; } /* }}} */ @@ -962,15 +1571,26 @@ PHP_MINIT_FUNCTION(http) /* {{{ PHP_RSHUTDOWN_FUNCTION */ PHP_RSHUTDOWN_FUNCTION(http) { + HTTP_G(etag_started) = 0; + HTTP_G(lmod) = 0; + + if (HTTP_G(etag)) { + efree(HTTP_G(etag)); + HTTP_G(etag) = NULL; + } + if (HTTP_G(ctype)) { efree(HTTP_G(ctype)); + HTTP_G(ctype) = NULL; } -#if defined(HAVE_CURL) && HAVE_CURL +#ifdef HTTP_HAVE_CURL if (HTTP_G(curlbuf).body.data) { efree(HTTP_G(curlbuf).body.data); + HTTP_G(curlbuf).body.data = NULL; } if (HTTP_G(curlbuf).hdrs.data) { efree(HTTP_G(curlbuf).hdrs.data); + HTTP_G(curlbuf).hdrs.data = NULL; } #endif return SUCCESS; @@ -984,13 +1604,15 @@ PHP_MINFO_FUNCTION(http) php_info_print_table_header(2, "Extended HTTP support", "enabled"); php_info_print_table_row(2, "Version:", PHP_EXT_HTTP_VERSION); php_info_print_table_row(2, "cURL convenience functions:", -#if defined(HAVE_CURL) && HAVE_CURL +#ifdef HTTP_HAVE_CURL "enabled" #else "disabled" #endif ); php_info_print_table_end(); + + DISPLAY_INI_ENTRIES(); } /* }}} */ @@ -1002,3 +1624,4 @@ PHP_MINFO_FUNCTION(http) * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ +