From: Remi Collet Date: Thu, 19 Feb 2015 09:22:19 +0000 (+0100) Subject: Merge branch 'master' of git.php.net:/pecl/http/pecl_http X-Git-Tag: RELEASE_2_3_0~13 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=commitdiff_plain;h=e867316f1364f589eec67cc684703c874984430b;hp=baff99d75b56868714f7f2ed1972b3baceb72014 Merge branch 'master' of git.php.net:/pecl/http/pecl_http * 'master' of git.php.net:/pecl/http/pecl_http: (84 commits) back to dev 2.3.0RC1 restore php-5.3 compatibility administrativa fix build with old libcurl remove dead code remove strlist api hoppala; removed actually living code remove dead code don't generate stat based etags for temp/mem streams test header parser errors and streaming test 4-byte sequences better error message (gcov) tests fixup better errors from the headers parser let the header parser fail more accurately add http\Header\Parser::stream() typo fix the stream message parser ignore disconnect exceptions ... --- diff --git a/.gitignore b/.gitignore index 64b23b6..93781ff 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ mkinstalldirs modules/ pecl_http-*.tgz *.lo +*.o run-tests.php tests/*.diff tests/*.exp @@ -37,3 +38,4 @@ tests/*.out tests/*.php tests/*.sh lcov_data +*~ diff --git a/TODO b/TODO index 3981099..33b759f 100644 --- a/TODO +++ b/TODO @@ -1,4 +1,3 @@ -* let http_info.request.url be a php_url * let the message body be a simple query string unless files are added * php_http_message_serialize reverses the chain twice; remove that * CURLOPT_PROXY_HEADER and CURLOPT_HEADEROPT \ No newline at end of file diff --git a/config9.m4 b/config9.m4 index 3b1f701..af3d55f 100644 --- a/config9.m4 +++ b/config9.m4 @@ -202,6 +202,14 @@ dnl ---- if test `echo $CURL_VERSION | $SED -e 's/[[^0-9]]/ /g' | $AWK '{print $1*10000 + $2*100 + $3}'` -lt 71802; then AC_MSG_ERROR([libcurl version greater or equal to 7.18.2 required]) fi + + AC_MSG_CHECKING([for HTTP2 support in libcurl]) + if $CURL_CONFIG --features | $EGREP -q HTTP2; then + AC_MSG_RESULT([yes]) + AC_DEFINE([PHP_HTTP_HAVE_HTTP2], [1], [ ]) + else + AC_MSG_RESULT([no]) + fi dnl dnl compile tests @@ -210,11 +218,11 @@ dnl ---- save_INCLUDES="$INCLUDES" INCLUDES= save_LIBS="$LIBS" - LIBS= + LIBS=-lcurl save_CFLAGS="$CFLAGS" CFLAGS="$CFLAGS `$CURL_CONFIG --cflags`" save_LDFLAGS="$LDFLAGS" - LDFLAGS="$LDFLAGS `$CURL_CONFIG --libs` $ld_runpath_switch$CURL_DIR/$PHP_LIBDIR" + LDFLAGS="$ld_runpath_switch$CURL_DIR/$PHP_LIBDIR" AC_MSG_CHECKING([for SSL support in libcurl]) CURL_SSL=`$CURL_CONFIG --feature | $EGREP SSL` @@ -296,6 +304,37 @@ dnl ---- ], [ AC_MSG_RESULT([no]) ]) + + AC_MSG_CHECKING([whether CURLOPT_TLSAUTH_TYPE expects CURL_TLSAUTH_SRP or literal "SRP"]) + AC_TRY_RUN([ + #include + int main(int argc, char *argv[]) { + CURL *ch = curl_easy_init(); + return curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, CURL_TLSAUTH_SRP); + } + ], [ + AC_MSG_RESULT([CURL_TLSAUTH_SRP]) + AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_SRP], [CURL_TLSAUTH_SRP], [ ]) + AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_DEF], [CURL_TLSAUTH_NONE], [ ]) + ], [ + AC_TRY_RUN([ + #include + int main(int argc, char *argv[]) { + CURL *ch = curl_easy_init(); + return curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, "SRP"); + } + ], [ + AC_MSG_RESULT(["SRP"]) + AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_SRP], ["SRP"], [ ]) + AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_DEF], [""], [ ]) + ], [ + AC_MSG_RESULT([neither]) + ], [ + AC_MSG_RESULT([neither]) + ]) + ], [ + AC_MSG_RESULT([neither]) + ]) INCLUDES="$save_INCLUDES" LIBS="$save_LIBS" @@ -508,7 +547,6 @@ dnl ---- php_http_options.c \ php_http_params.c \ php_http_querystring.c \ - php_http_strlist.c \ php_http_url.c \ php_http_version.c \ " @@ -560,8 +598,9 @@ dnl ---- php_http_options.h \ php_http_params.h \ php_http_querystring.h \ - php_http_strlist.h \ + php_http_response_codes.h \ php_http_url.h \ + php_http_utf8.h \ php_http_version.h \ " PHP_INSTALL_HEADERS(ext/http, $PHP_HTTP_HEADERS) diff --git a/gen_curlinfo.php b/gen_curlinfo.php index 0e8dfa1..076a10d 100755 --- a/gen_curlinfo.php +++ b/gen_curlinfo.php @@ -41,7 +41,8 @@ $ifdefs = array( ); $exclude = array( 'PRIVATE', 'LASTSOCKET', 'FTP_ENTRY_PATH', 'CERTINFO', 'TLS_SESSION', - 'RTSP_SESSION_ID', 'RTSP_CLIENT_CSEQ', 'RTSP_SERVER_CSEQ', 'RTSP_CSEQ_RECV' + 'RTSP_SESSION_ID', 'RTSP_CLIENT_CSEQ', 'RTSP_SERVER_CSEQ', 'RTSP_CSEQ_RECV', + 'COOKIELIST' ); $translate = array( diff --git a/package.xml b/package.xml index c02cad1..187f86e 100644 --- a/package.xml +++ b/package.xml @@ -37,22 +37,58 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ mike@php.net yes - 2014-08-19 + 2015-02-19 - 2.2.0dev - 2.2.0 + 2.3.0dev + 2.3.0 beta - stable + beta BSD, revised = 7.30.0) + . max_pipeline_length (int, max number of requests in a pipeline, libcurl >= 7.30.0) + . max_total_connections (int, max number of simultaneous open connections of this client, libcurl >= 7.30.0) + . pipelining (bool, whether to enable HTTP/1.1 pipelining) + . chunk_length_penalty_size (int, chunk length threshold for pipelining, libcurl >= 7.30.0) + . content_length_penalty_size (int, size threshold for pipelining, libcurl >= 7.30.0) + . pipelining_server_bl (array, list of server software names to blacklist for pipelining, libcurl >= 7.30.0) + . pipelining_site_bl (array, list of server host names to blacklist for pipelining, libcurl >= 7.30.0) + . use_eventloop (bool, whether to use libevent, libcurl+libevent) ++ Added http\Client::getAvailableOptions() and http\Client::getAvailableConfiguration() methods ++ Added support for HTTP2 if libcurl was built with nghttp2 support. ++ Added http\Client\Curl\HTTP_VERSION_2_0 constant (libcurl >= 7.33.0) ++ Added http\Client\Curl\TLS_AUTH_SRP constant (libcurl >= 7.21.4) ++ Added pinned_publickey SSL request option (libcurl >= 7.39.0) ++ Added tlsauthtype, tlsauthuser and tlsauthpass SSL request option (libcurl >= 7.21.4) ++ Added verifystatus (a.k.a OCSP) SSL request option (libcurl >= 7.41.0) ++ Added proxyheader request option (libcurl >= 7.37.0) ++ Added unix_socket_path request option (libcurl >= 7.40.0) +* Fixed compress request option +* Fixed parsing authorities of CONNECT messages +* Fixed parsing Content-Range messages +* Fixed http\Env\Response to default to chunked encoding over streams +* Fixed superfluous output of Content-Length:0 headers +* Fixed persistent easy handles to be only created for persistent multi handles +* Fixed the header parser to accept not-yet-complete header lines +* Fixed http\Message::toStream() crash in ZTS mode +* Fixed the message stream parser to handle intermediary data bigger than 4k +* Fixed the message stream parser to handle single header lines without EOL +* Fixed http\Message\Body to not generate stat based etags for temporary streams +- Deprecated http\Client::enablePipelining(), use http\Client::configure(["pipelining" => true]) instead +- Deprecated http\Client::enableEvents(), use http\Client::configure(["use_eventloop" => true]) instead +- Removed the cookies entry from the transfer info, wich was very slow and generated a Netscape formatted list of cookies +- Changed the header parser to reject illegal characters ]]> @@ -120,8 +156,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ - - + @@ -130,17 +165,31 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -157,6 +206,16 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ + + + + + + + + + + @@ -188,6 +247,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ + @@ -211,9 +271,12 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ + + + @@ -228,8 +291,11 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ - - + + + + + @@ -245,6 +311,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ + @@ -273,6 +340,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ + @@ -292,6 +360,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/ + diff --git a/php_http.c b/php_http.c index f7a0b86..17d9925 100644 --- a/php_http.c +++ b/php_http.c @@ -141,6 +141,7 @@ PHP_MINIT_FUNCTION(http) || SUCCESS != PHP_MINIT_CALL(http_encoding) || SUCCESS != PHP_MINIT_CALL(http_filter) || SUCCESS != PHP_MINIT_CALL(http_header) + || SUCCESS != PHP_MINIT_CALL(http_header_parser) || SUCCESS != PHP_MINIT_CALL(http_message) || SUCCESS != PHP_MINIT_CALL(http_message_parser) || SUCCESS != PHP_MINIT_CALL(http_message_body) diff --git a/php_http.h b/php_http.h index 98332cb..5a0575d 100644 --- a/php_http.h +++ b/php_http.h @@ -13,7 +13,7 @@ #ifndef PHP_EXT_HTTP_H #define PHP_EXT_HTTP_H -#define PHP_PECL_HTTP_VERSION "2.2.0dev" +#define PHP_PECL_HTTP_VERSION "2.3.0dev" extern zend_module_entry http_module_entry; #define phpext_http_ptr &http_module_entry diff --git a/php_http_api.h b/php_http_api.h index 5bddb0c..712c8cb 100644 --- a/php_http_api.h +++ b/php_http_api.h @@ -79,7 +79,6 @@ typedef int STATUS; #include "php_http.h" #include "php_http_buffer.h" -#include "php_http_strlist.h" #include "php_http_misc.h" #include "php_http_options.h" diff --git a/php_http_buffer.c b/php_http_buffer.c index 8ccee08..e24a4e1 100644 --- a/php_http_buffer.c +++ b/php_http_buffer.c @@ -45,7 +45,7 @@ PHP_HTTP_BUFFER_API size_t php_http_buffer_resize_ex(php_http_buffer_t *buf, siz { char *ptr = NULL; #if 0 - fprintf(stderr, "RESIZE: len=%lu, size=%lu, used=%lu, free=%lu\n", len, buf->size, buf->used, buf->free); + fprintf(stderr, "RESIZE: len=%lu, size=%lu, used=%lu, free=%lu, total=%lu\n", len, buf->size, buf->used, buf->free, buf->free+buf->used); #endif if (buf->free < len) { size_t size = override_size ? override_size : buf->size; @@ -74,10 +74,7 @@ PHP_HTTP_BUFFER_API size_t php_http_buffer_resize_ex(php_http_buffer_t *buf, siz PHP_HTTP_BUFFER_API char *php_http_buffer_account(php_http_buffer_t *buf, size_t to_account) { - /* it's probably already too late but check anyway */ - if (to_account > buf->free) { - return NULL; - } + assert(to_account <= buf->free); buf->free -= to_account; buf->used += to_account; @@ -229,7 +226,7 @@ PHP_HTTP_BUFFER_API size_t php_http_buffer_chunked_output(php_http_buffer_t **s, while ((got = php_http_buffer_chunk_buffer(s, data, data_len, &chunk, chunk_len))) { if (PHP_HTTP_BUFFER_PASS0 == passout(opaque, chunk, got TSRMLS_CC)) { - STR_SET(chunk, NULL); + PTR_SET(chunk, NULL); return PHP_HTTP_BUFFER_PASS0; } ++passed; @@ -240,9 +237,9 @@ PHP_HTTP_BUFFER_API size_t php_http_buffer_chunked_output(php_http_buffer_t **s, } data = NULL; data_len = 0; - STR_SET(chunk, NULL); + PTR_SET(chunk, NULL); } - STR_FREE(chunk); + PTR_FREE(chunk); return passed; } diff --git a/php_http_buffer.h b/php_http_buffer.h index cf9b458..faf8992 100644 --- a/php_http_buffer.h +++ b/php_http_buffer.h @@ -21,19 +21,19 @@ #define PHP_HTTP_BUFFER_NOMEM PHP_HTTP_BUFFER_ERROR #define PHP_HTTP_BUFFER_PASS0 PHP_HTTP_BUFFER_ERROR -#ifndef STR_FREE -# define STR_FREE(STR) \ +#ifndef PTR_FREE +# define PTR_FREE(PTR) \ { \ - if (STR) { \ - efree(STR); \ + if (PTR) { \ + efree(PTR); \ } \ } #endif -#ifndef STR_SET -# define STR_SET(STR, SET) \ +#ifndef PTR_SET +# define PTR_SET(PTR, SET) \ { \ - STR_FREE(STR); \ - STR = SET; \ + PTR_FREE(PTR); \ + PTR = SET; \ } #endif #ifndef TSRMLS_D diff --git a/php_http_client.c b/php_http_client.c index f96164b..4f0de3c 100644 --- a/php_http_client.c +++ b/php_http_client.c @@ -329,6 +329,8 @@ void php_http_client_object_free(void *object TSRMLS_DC) php_http_client_object_t *o = (php_http_client_object_t *) object; php_http_client_free(&o->client); + php_http_object_method_dtor(&o->notify); + php_http_object_method_free(&o->update); zend_object_std_dtor((zend_object *) o TSRMLS_CC); efree(o); } @@ -375,7 +377,7 @@ static void handle_history(zval *zclient, php_http_message_t *request, php_http_ zval_ptr_dtor(&new_hist); } -static STATUS handle_response(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_message_t **request, php_http_message_t **response) +static STATUS handle_response(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_message_t **response) { zend_bool dequeue = 0; zval zclient; @@ -395,7 +397,7 @@ static STATUS handle_response(void *arg, php_http_client_t *client, php_http_cli php_http_message_set_type(msg, PHP_HTTP_RESPONSE); if (z_is_true(zend_read_property(php_http_client_class_entry, &zclient, ZEND_STRL("recordHistory"), 0 TSRMLS_CC))) { - handle_history(&zclient, *request, *response TSRMLS_CC); + handle_history(&zclient, e->request, *response TSRMLS_CC); } /* hard detach, redirects etc. are in the history */ @@ -457,14 +459,18 @@ static STATUS handle_response(void *arg, php_http_client_t *client, php_http_cli static void handle_progress(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_client_progress_state_t *progress) { - zval *zrequest, *zprogress, *retval = NULL, *zclient; + zval *zrequest, *zprogress, *zclient, **args[2]; + php_http_client_object_t *client_obj = arg; zend_error_handling zeh; TSRMLS_FETCH_FROM_CTX(client->ts); MAKE_STD_ZVAL(zclient); - ZVAL_OBJVAL(zclient, ((php_http_client_object_t *) arg)->zv, 1); + ZVAL_OBJVAL(zclient, client_obj->zv, 1); + MAKE_STD_ZVAL(zrequest); ZVAL_OBJVAL(zrequest, ((php_http_message_object_t *) e->opaque)->zv, 1); + args[0] = &zrequest; + MAKE_STD_ZVAL(zprogress); object_init(zprogress); add_property_bool(zprogress, "started", progress->started); @@ -474,15 +480,15 @@ static void handle_progress(void *arg, php_http_client_t *client, php_http_clien add_property_double(zprogress, "dlnow", progress->dl.now); add_property_double(zprogress, "ultotal", progress->ul.total); add_property_double(zprogress, "ulnow", progress->ul.now); + args[1] = &zprogress; + zend_replace_error_handling(EH_NORMAL, NULL, &zeh TSRMLS_CC); - zend_call_method_with_2_params(&zclient, NULL, NULL, "notify", &retval, zrequest, zprogress); + php_http_object_method_call(&client_obj->notify, zclient, NULL, 2, args TSRMLS_CC); zend_restore_error_handling(&zeh TSRMLS_CC); + zval_ptr_dtor(&zclient); zval_ptr_dtor(&zrequest); zval_ptr_dtor(&zprogress); - if (retval) { - zval_ptr_dtor(&retval); - } } static void response_dtor(void *data) @@ -537,6 +543,8 @@ static PHP_METHOD(HttpClient, __construct) php_http_expect(obj->client = php_http_client_init(NULL, driver.client_ops, rf, NULL TSRMLS_CC), runtime, return); + php_http_object_method_init(&obj->notify, getThis(), ZEND_STRL("notify") TSRMLS_CC); + obj->client->callback.response.func = handle_response; obj->client->callback.response.arg = obj; obj->client->callback.progress.func = handle_progress; @@ -835,6 +843,22 @@ static PHP_METHOD(HttpClient, wait) } } +ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_configure, 0, 0, 1) + ZEND_ARG_ARRAY_INFO(0, settings, 1) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpClient, configure) +{ + HashTable *settings = NULL; + php_http_client_object_t *obj; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|H!", &settings), invalid_arg, return); + obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + php_http_expect(SUCCESS == php_http_client_setopt(obj->client, PHP_HTTP_CLIENT_OPT_CONFIGURATION, settings), unexpected_val, return); + + RETVAL_ZVAL(getThis(), 1, 0); +} + ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_enablePipelining, 0, 0, 0) ZEND_ARG_INFO(0, enable) ZEND_END_ARG_INFO(); @@ -869,13 +893,20 @@ static PHP_METHOD(HttpClient, enableEvents) RETVAL_ZVAL(getThis(), 1, 0); } +struct notify_arg { + php_http_object_method_t *cb; + zval **args[3]; + int argc; +}; + static int notify(zend_object_iterator *iter, void *puser TSRMLS_DC) { - zval **observer = NULL, ***args = puser; + zval **observer = NULL; + struct notify_arg *arg = puser; iter->funcs->get_current_data(iter, &observer TSRMLS_CC); if (observer) { - return php_http_method_call(*observer, ZEND_STRL("update"), args[2]?3:args[1]?2:args[0]?1:0, args, NULL TSRMLS_CC); + return php_http_object_method_call(arg->cb, *observer, NULL, arg->argc, arg->args TSRMLS_CC); } return FAILURE; } @@ -885,10 +916,13 @@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_notify, 0, 0, 0) ZEND_END_ARG_INFO(); static PHP_METHOD(HttpClient, notify) { - zval *request = NULL, *zprogress = NULL, *observers, **args[3]; + zval *request = NULL, *zprogress = NULL, *observers; + php_http_client_object_t *client_obj; + struct notify_arg arg = {NULL}; php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|O!o!", &request, php_http_client_request_class_entry, &zprogress), invalid_arg, return); + client_obj = zend_object_store_get_object(getThis() TSRMLS_CC); observers = zend_read_property(php_http_client_class_entry, getThis(), ZEND_STRL("observers"), 0 TSRMLS_CC); if (Z_TYPE_P(observers) != IS_OBJECT) { @@ -896,23 +930,34 @@ static PHP_METHOD(HttpClient, notify) return; } - Z_ADDREF_P(getThis()); - args[0] = &getThis(); - if (request) { - Z_ADDREF_P(request); - } - args[1] = &request; - if (zprogress) { - Z_ADDREF_P(zprogress); - } - args[2] = &zprogress; - spl_iterator_apply(observers, notify, args TSRMLS_CC); - zval_ptr_dtor(&getThis()); - if (request) { - zval_ptr_dtor(&request); - } - if (zprogress) { - zval_ptr_dtor(&zprogress); + if (client_obj->update) { + arg.cb = client_obj->update; + + Z_ADDREF_P(getThis()); + arg.args[0] = &getThis(); + arg.argc = 1; + + if (request) { + Z_ADDREF_P(request); + arg.args[1] = &request; + arg.argc += 1; + } + + if (zprogress) { + Z_ADDREF_P(zprogress); + arg.args[2] = &zprogress; + arg.argc += 1; + } + + spl_iterator_apply(observers, notify, &arg TSRMLS_CC); + + zval_ptr_dtor(&getThis()); + if (request) { + zval_ptr_dtor(&request); + } + if (zprogress) { + zval_ptr_dtor(&zprogress); + } } RETVAL_ZVAL(getThis(), 1, 0); @@ -924,9 +969,11 @@ ZEND_END_ARG_INFO(); static PHP_METHOD(HttpClient, attach) { zval *observers, *observer, *retval = NULL; + php_http_client_object_t *client_obj; php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &observer, spl_ce_SplObserver), invalid_arg, return); + client_obj = zend_object_store_get_object(getThis() TSRMLS_CC); observers = zend_read_property(php_http_client_class_entry, getThis(), ZEND_STRL("observers"), 0 TSRMLS_CC); if (Z_TYPE_P(observers) != IS_OBJECT) { @@ -934,6 +981,10 @@ static PHP_METHOD(HttpClient, attach) return; } + if (!client_obj->update) { + client_obj->update = php_http_object_method_init(NULL, observer, ZEND_STRL("update") TSRMLS_CC); + } + zend_call_method_with_1_params(&observers, NULL, NULL, "attach", &retval, observer); if (retval) { zval_ptr_dtor(&retval); @@ -1138,6 +1189,30 @@ static PHP_METHOD(HttpClient, getAvailableDrivers) { } } +ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_getAvailableOptions, 0, 0, 0) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpClient, getAvailableOptions) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_client_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + array_init(return_value); + php_http_client_getopt(obj->client, PHP_HTTP_CLIENT_OPT_AVAILABLE_OPTIONS, NULL, &Z_ARRVAL_P(return_value)); + } +} + +ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_getAvailableConfiguration, 0, 0, 0) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpClient, getAvailableConfiguration) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_client_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + array_init(return_value); + php_http_client_getopt(obj->client, PHP_HTTP_CLIENT_OPT_AVAILABLE_CONFIGURATION, NULL, &Z_ARRVAL_P(return_value)); + } +} + static zend_function_entry php_http_client_methods[] = { PHP_ME(HttpClient, __construct, ai_HttpClient_construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) PHP_ME(HttpClient, reset, ai_HttpClient_reset, ZEND_ACC_PUBLIC) @@ -1150,8 +1225,9 @@ static zend_function_entry php_http_client_methods[] = { PHP_ME(HttpClient, wait, ai_HttpClient_wait, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, getResponse, ai_HttpClient_getResponse, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, getHistory, ai_HttpClient_getHistory, ZEND_ACC_PUBLIC) - PHP_ME(HttpClient, enablePipelining, ai_HttpClient_enablePipelining, ZEND_ACC_PUBLIC) - PHP_ME(HttpClient, enableEvents, ai_HttpClient_enableEvents, ZEND_ACC_PUBLIC) + PHP_ME(HttpClient, configure, ai_HttpClient_configure, ZEND_ACC_PUBLIC) + PHP_ME(HttpClient, enablePipelining, ai_HttpClient_enablePipelining, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED) + PHP_ME(HttpClient, enableEvents, ai_HttpClient_enableEvents, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED) PHP_ME(HttpClient, notify, ai_HttpClient_notify, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, attach, ai_HttpClient_attach, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, detach, ai_HttpClient_detach, ZEND_ACC_PUBLIC) @@ -1167,6 +1243,8 @@ static zend_function_entry php_http_client_methods[] = { PHP_ME(HttpClient, addCookies, ai_HttpClient_addCookies, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, getCookies, ai_HttpClient_getCookies, ZEND_ACC_PUBLIC) PHP_ME(HttpClient, getAvailableDrivers, ai_HttpClient_getAvailableDrivers, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_ME(HttpClient, getAvailableOptions, ai_HttpClient_getAvailableOptions, ZEND_ACC_PUBLIC) + PHP_ME(HttpClient, getAvailableConfiguration, ai_HttpClient_getAvailableConfiguration, ZEND_ACC_PUBLIC) EMPTY_FUNCTION_ENTRY }; diff --git a/php_http_client.h b/php_http_client.h index 6c45516..4008e87 100644 --- a/php_http_client.h +++ b/php_http_client.h @@ -16,11 +16,14 @@ typedef enum php_http_client_setopt_opt { PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING, PHP_HTTP_CLIENT_OPT_USE_EVENTS, + PHP_HTTP_CLIENT_OPT_CONFIGURATION, } php_http_client_setopt_opt_t; typedef enum php_http_client_getopt_opt { - PHP_HTTP_CLIENT_OPT_PROGRESS_INFO, /* php_http_client_progress_state_t** */ - PHP_HTTP_CLIENT_OPT_TRANSFER_INFO, /* HashTable* */ + PHP_HTTP_CLIENT_OPT_PROGRESS_INFO, /* php_http_client_enqueue_t*, php_http_client_progress_state_t** */ + PHP_HTTP_CLIENT_OPT_TRANSFER_INFO, /* php_http_client_enqueue_t*, HashTable* */ + PHP_HTTP_CLIENT_OPT_AVAILABLE_OPTIONS, /* NULL, HashTable* */ + PHP_HTTP_CLIENT_OPT_AVAILABLE_CONFIGURATION,/* NULL, HashTable */ } php_http_client_getopt_opt_t; typedef struct php_http_client_enqueue { @@ -84,7 +87,7 @@ typedef struct php_http_client_progress_state { unsigned finished:1; } php_http_client_progress_state_t; -typedef STATUS (*php_http_client_response_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_message_t **request, php_http_message_t **response); +typedef STATUS (*php_http_client_response_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_message_t **response); typedef void (*php_http_client_progress_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_client_progress_state_t *state); typedef struct php_http_client { @@ -118,6 +121,8 @@ typedef struct php_http_client_object { zend_object_value zv; php_http_client_t *client; long iterator; + php_http_object_method_t *update; + php_http_object_method_t notify; } php_http_client_object_t; PHP_HTTP_API php_http_client_t *php_http_client_init(php_http_client_t *h, php_http_client_ops_t *ops, php_resource_factory_t *rf, void *init_arg TSRMLS_DC); diff --git a/php_http_client_curl.c b/php_http_client_curl.c index 48bd0de..44b2c21 100644 --- a/php_http_client_curl.c +++ b/php_http_client_curl.c @@ -60,24 +60,17 @@ typedef struct php_http_client_curl_handler { php_resource_factory_t *rf; php_http_client_t *client; php_http_client_progress_state_t progress; - php_http_client_enqueue_t queue; struct { - php_http_message_parser_t *parser; - php_http_message_t *message; - php_http_buffer_t *buffer; - } request; - - struct { - php_http_message_parser_t *parser; - php_http_message_t *message; - php_http_buffer_t *buffer; + php_http_buffer_t headers; + php_http_message_body_t *body; } response; struct { HashTable cache; + struct curl_slist *proxyheaders; struct curl_slist *headers; struct curl_slist *resolve; php_http_buffer_t cookies; @@ -225,29 +218,23 @@ static int php_http_curle_progress_callback(void *ctx, double dltotal, double dl return 0; } -static curlioerr php_http_curle_ioctl_callback(CURL *ch, curliocmd cmd, void *ctx) +static int php_http_curle_seek_callback(void *userdata, curl_off_t offset, int origin) { - php_http_message_body_t *body = ctx; + php_http_message_body_t *body = userdata; + TSRMLS_FETCH_FROM_CTX(body->ts); - if (cmd != CURLIOCMD_RESTARTREAD) { - return CURLIOE_UNKNOWNCMD; + if (!body) { + return 1; } - - if (body) { - TSRMLS_FETCH_FROM_CTX(body->ts); - - if (SUCCESS == php_stream_rewind(php_http_message_body_stream(body))) { - return CURLIOE_OK; - } + if (0 == php_stream_seek(php_http_message_body_stream(body), offset, origin)) { + return 0; } - - return CURLIOE_FAILRESTART; + return 2; } static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, size_t length, void *ctx) { php_http_client_curl_handler_t *h = ctx; - unsigned flags = 0; /* catch progress */ switch (type) { @@ -304,32 +291,6 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, default: break; } - /* process data */ - switch (type) { - case CURLINFO_HEADER_IN: - case CURLINFO_DATA_IN: - php_http_buffer_append(h->response.buffer, data, length); - - if (h->options.redirects) { - flags |= PHP_HTTP_MESSAGE_PARSER_EMPTY_REDIRECTS; - } - - if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->response.parser, h->response.buffer, flags, &h->response.message)) { - return -1; - } - break; - - case CURLINFO_HEADER_OUT: - case CURLINFO_DATA_OUT: - php_http_buffer_append(h->request.buffer, data, length); - - if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->request.parser, h->request.buffer, flags, &h->request.message)) { - return -1; - } - break; - default: - break; - } #if 0 /* debug */ @@ -339,9 +300,18 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, return 0; } -static int php_http_curle_dummy_callback(char *data, size_t n, size_t l, void *s) +static int php_http_curle_header_callback(char *data, size_t n, size_t l, void *arg) +{ + php_http_client_curl_handler_t *h = arg; + + return php_http_buffer_append(&h->response.headers, data, n * l); +} + +static int php_http_curle_body_callback(char *data, size_t n, size_t l, void *arg) { - return n*l; + php_http_client_curl_handler_t *h = arg; + + return php_http_message_body_append(h->response.body, data, n*l); } static STATUS php_http_curle_get_info(CURL *ch, HashTable *info) @@ -440,17 +410,6 @@ static STATUS php_http_curle_get_info(CURL *ch, HashTable *info) add_assoc_zval_ex(&array, "ssl_engines", sizeof("ssl_engines"), subarray); curl_slist_free_all(s); } - if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_COOKIELIST, &s)) { - MAKE_STD_ZVAL(subarray); - array_init(subarray); - for (p = s; p; p = p->next) { - if (p->data) { - add_next_index_string(subarray, p->data, 1); - } - } - add_assoc_zval_ex(&array, "cookies", sizeof("cookies"), subarray); - curl_slist_free_all(s); - } if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_REDIRECT_URL, &c)) { add_assoc_string_ex(&array, "redirect_url", sizeof("redirect_url"), c ? c : "", 1); } @@ -617,6 +576,50 @@ static int compare_queue(php_http_client_enqueue_t *e, void *handle) return handle == ((php_http_client_curl_handler_t *) e->opaque)->handle; } +static php_http_message_t *php_http_curlm_responseparser(php_http_client_curl_handler_t *h TSRMLS_DC) +{ + php_http_message_t *response; + php_http_header_parser_t parser; + zval *zh; + + response = php_http_message_init(NULL, 0, h->response.body TSRMLS_CC); + php_http_header_parser_init(&parser TSRMLS_CC); + php_http_header_parser_parse(&parser, &h->response.headers, PHP_HTTP_HEADER_PARSER_CLEANUP, &response->hdrs, (php_http_info_callback_t) php_http_message_info_callback, (void *) &response); + php_http_header_parser_dtor(&parser); + + /* move body to right message */ + if (response->body != h->response.body) { + php_http_message_t *ptr = response; + + while (ptr->parent) { + ptr = ptr->parent; + } + response->body = ptr->body; + ptr->body = NULL; + } + php_http_message_body_addref(h->response.body); + + /* let's update the response headers */ + if ((zh = php_http_message_header(response, ZEND_STRL("Content-Length"), 1))) { + zend_hash_update(&response->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), &zh, sizeof(zval *), NULL); + } + if ((zh = php_http_message_header(response, ZEND_STRL("Transfer-Encoding"), 0))) { + zend_hash_update(&response->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &zh, sizeof(zval *), NULL); + zend_hash_del(&response->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding")); + } + if ((zh = php_http_message_header(response, ZEND_STRL("Content-Range"), 0))) { + zend_hash_update(&response->hdrs, "X-Original-Content-Range", sizeof("X-Original-Content-Range"), &zh, sizeof(zval *), NULL); + zend_hash_del(&response->hdrs, "Content-Range", sizeof("Content-Range")); + } + if ((zh = php_http_message_header(response, ZEND_STRL("Content-Encoding"), 0))) { + zend_hash_update(&response->hdrs, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), &zh, sizeof(zval *), NULL); + zend_hash_del(&response->hdrs, "Content-Encoding", sizeof("Content-Encoding")); + } + php_http_message_update_headers(response); + + return response; +} + static void php_http_curlm_responsehandler(php_http_client_t *context) { int remaining = 0; @@ -635,8 +638,12 @@ static void php_http_curlm_responsehandler(php_http_client_t *context) if ((enqueue = php_http_client_enqueued(context, msg->easy_handle, compare_queue))) { php_http_client_curl_handler_t *handler = enqueue->opaque; + php_http_message_t *response = php_http_curlm_responseparser(handler TSRMLS_CC); - context->callback.response.func(context->callback.response.arg, context, &handler->queue, &handler->request.message, &handler->response.message); + if (response) { + context->callback.response.func(context->callback.response.arg, context, &handler->queue, &response); + php_http_message_free(&response); + } } } } while (remaining); @@ -799,7 +806,7 @@ static void php_http_curlm_timer_callback(CURLM *multi, long timeout_ms, void *t /* curl options */ -static php_http_options_t php_http_curle_options; +static php_http_options_t php_http_curle_options, php_http_curlm_options; #define PHP_HTTP_CURLE_OPTION_CHECK_STRLEN 0x0001 #define PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR 0x0002 @@ -925,9 +932,13 @@ static STATUS php_http_curle_option_set_lastmodified(php_http_option_t *opt, zva static STATUS php_http_curle_option_set_compress(php_http_option_t *opt, zval *val, void *userdata) { php_http_client_curl_handler_t *curl = userdata; + CURL *ch = curl->handle; - if (Z_BVAL_P(val)) { - curl->options.headers = curl_slist_append(curl->options.headers, "Accept-Encoding: gzip;q=1.0,deflate;q=0.5"); +#if !PHP_HTTP_CURL_VERSION(7,21,6) +# define CURLOPT_ACCEPT_ENCODING CURLOPT_ENCODING +#endif + if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_ACCEPT_ENCODING, Z_BVAL_P(val) ? "" : NULL)) { + return FAILURE; } return SUCCESS; } @@ -1073,6 +1084,43 @@ static STATUS php_http_curle_option_set_portrange(php_http_option_t *opt, zval * return SUCCESS; } +#if PHP_HTTP_CURL_VERSION(7,37,0) +static STATUS php_http_curle_option_set_proxyheader(php_http_option_t *opt, zval *val, void *userdata) +{ + php_http_client_curl_handler_t *curl = userdata; + TSRMLS_FETCH_FROM_CTX(curl->client->ts); + + if (val && Z_TYPE_P(val) != IS_NULL) { + php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0); + zval **header_val, *header_cpy; + HashPosition pos; + php_http_buffer_t header; + + php_http_buffer_init(&header); + FOREACH_KEYVAL(pos, val, header_key, header_val) { + if (header_key.type == HASH_KEY_IS_STRING) { + header_cpy = php_http_ztyp(IS_STRING, *header_val); + php_http_buffer_appendf(&header, "%s: %s", header_key.str, Z_STRVAL_P(header_cpy)); + php_http_buffer_fix(&header); + curl->options.proxyheaders = curl_slist_append(curl->options.proxyheaders, header.data); + php_http_buffer_reset(&header); + + zval_ptr_dtor(&header_cpy); + } + } + php_http_buffer_dtor(&header); + } + if (CURLE_OK != curl_easy_setopt(curl->handle, CURLOPT_PROXYHEADER, curl->options.proxyheaders)) { + return FAILURE; + } + if (CURLE_OK != curl_easy_setopt(curl->handle, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE)) { + curl_easy_setopt(curl->handle, CURLOPT_PROXYHEADER, NULL); + return FAILURE; + } + return SUCCESS; +} +#endif + #if PHP_HTTP_CURL_VERSION(7,21,3) static STATUS php_http_curle_option_set_resolve(php_http_option_t *opt, zval *val, void *userdata) { @@ -1103,6 +1151,30 @@ static STATUS php_http_curle_option_set_resolve(php_http_option_t *opt, zval *va } #endif +#if PHP_HTTP_CURL_VERSION(7,21,4) && defined(PHP_HTTP_CURL_TLSAUTH_SRP) +static STATUS php_http_curle_option_set_ssl_tlsauthtype(php_http_option_t *opt, zval *val, void *userdata) +{ + php_http_client_curl_handler_t *curl = userdata; + CURL *ch = curl->handle; + + if (val && Z_LVAL_P(val)) { + switch (Z_LVAL_P(val)) { + case CURL_TLSAUTH_SRP: + if (CURLE_OK == curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, PHP_HTTP_CURL_TLSAUTH_SRP)) { + return SUCCESS; + } + /* no break */ + default: + return FAILURE; + } + } + if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, PHP_HTTP_CURL_TLSAUTH_DEF)) { + return FAILURE; + } + return SUCCESS; +} +#endif + static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC) { php_http_option_t *opt; @@ -1124,6 +1196,19 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC) php_http_option_register(registry, ZEND_STRL("noproxy"), CURLOPT_NOPROXY, IS_STRING); #endif +#if PHP_HTTP_CURL_VERSION(7,37,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("proxyheader"), CURLOPT_PROXYHEADER, IS_ARRAY))) { + opt->setter = php_http_curle_option_set_proxyheader; + } +#endif + +#if PHP_HTTP_CURL_VERSION(7,40,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("unix_socket_path"), CURLOPT_UNIX_SOCKET_PATH, IS_STRING))) { + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR; + } +#endif + /* dns */ if ((opt = php_http_option_register(registry, ZEND_STRL("dns_cache_timeout"), CURLOPT_DNS_CACHE_TIMEOUT, IS_LONG))) { Z_LVAL(opt->defval) = 60; @@ -1336,6 +1421,9 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC) ZVAL_BOOL(&opt->defval, 1); opt->setter = php_http_curle_option_set_ssl_verifyhost; } +#if PHP_HTTP_CURL_VERSION(7,41,0) + php_http_option_register(registry, ZEND_STRL("verifystatus"), CURLOPT_SSL_VERIFYSTATUS, IS_BOOL); +#endif php_http_option_register(registry, ZEND_STRL("cipher_list"), CURLOPT_SSL_CIPHER_LIST, IS_STRING); if ((opt = php_http_option_register(registry, ZEND_STRL("cainfo"), CURLOPT_CAINFO, IS_STRING))) { opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; @@ -1378,6 +1466,23 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC) if ((opt = php_http_option_register(registry, ZEND_STRL("enable_alpn"), CURLOPT_SSL_ENABLE_ALPN, IS_BOOL))) { ZVAL_BOOL(&opt->defval, 1); } +#endif +#if PHP_HTTP_CURL_VERSION(7,39,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("pinned_publickey"), CURLOPT_PINNEDPUBLICKEY, IS_STRING))) { + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR; + } +#endif +#if PHP_HTTP_CURL_VERSION(7,21,4) && defined(PHP_HTTP_CURL_TLSAUTH_SRP) + if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthtype"), CURLOPT_TLSAUTH_TYPE, IS_LONG))) { + opt->setter = php_http_curle_option_set_ssl_tlsauthtype; + } + if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthuser"), CURLOPT_TLSAUTH_USERNAME, IS_STRING))) { + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; + } + if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthpass"), CURLOPT_TLSAUTH_PASSWORD, IS_STRING))) { + opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN; + } #endif } } @@ -1475,6 +1580,182 @@ static STATUS php_http_curle_set_option(php_http_option_t *opt, zval *val, void return rv; } +#if PHP_HTTP_CURL_VERSION(7,30,0) +static STATUS php_http_curlm_option_set_pipelining_bl(php_http_option_t *opt, zval *value, void *userdata) +{ + php_http_client_t *client = userdata; + php_http_client_curl_t *curl = client->ctx; + CURLM *ch = curl->handle; + HashTable tmp_ht; + char **bl = NULL; + TSRMLS_FETCH_FROM_CTX(client->ts); + + /* array of char *, ending with a NULL */ + if (value && Z_TYPE_P(value) != IS_NULL) { + zval **entry; + HashPosition pos; + HashTable *ht = HASH_OF(value); + int c = zend_hash_num_elements(ht); + char **ptr = ecalloc(c + 1, sizeof(char *)); + + bl = ptr; + + zend_hash_init(&tmp_ht, c, NULL, ZVAL_PTR_DTOR, 0); + array_join(ht, &tmp_ht, 0, ARRAY_JOIN_STRINGIFY); + + FOREACH_HASH_VAL(pos, &tmp_ht, entry) { + *ptr++ = Z_STRVAL_PP(entry); + } + } + + if (CURLM_OK != curl_multi_setopt(ch, opt->option, bl)) { + if (bl) { + efree(bl); + zend_hash_destroy(&tmp_ht); + } + return FAILURE; + } + + if (bl) { + efree(bl); + zend_hash_destroy(&tmp_ht); + } + return SUCCESS; +} +#endif + +#if PHP_HTTP_HAVE_EVENT +static inline STATUS php_http_curlm_use_eventloop(php_http_client_t *h, zend_bool enable) +{ + php_http_client_curl_t *curl = h->ctx; + + if ((curl->useevents = enable)) { + if (!curl->evbase) { + curl->evbase = event_base_new(); + } + if (!curl->timeout) { + curl->timeout = ecalloc(1, sizeof(struct event)); + } + curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h); + curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback); + curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h); + curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback); + } else { + curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL); + curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL); + curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL); + curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL); + } + + return SUCCESS; +} + +static STATUS php_http_curlm_option_set_use_eventloop(php_http_option_t *opt, zval *value, void *userdata) +{ + php_http_client_t *client = userdata; + + return php_http_curlm_use_eventloop(client, value && Z_BVAL_P(value)); +} +#endif + +static void php_http_curlm_options_init(php_http_options_t *registry TSRMLS_DC) +{ + php_http_option_t *opt; + + /* set size of connection cache */ + if ((opt = php_http_option_register(registry, ZEND_STRL("maxconnects"), CURLMOPT_MAXCONNECTS, IS_LONG))) { + /* -1 == default, 0 == unlimited */ + ZVAL_LONG(&opt->defval, -1); + } + /* set max number of connections to a single host */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + php_http_option_register(registry, ZEND_STRL("max_host_connections"), CURLMOPT_MAX_HOST_CONNECTIONS, IS_LONG); +#endif + /* maximum number of requests in a pipeline */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("max_pipeline_length"), CURLMOPT_MAX_PIPELINE_LENGTH, IS_LONG))) { + ZVAL_LONG(&opt->defval, 5); + } +#endif + /* max simultaneously open connections */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + php_http_option_register(registry, ZEND_STRL("max_total_connections"), CURLMOPT_MAX_TOTAL_CONNECTIONS, IS_LONG); +#endif + /* enable/disable HTTP pipelining */ + php_http_option_register(registry, ZEND_STRL("pipelining"), CURLMOPT_PIPELINING, IS_BOOL); + /* chunk length threshold for pipelining */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + php_http_option_register(registry, ZEND_STRL("chunk_length_penalty_size"), CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, IS_LONG); +#endif + /* size threshold for pipelining penalty */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + php_http_option_register(registry, ZEND_STRL("content_length_penalty_size"), CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, IS_LONG); +#endif + /* pipelining server blacklist */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_server_bl"), CURLMOPT_PIPELINING_SERVER_BL, IS_ARRAY))) { + opt->setter = php_http_curlm_option_set_pipelining_bl; + } +#endif + /* pipelining host blacklist */ +#if PHP_HTTP_CURL_VERSION(7,30,0) + if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_site_bl"), CURLMOPT_PIPELINING_SITE_BL, IS_ARRAY))) { + opt->setter = php_http_curlm_option_set_pipelining_bl; + } +#endif + /* events */ +#if PHP_HTTP_HAVE_EVENT + if ((opt = php_http_option_register(registry, ZEND_STRL("use_eventloop"), 0, IS_BOOL))) { + opt->setter = php_http_curlm_option_set_use_eventloop; + } +#endif +} + +static STATUS php_http_curlm_set_option(php_http_option_t *opt, zval *val, void *userdata) +{ + php_http_client_t *client = userdata; + php_http_client_curl_t *curl = client->ctx; + CURLM *ch = curl->handle; + zval *orig = val; + CURLMcode rc = CURLM_UNKNOWN_OPTION; + STATUS rv = SUCCESS; + TSRMLS_FETCH_FROM_CTX(client->ts); + + if (!val) { + val = &opt->defval; + } else if (opt->type && Z_TYPE_P(val) != opt->type && !(Z_TYPE_P(val) == IS_NULL && opt->type == IS_ARRAY)) { + val = php_http_ztyp(opt->type, val); + } + + if (opt->setter) { + rv = opt->setter(opt, val, client); + } else { + switch (opt->type) { + case IS_BOOL: + if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, (long) Z_BVAL_P(val)))) { + rv = FAILURE; + } + break; + case IS_LONG: + if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, Z_LVAL_P(val)))) { + rv = FAILURE; + } + break; + default: + rv = FAILURE; + break; + } + } + + if (val && val != orig && val != &opt->defval) { + zval_ptr_dtor(&val); + } + + if (rv != SUCCESS) { + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Could not set option %s (%s)", opt->name.s, curl_easy_strerror(rc)); + } + return rv; +} /* client ops */ @@ -1522,6 +1803,10 @@ static STATUS php_http_client_curl_handler_reset(php_http_client_curl_handler_t curl_slist_free_all(curl->options.headers); curl->options.headers = NULL; } + if (curl->options.proxyheaders) { + curl_slist_free_all(curl->options.proxyheaders); + curl->options.proxyheaders = NULL; + } php_http_buffer_reset(&curl->options.cookies); php_http_buffer_reset(&curl->options.ranges); @@ -1544,12 +1829,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt handler->rf = rf; handler->client = h; handler->handle = handle; - handler->request.buffer = php_http_buffer_init(NULL); - handler->request.parser = php_http_message_parser_init(NULL TSRMLS_CC); - handler->request.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC); - handler->response.buffer = php_http_buffer_init(NULL); - handler->response.parser = php_http_message_parser_init(NULL TSRMLS_CC); - handler->response.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC); + handler->response.body = php_http_message_body_init(NULL, NULL TSRMLS_CC); + php_http_buffer_init(&handler->response.headers); php_http_buffer_init(&handler->options.cookies); php_http_buffer_init(&handler->options.ranges); zend_hash_init(&handler->options.cache, 0, NULL, ZVAL_PTR_DTOR, 0); @@ -1562,11 +1843,11 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt curl_easy_setopt(handle, CURLOPT_AUTOREFERER, 1L); curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L); curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L); - curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL); - curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_dummy_callback); + curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, php_http_curle_header_callback); + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_body_callback); curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, php_http_curle_raw_callback); curl_easy_setopt(handle, CURLOPT_READFUNCTION, php_http_curle_read_callback); - curl_easy_setopt(handle, CURLOPT_IOCTLFUNCTION, php_http_curle_ioctl_callback); + curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, php_http_curle_seek_callback); #if PHP_HTTP_CURL_VERSION(7,32,0) curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, php_http_curle_xferinfo_callback); curl_easy_setopt(handle, CURLOPT_XFERINFODATA, handler); @@ -1575,6 +1856,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, handler); #endif curl_easy_setopt(handle, CURLOPT_DEBUGDATA, handler); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, handler); + curl_easy_setopt(handle, CURLOPT_HEADERDATA, handler); php_http_client_curl_handler_reset(handler); @@ -1598,7 +1881,7 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_ if (storage->url) { pefree(storage->url, 1); } - storage->url = pestrdup(PHP_HTTP_INFO(msg).request.url, 1); + php_http_url_to_string(PHP_HTTP_INFO(msg).request.url, &storage->url, NULL, 1); curl_easy_setopt(curl->handle, CURLOPT_URL, storage->url); /* request method */ @@ -1630,6 +1913,9 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_ } } + /* apply options */ + php_http_options_apply(&php_http_curle_options, enqueue->options, curl); + /* request headers */ php_http_message_update_headers(msg); if (zend_hash_num_elements(&msg->hdrs)) { @@ -1674,19 +1960,17 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_ * does not allow a request body. */ php_stream_rewind(php_http_message_body_stream(msg->body)); - curl_easy_setopt(curl->handle, CURLOPT_IOCTLDATA, msg->body); + curl_easy_setopt(curl->handle, CURLOPT_SEEKDATA, msg->body); curl_easy_setopt(curl->handle, CURLOPT_READDATA, msg->body); curl_easy_setopt(curl->handle, CURLOPT_INFILESIZE, body_size); curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, body_size); } else { - curl_easy_setopt(curl->handle, CURLOPT_IOCTLDATA, NULL); + curl_easy_setopt(curl->handle, CURLOPT_SEEKDATA, NULL); curl_easy_setopt(curl->handle, CURLOPT_READDATA, NULL); curl_easy_setopt(curl->handle, CURLOPT_INFILESIZE, 0L); curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, 0L); } - php_http_options_apply(&php_http_curle_options, enqueue->options, curl); - return SUCCESS; } @@ -1711,12 +1995,8 @@ static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *ha php_resource_factory_handle_dtor(handler->rf, handler->handle TSRMLS_CC); php_resource_factory_free(&handler->rf); - php_http_message_parser_free(&handler->request.parser); - php_http_message_free(&handler->request.message); - php_http_buffer_free(&handler->request.buffer); - php_http_message_parser_free(&handler->response.parser); - php_http_message_free(&handler->response.message); - php_http_buffer_free(&handler->response.buffer); + php_http_message_body_free(&handler->response.body); + php_http_buffer_dtor(&handler->response.headers); php_http_buffer_dtor(&handler->options.ranges); php_http_buffer_dtor(&handler->options.cookies); zend_hash_destroy(&handler->options.cache); @@ -1784,35 +2064,41 @@ static void queue_dtor(php_http_client_enqueue_t *e) php_http_client_curl_handler_dtor(handler); } -static php_resource_factory_t *create_rf(const char *url TSRMLS_DC) +static php_resource_factory_t *create_rf(php_http_client_t *h, php_http_client_enqueue_t *enqueue TSRMLS_DC) { - php_url *purl; + php_persistent_handle_factory_t *pf = NULL; php_resource_factory_t *rf = NULL; + php_http_url_t *url = enqueue->request->http.info.request.url; - if (!url || !*url) { + if (!url || (!url->host && !url->path)) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot request empty URL"); return NULL; } - purl = php_url_parse(url); - - if (!purl) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse URL '%s'", url); - return NULL; - } else { + /* only if the client itself is setup for persistence */ + if (h->rf->dtor == (void (*)(void*)) php_persistent_handle_abandon) { char *id_str = NULL; - size_t id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(purl->host), purl->port ? purl->port : 80); - php_persistent_handle_factory_t *pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC); + size_t id_len; + int port = url->port ? url->port : 80; + zval **zport; + + if (SUCCESS == zend_hash_find(enqueue->options, ZEND_STRS("port"), (void *) &zport)) { + zval *zcpy = php_http_ztyp(IS_LONG, *zport); - if (pf) { - rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon); + if (Z_LVAL_P(zcpy)) { + port = Z_LVAL_P(zcpy); + } + zval_ptr_dtor(&zcpy); } - php_url_free(purl); + id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), port); + pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC); efree(id_str); } - if (!rf) { + if (pf) { + rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon); + } else { rf = php_resource_factory_init(NULL, &php_http_curle_resource_factory_ops, NULL, NULL); } @@ -1828,7 +2114,7 @@ static STATUS php_http_client_curl_enqueue(php_http_client_t *h, php_http_client php_resource_factory_t *rf; TSRMLS_FETCH_FROM_CTX(h->ts); - rf = create_rf(enqueue->request->http.info.request.url TSRMLS_CC); + rf = create_rf(h, enqueue TSRMLS_CC); if (!rf) { return FAILURE; } @@ -2017,6 +2303,10 @@ static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_ php_http_client_curl_t *curl = h->ctx; switch (opt) { + case PHP_HTTP_CLIENT_OPT_CONFIGURATION: + return php_http_options_apply(&php_http_curlm_options, (HashTable *) arg, h); + break; + case PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING: if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) { return FAILURE; @@ -2025,23 +2315,7 @@ static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_ case PHP_HTTP_CLIENT_OPT_USE_EVENTS: #if PHP_HTTP_HAVE_EVENT - if ((curl->useevents = *((zend_bool *) arg))) { - if (!curl->evbase) { - curl->evbase = event_base_new(); - } - if (!curl->timeout) { - curl->timeout = ecalloc(1, sizeof(struct event)); - } - curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h); - curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback); - curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h); - curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback); - } else { - curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL); - curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL); - curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL); - curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL); - } + return php_http_curlm_use_eventloop(h, *(zend_bool *) arg); break; #endif @@ -2051,9 +2325,43 @@ static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_ return SUCCESS; } +static int apply_available_options(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) +{ + php_http_option_t *opt = pDest; + HashTable *ht; + zval *entry; + int c; + + ht = va_arg(args, HashTable*); + + MAKE_STD_ZVAL(entry); + + if ((c = zend_hash_num_elements(&opt->suboptions.options))) { + array_init_size(entry, c); + zend_hash_apply_with_arguments(&opt->suboptions.options TSRMLS_CC, apply_available_options, 1, Z_ARRVAL_P(entry)); + } else { + /* catch deliberate NULL options */ + if (Z_TYPE(opt->defval) == IS_STRING && !Z_STRVAL(opt->defval)) { + ZVAL_NULL(entry); + } else { + ZVAL_COPY_VALUE(entry, &opt->defval); + zval_copy_ctor(entry); + } + } + + if (hash_key->nKeyLength) { + zend_hash_quick_update(ht, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &entry, sizeof(zval *), NULL); + } else { + zend_hash_index_update(ht, hash_key->h, (void *) &entry, sizeof(zval *), NULL); + } + + return ZEND_HASH_APPLY_KEEP; +} + static STATUS php_http_client_curl_getopt(php_http_client_t *h, php_http_client_getopt_opt_t opt, void *arg, void **res) { php_http_client_enqueue_t *enqueue; + TSRMLS_FETCH_FROM_CTX(h->ts); switch (opt) { case PHP_HTTP_CLIENT_OPT_PROGRESS_INFO: @@ -2074,6 +2382,14 @@ static STATUS php_http_client_curl_getopt(php_http_client_t *h, php_http_client_ } break; + case PHP_HTTP_CLIENT_OPT_AVAILABLE_OPTIONS: + zend_hash_apply_with_arguments(&php_http_curle_options.options TSRMLS_CC, apply_available_options, 1, *(HashTable **) res); + break; + + case PHP_HTTP_CLIENT_OPT_AVAILABLE_CONFIGURATION: + zend_hash_apply_with_arguments(&php_http_curlm_options.options TSRMLS_CC, apply_available_options, 1, *(HashTable **) res); + break; + default: break; } @@ -2110,8 +2426,8 @@ PHP_MINIT_FUNCTION(http_client_curl) }; if (SUCCESS != php_http_client_driver_add(&driver)) { - return FAILURE; - } + return FAILURE; + } if (SUCCESS != php_persistent_handle_provide(ZEND_STRL("http\\Client\\Curl"), &php_http_curlm_resource_factory_ops, NULL, NULL TSRMLS_CC)) { return FAILURE; @@ -2126,12 +2442,21 @@ PHP_MINIT_FUNCTION(http_client_curl) php_http_curle_options_init(options TSRMLS_CC); } + if ((options = php_http_options_init(&php_http_curlm_options, 1))) { + options->getter = php_http_option_get; + options->setter = php_http_curlm_set_option; + + php_http_curlm_options_init(options TSRMLS_CC); + } /* * HTTP Protocol Version Constants */ REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0, CONST_CS|CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1, CONST_CS|CONST_PERSISTENT); +#if PHP_HTTP_CURL_VERSION(7,33,0) + REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_2_0", CURL_HTTP_VERSION_2_0, CONST_CS|CONST_PERSISTENT); +#endif REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_ANY", CURL_HTTP_VERSION_NONE, CONST_CS|CONST_PERSISTENT); /* @@ -2146,6 +2471,9 @@ PHP_MINIT_FUNCTION(http_client_curl) REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv2", CURL_SSLVERSION_SSLv2, CONST_CS|CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv3", CURL_SSLVERSION_SSLv3, CONST_CS|CONST_PERSISTENT); REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_ANY", CURL_SSLVERSION_DEFAULT, CONST_CS|CONST_PERSISTENT); +#if PHP_HTTP_CURL_VERSION(7,21,4) && defined(PHP_HTTP_CURL_TLSAUTH_SRP) + REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "TLSAUTH_SRP", CURL_TLSAUTH_SRP, CONST_CS|CONST_PERSISTENT); +#endif /* * DNS IPvX resolving @@ -2202,6 +2530,7 @@ PHP_MSHUTDOWN_FUNCTION(http_client_curl) php_persistent_handle_cleanup(ZEND_STRL("http\\Client\\Curl\\Request"), NULL, 0 TSRMLS_CC); php_http_options_dtor(&php_http_curle_options); + php_http_options_dtor(&php_http_curlm_options); return SUCCESS; } diff --git a/php_http_client_request.c b/php_http_client_request.c index ea223be..0e40cc5 100644 --- a/php_http_client_request.c +++ b/php_http_client_request.c @@ -31,12 +31,12 @@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpClientRequest___construct, 0, 0, 0) ZEND_END_ARG_INFO(); static PHP_METHOD(HttpClientRequest, __construct) { - char *meth_str = NULL, *url_str = NULL; - int meth_len = 0, url_len = 0; - zval *zheaders = NULL, *zbody = NULL; + char *meth_str = NULL; + int meth_len = 0; + zval *zheaders = NULL, *zbody = NULL, *zurl = NULL; php_http_message_object_t *obj; - php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!s!a!O!", &meth_str, &meth_len, &url_str, &url_len, &zheaders, &zbody, php_http_message_body_class_entry), invalid_arg, return); + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!z!a!O!", &meth_str, &meth_len, &zurl, &zheaders, &zbody, php_http_message_body_class_entry), invalid_arg, return); obj = zend_object_store_get_object(getThis() TSRMLS_CC); @@ -52,8 +52,8 @@ static PHP_METHOD(HttpClientRequest, __construct) if (meth_str && meth_len) { PHP_HTTP_INFO(obj->message).request.method = estrndup(meth_str, meth_len); } - if (url_str && url_len) { - PHP_HTTP_INFO(obj->message).request.url = estrndup(url_str, url_len); + if (zurl) { + PHP_HTTP_INFO(obj->message).request.url = php_http_url_from_zval(zurl, ~0 TSRMLS_CC); } if (zheaders) { array_copy(Z_ARRVAL_P(zheaders), &obj->message->hdrs); @@ -113,8 +113,9 @@ static PHP_METHOD(HttpClientRequest, setQuery) { zval *qdata = NULL; php_http_message_object_t *obj; - php_url *old_url = NULL, new_url = {NULL}; + php_http_url_t *old_url = NULL, new_url = {NULL}; char empty[] = ""; + unsigned flags = PHP_HTTP_URL_REPLACE; php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!", &qdata), invalid_arg, return); @@ -137,21 +138,20 @@ static PHP_METHOD(HttpClientRequest, setQuery) new_url.query = Z_STRVAL(str); zval_dtor(&arr); } else { - new_url.query = &empty[0]; + flags = PHP_HTTP_URL_STRIP_QUERY; } if (obj->message->http.info.request.url) { - old_url = php_url_parse(obj->message->http.info.request.url); - efree(obj->message->http.info.request.url); + old_url = obj->message->http.info.request.url; } - php_http_url(PHP_HTTP_URL_REPLACE, old_url, &new_url, NULL, &obj->message->http.info.request.url, NULL TSRMLS_CC); + obj->message->http.info.request.url = php_http_url_mod(old_url, &new_url, flags TSRMLS_CC); if (old_url) { - php_url_free(old_url); + php_http_url_free(&old_url); } if (new_url.query != &empty[0]) { - STR_FREE(new_url.query); + PTR_FREE(new_url.query); } RETVAL_ZVAL(getThis(), 1, 0); @@ -166,16 +166,8 @@ static PHP_METHOD(HttpClientRequest, getQuery) PHP_HTTP_CLIENT_REQUEST_OBJECT_INIT(obj); - if (obj->message->http.info.request.url) { - php_url *purl = php_url_parse(obj->message->http.info.request.url); - - if (purl) { - if (purl->query) { - RETVAL_STRING(purl->query, 0); - purl->query = NULL; - } - php_url_free(purl); - } + if (obj->message->http.info.request.url && obj->message->http.info.request.url->query) { + RETVAL_STRING(obj->message->http.info.request.url->query, 1); } } } @@ -187,7 +179,7 @@ static PHP_METHOD(HttpClientRequest, addQuery) { zval *qdata, arr, str; php_http_message_object_t *obj; - php_url *old_url = NULL, new_url = {NULL}; + php_http_url_t *old_url = NULL, new_url = {NULL}; php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &qdata), invalid_arg, return); @@ -208,16 +200,15 @@ static PHP_METHOD(HttpClientRequest, addQuery) zval_dtor(&arr); if (obj->message->http.info.request.url) { - old_url = php_url_parse(obj->message->http.info.request.url); - efree(obj->message->http.info.request.url); + old_url = obj->message->http.info.request.url; } - php_http_url(PHP_HTTP_URL_JOIN_QUERY, old_url, &new_url, NULL, &obj->message->http.info.request.url, NULL TSRMLS_CC); + obj->message->http.info.request.url = php_http_url_mod(old_url, &new_url, PHP_HTTP_URL_JOIN_QUERY TSRMLS_CC); if (old_url) { - php_url_free(old_url); + php_http_url_free(&old_url); } - STR_FREE(new_url.query); + PTR_FREE(new_url.query); RETVAL_ZVAL(getThis(), 1, 0); } diff --git a/php_http_cookie.c b/php_http_cookie.c index 4bd8d80..354dfa6 100644 --- a/php_http_cookie.c +++ b/php_http_cookie.c @@ -41,8 +41,8 @@ php_http_cookie_list_t *php_http_cookie_list_copy(php_http_cookie_list_t *from, array_copy(&from->cookies, &to->cookies); array_copy(&from->extras, &to->extras); - STR_SET(to->path, from->path ? estrdup(from->path) : NULL); - STR_SET(to->domain, from->domain ? estrdup(from->domain) : NULL); + PTR_SET(to->path, from->path ? estrdup(from->path) : NULL); + PTR_SET(to->domain, from->domain ? estrdup(from->domain) : NULL); to->expires = from->expires; to->max_age = from->max_age; to->flags = from->flags; @@ -56,8 +56,8 @@ void php_http_cookie_list_dtor(php_http_cookie_list_t *list) zend_hash_destroy(&list->cookies); zend_hash_destroy(&list->extras); - STR_SET(list->path, NULL); - STR_SET(list->domain, NULL); + PTR_SET(list->path, NULL); + PTR_SET(list->domain, NULL); } } @@ -125,9 +125,9 @@ static void add_entry(php_http_cookie_list_t *list, char **allowed_extras, long } if _KEY_IS("path") { - STR_SET(list->path, estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg))); + PTR_SET(list->path, estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg))); } else if _KEY_IS("domain") { - STR_SET(list->domain, estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg))); + PTR_SET(list->domain, estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg))); } else if _KEY_IS("expires") { char *date = estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg)); list->expires = php_parse_date(date, NULL); @@ -782,7 +782,7 @@ static PHP_METHOD(HttpCookie, setDomain) PHP_HTTP_COOKIE_OBJECT_INIT(obj); - STR_SET(obj->list->domain, domain_str ? estrndup(domain_str, domain_len) : NULL); + PTR_SET(obj->list->domain, domain_str ? estrndup(domain_str, domain_len) : NULL); RETVAL_ZVAL(getThis(), 1, 0); } @@ -821,7 +821,7 @@ static PHP_METHOD(HttpCookie, setPath) PHP_HTTP_COOKIE_OBJECT_INIT(obj); - STR_SET(obj->list->path, path_str ? estrndup(path_str, path_len) : NULL); + PTR_SET(obj->list->path, path_str ? estrndup(path_str, path_len) : NULL); RETVAL_ZVAL(getThis(), 1, 0); } diff --git a/php_http_encoding.c b/php_http_encoding.c index 7f0462c..b7050f6 100644 --- a/php_http_encoding.c +++ b/php_http_encoding.c @@ -180,7 +180,7 @@ STATUS php_http_encoding_deflate(int flags, const char *data, size_t data_len, c (*encoded)[*encoded_len = Z.total_out] = '\0'; return SUCCESS; } else { - STR_SET(*encoded, NULL); + PTR_SET(*encoded, NULL); *encoded_len = 0; } } @@ -513,7 +513,7 @@ static STATUS deflate_update(php_http_encoding_stream_t *s, const char *data, si return SUCCESS; } - STR_SET(*encoded, NULL); + PTR_SET(*encoded, NULL); *encoded_len = 0; php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to update deflate stream: %s", zError(status)); return FAILURE; @@ -709,7 +709,7 @@ static STATUS deflate_flush(php_http_encoding_stream_t *s, char **encoded, size_ return SUCCESS; } - STR_SET(*encoded, NULL); + PTR_SET(*encoded, NULL); *encoded_len = 0; php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to flush deflate stream: %s", zError(status)); return FAILURE; @@ -766,7 +766,7 @@ static STATUS deflate_finish(php_http_encoding_stream_t *s, char **encoded, size return SUCCESS; } - STR_SET(*encoded, NULL); + PTR_SET(*encoded, NULL); *encoded_len = 0; php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to finish deflate stream: %s", zError(status)); return FAILURE; @@ -805,7 +805,7 @@ static STATUS inflate_finish(php_http_encoding_stream_t *s, char **decoded, size return SUCCESS; } - STR_SET(*decoded, NULL); + PTR_SET(*decoded, NULL); *decoded_len = 0; php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to finish inflate stream: %s", zError(status)); return FAILURE; @@ -1071,7 +1071,7 @@ static PHP_METHOD(HttpEncodingStream, finish) RETURN_EMPTY_STRING(); } } else { - STR_FREE(encoded_str); + PTR_FREE(encoded_str); } } } diff --git a/php_http_env.c b/php_http_env.c index 2969d1a..6de5ecb 100644 --- a/php_http_env.c +++ b/php_http_env.c @@ -70,7 +70,7 @@ PHP_RINIT_FUNCTION(http_env) } } - STR_SET(SG(request_info).content_type_dup, NULL); + PTR_SET(SG(request_info).content_type_dup, NULL); return SUCCESS; } @@ -287,7 +287,7 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_ return PHP_HTTP_RANGE_NO; } if (strncmp(range, "bytes=", lenof("bytes="))) { - STR_FREE(range); + PTR_FREE(range); return PHP_HTTP_RANGE_NO; } @@ -343,7 +343,7 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_ switch (end) { /* "0-" */ case -1: - STR_FREE(range); + PTR_FREE(range); return PHP_HTTP_RANGE_NO; /* "0-0" */ @@ -364,7 +364,7 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_ case -1: /* "-", "-0" */ if (end == -1 || end == -10) { - STR_FREE(range); + PTR_FREE(range); return PHP_HTTP_RANGE_ERR; } begin = length - end; @@ -374,13 +374,13 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_ /* "12345-(NNN)" */ default: if (length <= (size_t) begin) { - STR_FREE(range); + PTR_FREE(range); return PHP_HTTP_RANGE_ERR; } switch (end) { /* "12345-0" */ case -10: - STR_FREE(range); + PTR_FREE(range); return PHP_HTTP_RANGE_ERR; /* "12345-" */ @@ -393,7 +393,7 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_ if (length <= (size_t) end) { end = length - 1; } else if (end < begin) { - STR_FREE(range); + PTR_FREE(range); return PHP_HTTP_RANGE_ERR; } break; @@ -415,12 +415,12 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_ break; default: - STR_FREE(range); + PTR_FREE(range); return PHP_HTTP_RANGE_NO; } } while (c != 0); - STR_FREE(range); + PTR_FREE(range); return PHP_HTTP_RANGE_OK; } @@ -573,107 +573,22 @@ STATUS php_http_env_set_response_header_value(long http_code, const char *name_s ret = sapi_header_op(replace ? SAPI_HEADER_REPLACE : SAPI_HEADER_ADD, (void *) &h TSRMLS_CC); zval_ptr_dtor(&data); - STR_FREE(h.line); + PTR_FREE(h.line); return ret; } } } -static PHP_HTTP_STRLIST(php_http_env_response_status) = - PHP_HTTP_STRLIST_ITEM("Continue") - PHP_HTTP_STRLIST_ITEM("Switching Protocols") - PHP_HTTP_STRLIST_ITEM("Processing") - PHP_HTTP_STRLIST_NEXT - PHP_HTTP_STRLIST_ITEM("OK") - PHP_HTTP_STRLIST_ITEM("Created") - PHP_HTTP_STRLIST_ITEM("Accepted") - PHP_HTTP_STRLIST_ITEM("Non-Authoritative Information") - PHP_HTTP_STRLIST_ITEM("No Content") - PHP_HTTP_STRLIST_ITEM("Reset Content") - PHP_HTTP_STRLIST_ITEM("Partial Content") - PHP_HTTP_STRLIST_ITEM("Multi-Status") - PHP_HTTP_STRLIST_ITEM("Already Reported") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("IM Used") - PHP_HTTP_STRLIST_NEXT - PHP_HTTP_STRLIST_ITEM("Multiple Choices") - PHP_HTTP_STRLIST_ITEM("Moved Permanently") - PHP_HTTP_STRLIST_ITEM("Found") - PHP_HTTP_STRLIST_ITEM("See Other") - PHP_HTTP_STRLIST_ITEM("Not Modified") - PHP_HTTP_STRLIST_ITEM("Use Proxy") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("Temporary Redirect") - PHP_HTTP_STRLIST_ITEM("Permanent Redirect") - PHP_HTTP_STRLIST_NEXT - PHP_HTTP_STRLIST_ITEM("Bad Request") - PHP_HTTP_STRLIST_ITEM("Unauthorized") - PHP_HTTP_STRLIST_ITEM("Payment Required") - PHP_HTTP_STRLIST_ITEM("Forbidden") - PHP_HTTP_STRLIST_ITEM("Not Found") - PHP_HTTP_STRLIST_ITEM("Method Not Allowed") - PHP_HTTP_STRLIST_ITEM("Not Acceptable") - PHP_HTTP_STRLIST_ITEM("Proxy Authentication Required") - PHP_HTTP_STRLIST_ITEM("Request Timeout") - PHP_HTTP_STRLIST_ITEM("Conflict") - PHP_HTTP_STRLIST_ITEM("Gone") - PHP_HTTP_STRLIST_ITEM("Length Required") - PHP_HTTP_STRLIST_ITEM("Precondition Failed") - PHP_HTTP_STRLIST_ITEM("Request Entity Too Large") - PHP_HTTP_STRLIST_ITEM("Request URI Too Long") - PHP_HTTP_STRLIST_ITEM("Unsupported Media Type") - PHP_HTTP_STRLIST_ITEM("Requested Range Not Satisfiable") - PHP_HTTP_STRLIST_ITEM("Expectation Failed") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("Unprocessible Entity") - PHP_HTTP_STRLIST_ITEM("Locked") - PHP_HTTP_STRLIST_ITEM("Failed Dependency") - PHP_HTTP_STRLIST_ITEM("(Reserved)") - PHP_HTTP_STRLIST_ITEM("Upgrade Required") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("Precondition Required") - PHP_HTTP_STRLIST_ITEM("Too Many Requests") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("Request Header Fields Too Large") - PHP_HTTP_STRLIST_NEXT - PHP_HTTP_STRLIST_ITEM("Internal Server Error") - PHP_HTTP_STRLIST_ITEM("Not Implemented") - PHP_HTTP_STRLIST_ITEM("Bad Gateway") - PHP_HTTP_STRLIST_ITEM("Service Unavailable") - PHP_HTTP_STRLIST_ITEM("Gateway Timeout") - PHP_HTTP_STRLIST_ITEM("HTTP Version Not Supported") - PHP_HTTP_STRLIST_ITEM("Variant Also Negotiates") - PHP_HTTP_STRLIST_ITEM("Insufficient Storage") - PHP_HTTP_STRLIST_ITEM("Loop Detected") - PHP_HTTP_STRLIST_ITEM("(Unused)") - PHP_HTTP_STRLIST_ITEM("Not Extended") - PHP_HTTP_STRLIST_ITEM("Network Authentication Required") - PHP_HTTP_STRLIST_STOP -; - const char *php_http_env_get_response_status_for_code(unsigned code) { - return php_http_strlist_find(php_http_env_response_status, 100, code); + switch (code) { +#define PHP_HTTP_RESPONSE_CODE(c, s) case c: return s; +#include "php_http_response_codes.h" +#undef PHP_HTTP_RESPONSE_CODE + default: + return NULL; + } } ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getRequestHeader, 0, 0, 0) @@ -724,32 +639,29 @@ ZEND_END_ARG_INFO(); static PHP_METHOD(HttpEnv, getResponseStatusForCode) { long code; + const char *status; if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &code)) { return; } - RETURN_STRING(php_http_env_get_response_status_for_code(code), 1); + + if ((status = php_http_env_get_response_status_for_code(code))) { + RETURN_STRING(status, 1); + } } ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseStatusForAllCodes, 0, 0, 0) ZEND_END_ARG_INFO(); static PHP_METHOD(HttpEnv, getResponseStatusForAllCodes) { - const char *s; - unsigned c; - php_http_strlist_iterator_t i; - if (SUCCESS != zend_parse_parameters_none()) { return; } array_init(return_value); - for ( php_http_strlist_iterator_init(&i, php_http_env_response_status, 100); - *(s = php_http_strlist_iterator_this(&i, &c)); - php_http_strlist_iterator_next(&i) - ) { - add_index_string(return_value, c, s, 1); - } +#define PHP_HTTP_RESPONSE_CODE(code, status) add_index_string(return_value, code, status, 1); +#include "php_http_response_codes.h" +#undef PHP_HTTP_RESPONSE_CODE } ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseHeader, 0, 0, 0) @@ -977,7 +889,7 @@ static SAPI_POST_HANDLER_FUNC(php_http_json_post_handler) } } #if PHP_VERSION_ID >= 50600 - STR_FREE(json_str); + PTR_FREE(json_str); #endif } diff --git a/php_http_env_request.c b/php_http_env_request.c index 6aff0c0..ac0a5e4 100644 --- a/php_http_env_request.c +++ b/php_http_env_request.c @@ -149,6 +149,16 @@ static PHP_METHOD(HttpEnvRequest, __construct) zend_update_property(php_http_env_request_class_entry, getThis(), ZEND_STRL("form"), zqs TSRMLS_CC); zval_ptr_dtor(&zqs); + zsg = php_http_env_get_superglobal(ZEND_STRL("_COOKIE") TSRMLS_CC); + MAKE_STD_ZVAL(zqs); + object_init_ex(zqs, php_http_querystring_class_entry); + php_http_expect(SUCCESS == php_http_querystring_ctor(zqs, zsg TSRMLS_CC), unexpected_val, + zval_ptr_dtor(&zqs); + return; + ); + zend_update_property(php_http_env_request_class_entry, getThis(), ZEND_STRL("cookie"), zqs TSRMLS_CC); + zval_ptr_dtor(&zqs); + MAKE_STD_ZVAL(zqs); array_init(zqs); if ((zsg = php_http_env_get_superglobal(ZEND_STRL("_FILES") TSRMLS_CC))) { @@ -212,6 +222,22 @@ static PHP_METHOD(HttpEnvRequest, getQuery) } } +ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnvRequest_getCookie, 0, 0, 0) + ZEND_ARG_INFO(0, name) + ZEND_ARG_INFO(0, type) + ZEND_ARG_INFO(0, defval) + ZEND_ARG_INFO(0, delete) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpEnvRequest, getCookie) +{ + if (ZEND_NUM_ARGS()) { + call_querystring_get("cookie"); + } else { + zval *zcookie = zend_read_property(php_http_env_request_class_entry, getThis(), ZEND_STRL("cookie"), 0 TSRMLS_CC); + RETURN_ZVAL(zcookie, 1, 0); + } +} + ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnvRequest_getFiles, 0, 0, 0) ZEND_END_ARG_INFO(); static PHP_METHOD(HttpEnvRequest, getFiles) @@ -226,6 +252,7 @@ static zend_function_entry php_http_env_request_methods[] = { PHP_ME(HttpEnvRequest, __construct, ai_HttpEnvRequest___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) PHP_ME(HttpEnvRequest, getForm, ai_HttpEnvRequest_getForm, ZEND_ACC_PUBLIC) PHP_ME(HttpEnvRequest, getQuery, ai_HttpEnvRequest_getQuery, ZEND_ACC_PUBLIC) + PHP_ME(HttpEnvRequest, getCookie, ai_HttpEnvRequest_getCookie, ZEND_ACC_PUBLIC) PHP_ME(HttpEnvRequest, getFiles, ai_HttpEnvRequest_getFiles, ZEND_ACC_PUBLIC) EMPTY_FUNCTION_ENTRY }; @@ -241,6 +268,7 @@ PHP_MINIT_FUNCTION(http_env_request) zend_declare_property_null(php_http_env_request_class_entry, ZEND_STRL("query"), ZEND_ACC_PROTECTED TSRMLS_CC); zend_declare_property_null(php_http_env_request_class_entry, ZEND_STRL("form"), ZEND_ACC_PROTECTED TSRMLS_CC); + zend_declare_property_null(php_http_env_request_class_entry, ZEND_STRL("cookie"), ZEND_ACC_PROTECTED TSRMLS_CC); zend_declare_property_null(php_http_env_request_class_entry, ZEND_STRL("files"), ZEND_ACC_PROTECTED TSRMLS_CC); return SUCCESS; diff --git a/php_http_env_response.c b/php_http_env_response.c index 13a35c6..2f2f161 100644 --- a/php_http_env_response.c +++ b/php_http_env_response.c @@ -26,6 +26,7 @@ static void set_option(zval *options, const char *name_str, size_t name_len, int case IS_STRING: zend_update_property_stringl(Z_OBJCE_P(options), options, name_str, name_len, value_ptr, value_len TSRMLS_CC); break; + case IS_ARRAY: case IS_OBJECT: zend_update_property(Z_OBJCE_P(options), options, name_str, name_len, value_ptr TSRMLS_CC); break; @@ -47,6 +48,7 @@ static void set_option(zval *options, const char *name_str, size_t name_len, int char *value = estrndup(value_ptr, value_len); add_assoc_stringl_ex(options, name_str, name_len + 1, value, value_len, 0); break; + case IS_ARRAY: case IS_OBJECT: Z_ADDREF_P(value_ptr); add_assoc_zval_ex(options, name_str, name_len + 1, value_ptr); @@ -108,6 +110,36 @@ static php_http_message_t *get_request(zval *options TSRMLS_DC) return request; } +static void set_cookie(zval *options, zval *zcookie_new TSRMLS_DC) +{ + HashPosition pos; + zval *zcookies_set; + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + php_http_cookie_object_t *obj = zend_object_store_get_object(zcookie_new TSRMLS_CC); + + zcookies_set = get_option(options, ZEND_STRL("cookies") TSRMLS_CC); + if (!zcookies_set || Z_TYPE_P(zcookies_set) != IS_ARRAY) { + if (zcookies_set) { + zval_ptr_dtor(&zcookies_set); + } + MAKE_STD_ZVAL(zcookies_set); + array_init_size(zcookies_set, zend_hash_num_elements(&obj->list->cookies)); + } else { + SEPARATE_ZVAL(&zcookies_set); + } + + FOREACH_HASH_KEY(pos, &obj->list->cookies, key) { + Z_ADDREF_P(zcookie_new); + if (key.type == HASH_KEY_IS_STRING) { + add_assoc_zval_ex(zcookies_set, key.str, key.len, zcookie_new); + } else { + add_index_zval(zcookies_set, key.num, zcookie_new); + } + } + + set_option(options, ZEND_STRL("cookies"), IS_ARRAY, zcookies_set, 0 TSRMLS_CC); + zval_ptr_dtor(&zcookies_set); +} php_http_cache_status_t php_http_env_is_response_cached_by_etag(zval *options, const char *header_str, size_t header_len, php_http_message_t *request TSRMLS_DC) { @@ -147,7 +179,7 @@ php_http_cache_status_t php_http_env_is_response_cached_by_etag(zval *options, c efree(etag); } - STR_FREE(header); + PTR_FREE(header); return ret; } @@ -190,7 +222,7 @@ php_http_cache_status_t php_http_env_is_response_cached_by_last_modified(zval *o } } - STR_FREE(header); + PTR_FREE(header); return ret; } @@ -254,7 +286,7 @@ static STATUS php_http_env_response_send_data(php_http_env_response_t *r, const return SUCCESS; } chunks_sent = php_http_buffer_chunked_output(&r->buffer, enc_str, enc_len, buf ? chunk : 0, output, r TSRMLS_CC); - STR_FREE(enc_str); + PTR_FREE(enc_str); } else { chunks_sent = php_http_buffer_chunked_output(&r->buffer, buf, len, buf ? chunk : 0, output, r TSRMLS_CC); } @@ -303,8 +335,8 @@ void php_http_env_response_dtor(php_http_env_response_t *r) } php_http_buffer_free(&r->buffer); zval_ptr_dtor(&r->options); - STR_FREE(r->content.type); - STR_FREE(r->content.encoding); + PTR_FREE(r->content.type); + PTR_FREE(r->content.encoding); if (r->content.encoder) { php_http_encoding_stream_free(&r->content.encoder); } @@ -370,6 +402,33 @@ static STATUS php_http_env_response_send_head(php_http_env_response_t *r, php_ht return ret; } + if ((zoption = get_option(options, ZEND_STRL("cookies") TSRMLS_CC))) { + if (Z_TYPE_P(zoption) == IS_ARRAY) { + HashPosition pos; + zval **zcookie; + + FOREACH_VAL(pos, zoption, zcookie) { + if (Z_TYPE_PP(zcookie) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(zcookie), php_http_cookie_class_entry TSRMLS_CC)) { + php_http_cookie_object_t *obj = zend_object_store_get_object(*zcookie TSRMLS_CC); + char *str; + size_t len; + + php_http_cookie_list_to_string(obj->list, &str, &len); + if (SUCCESS != (ret = r->ops->add_header(r, "Set-Cookie: %s", str))) { + efree(str); + break; + } + efree(str); + } + } + } + zval_ptr_dtor(&zoption); + } + + if (ret != SUCCESS) { + return ret; + } + if ((zoption = get_option(options, ZEND_STRL("contentType") TSRMLS_CC))) { zval *zoption_copy = php_http_ztyp(IS_STRING, zoption); @@ -824,26 +883,38 @@ typedef struct php_http_env_response_stream_ctx { long status_code; php_stream *stream; + php_stream_filter *chunked_filter; + php_http_message_t *request; unsigned started:1; unsigned finished:1; + unsigned chunked:1; } php_http_env_response_stream_ctx_t; static STATUS php_http_env_response_stream_init(php_http_env_response_t *r, void *init_arg) { php_http_env_response_stream_ctx_t *ctx; + size_t buffer_size = 0x1000; TSRMLS_FETCH_FROM_CTX(r->ts); ctx = ecalloc(1, sizeof(*ctx)); ctx->stream = init_arg; - if (SUCCESS != zend_list_addref(ctx->stream->rsrc_id)) { + if (!ctx->stream || SUCCESS != zend_list_addref(ctx->stream->rsrc_id)) { efree(ctx); return FAILURE; } + php_stream_set_option(ctx->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buffer_size); zend_hash_init(&ctx->header, 0, NULL, ZVAL_PTR_DTOR, 0); php_http_version_init(&ctx->version, 1, 1 TSRMLS_CC); ctx->status_code = 200; + ctx->chunked = 1; + ctx->request = get_request(r->options TSRMLS_CC); + + /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */ + if (ctx->request && ctx->request->http.version.major == 1 && ctx->request->http.version.minor == 0) { + ctx->version.minor = 0; + } r->ctx = ctx; @@ -854,36 +925,77 @@ static void php_http_env_response_stream_dtor(php_http_env_response_t *r) php_http_env_response_stream_ctx_t *ctx = r->ctx; TSRMLS_FETCH_FROM_CTX(r->ts); + if (ctx->chunked_filter) { + php_stream_filter_free(ctx->chunked_filter TSRMLS_CC); + } zend_hash_destroy(&ctx->header); zend_list_delete(ctx->stream->rsrc_id); efree(ctx); r->ctx = NULL; } -static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header TSRMLS_DC) +static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header, php_http_buffer_t *buf TSRMLS_DC) { HashPosition pos; zval **val; - FOREACH_HASH_VAL(pos, &ctx->header, val) { + FOREACH_HASH_VAL(pos, header, val) { if (Z_TYPE_PP(val) == IS_ARRAY) { - php_http_env_response_stream_header(ctx, Z_ARRVAL_PP(val) TSRMLS_CC); + php_http_env_response_stream_header(ctx, Z_ARRVAL_PP(val), buf TSRMLS_CC); } else { - php_stream_write(ctx->stream, Z_STRVAL_PP(val), Z_STRLEN_PP(val)); - php_stream_write_string(ctx->stream, PHP_HTTP_CRLF); + zval *tmp = php_http_ztyp(IS_STRING, *val); + + if (ctx->chunked) { + /* disable chunked transfer encoding if we've got an explicit content-length */ + if (!strncasecmp(Z_STRVAL_P(tmp), "Content-Length:", lenof("Content-Length:"))) { + ctx->chunked = 0; + } + } + php_http_buffer_append(buf, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp)); + php_http_buffer_appends(buf, PHP_HTTP_CRLF); + zval_ptr_dtor(&tmp); } } } static STATUS php_http_env_response_stream_start(php_http_env_response_stream_ctx_t *ctx TSRMLS_DC) { + php_http_buffer_t header_buf; + if (ctx->started || ctx->finished) { return FAILURE; } - php_stream_printf(ctx->stream TSRMLS_CC, "HTTP/%u.%u %ld %s" PHP_HTTP_CRLF, ctx->version.major, ctx->version.minor, ctx->status_code, php_http_env_get_response_status_for_code(ctx->status_code)); - php_http_env_response_stream_header(ctx, &ctx->header TSRMLS_CC); - php_stream_write_string(ctx->stream, PHP_HTTP_CRLF); - ctx->started = 1; - return SUCCESS; + php_http_buffer_init(&header_buf); + php_http_buffer_appendf(&header_buf, "HTTP/%u.%u %ld %s" PHP_HTTP_CRLF, ctx->version.major, ctx->version.minor, ctx->status_code, php_http_env_get_response_status_for_code(ctx->status_code)); + + /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */ + if (ctx->version.major == 1 && ctx->version.minor == 0) { + ctx->chunked = 0; + } else if (ctx->status_code == 204 || ctx->status_code/100 == 1) { + ctx->chunked = 0; + } else if (ctx->request && ctx->status_code/100 == 2 && !strcasecmp(ctx->request->http.info.request.method, "CONNECT")) { + ctx->chunked = 0; + } + + php_http_env_response_stream_header(ctx, &ctx->header, &header_buf TSRMLS_CC); + + /* enable chunked transfer encoding */ + if (ctx->chunked) { + php_http_buffer_appends(&header_buf, "Transfer-Encoding: chunked" PHP_HTTP_CRLF); + } + php_http_buffer_appends(&header_buf, PHP_HTTP_CRLF); + + if (header_buf.used == php_stream_write(ctx->stream, header_buf.data, header_buf.used)) { + ctx->started = 1; + } + php_http_buffer_dtor(&header_buf); + php_stream_flush(ctx->stream); + + if (ctx->chunked) { + ctx->chunked_filter = php_stream_filter_create("http.chunked_encode", NULL, 0 TSRMLS_CC); + php_stream_filter_append(&ctx->stream->writefilters, ctx->chunked_filter); + } + + return ctx->started ? SUCCESS : FAILURE; } static long php_http_env_response_stream_get_status(php_http_env_response_t *r) { @@ -1023,19 +1135,25 @@ static STATUS php_http_env_response_stream_flush(php_http_env_response_t *r) } static STATUS php_http_env_response_stream_finish(php_http_env_response_t *r) { - php_http_env_response_stream_ctx_t *stream_ctx = r->ctx; + php_http_env_response_stream_ctx_t *ctx = r->ctx; TSRMLS_FETCH_FROM_CTX(r->ts); - if (stream_ctx->finished) { + if (ctx->finished) { return FAILURE; } - if (!stream_ctx->started) { - if (SUCCESS != php_http_env_response_stream_start(stream_ctx TSRMLS_CC)) { + if (!ctx->started) { + if (SUCCESS != php_http_env_response_stream_start(ctx TSRMLS_CC)) { return FAILURE; } } - stream_ctx->finished = 1; + php_stream_flush(ctx->stream); + if (ctx->chunked && ctx->chunked_filter) { + php_stream_filter_flush(ctx->chunked_filter, 1); + ctx->chunked_filter = php_stream_filter_remove(ctx->chunked_filter, 1 TSRMLS_CC); + } + + ctx->finished = 1; return SUCCESS; } @@ -1249,6 +1367,46 @@ static PHP_METHOD(HttpEnvResponse, setThrottleRate) RETVAL_ZVAL(getThis(), 1, 0); } +ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnvResponse_setCookie, 0, 0, 1) + ZEND_ARG_INFO(0, cookie) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpEnvResponse, setCookie) +{ + zval *zcookie_new; + zend_error_handling zeh; + php_http_cookie_list_t *list = NULL; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zcookie_new), invalid_arg, return); + + zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh TSRMLS_CC); + switch (Z_TYPE_P(zcookie_new)) { + case IS_OBJECT: + if (instanceof_function(Z_OBJCE_P(zcookie_new), php_http_cookie_class_entry TSRMLS_CC)) { + Z_ADDREF_P(zcookie_new); + break; + } + /* no break */ + case IS_ARRAY: + list = php_http_cookie_list_from_struct(NULL, zcookie_new TSRMLS_CC); + MAKE_STD_ZVAL(zcookie_new); + ZVAL_OBJVAL(zcookie_new, php_http_cookie_object_new_ex(php_http_cookie_class_entry, list, NULL TSRMLS_CC), 0); + break; + + default: + zcookie_new = php_http_ztyp(IS_STRING, zcookie_new); + list = php_http_cookie_list_parse(NULL, Z_STRVAL_P(zcookie_new), Z_STRLEN_P(zcookie_new), 0, NULL TSRMLS_CC); + zval_ptr_dtor(&zcookie_new); + MAKE_STD_ZVAL(zcookie_new); + ZVAL_OBJVAL(zcookie_new, php_http_cookie_object_new_ex(php_http_cookie_class_entry, list, NULL TSRMLS_CC), 0); + } + zend_restore_error_handling(&zeh TSRMLS_CC); + + set_cookie(getThis(), zcookie_new TSRMLS_CC); + zval_ptr_dtor(&zcookie_new); + + RETVAL_ZVAL(getThis(), 1, 0); +} + ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnvResponse_send, 0, 0, 0) ZEND_ARG_INFO(0, stream) ZEND_END_ARG_INFO(); @@ -1265,6 +1423,7 @@ static PHP_METHOD(HttpEnvResponse, send) #else php_end_ob_buffers(1 TSRMLS_CC); #endif + if (zstream) { php_http_env_response_t *r; @@ -1293,6 +1452,7 @@ static zend_function_entry php_http_env_response_methods[] = { PHP_ME(HttpEnvResponse, __construct, ai_HttpEnvResponse___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) PHP_ME(HttpEnvResponse, __invoke, ai_HttpEnvResponse___invoke, ZEND_ACC_PUBLIC) PHP_ME(HttpEnvResponse, setEnvRequest, ai_HttpEnvResponse_setEnvRequest, ZEND_ACC_PUBLIC) + PHP_ME(HttpEnvResponse, setCookie, ai_HttpEnvResponse_setCookie, ZEND_ACC_PUBLIC) PHP_ME(HttpEnvResponse, setContentType, ai_HttpEnvResponse_setContentType, ZEND_ACC_PUBLIC) PHP_ME(HttpEnvResponse, setContentDisposition, ai_HttpEnvResponse_setContentDisposition, ZEND_ACC_PUBLIC) PHP_ME(HttpEnvResponse, setContentEncoding, ai_HttpEnvResponse_setContentEncoding, ZEND_ACC_PUBLIC) @@ -1323,6 +1483,7 @@ PHP_MINIT_FUNCTION(http_env_response) zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CACHE_MISS"), PHP_HTTP_CACHE_MISS TSRMLS_CC); zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("request"), ZEND_ACC_PROTECTED TSRMLS_CC); + zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("cookies"), ZEND_ACC_PROTECTED TSRMLS_CC); zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentType"), ZEND_ACC_PROTECTED TSRMLS_CC); zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentDisposition"), ZEND_ACC_PROTECTED TSRMLS_CC); zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentEncoding"), ZEND_ACC_PROTECTED TSRMLS_CC); diff --git a/php_http_env_response.h b/php_http_env_response.h index 3a702b2..b672930 100644 --- a/php_http_env_response.h +++ b/php_http_env_response.h @@ -36,6 +36,7 @@ struct php_http_env_response { void *ctx; php_http_env_response_ops_t *ops; + php_http_cookie_list_t *cookies; php_http_buffer_t *buffer; zval *options; diff --git a/php_http_header.c b/php_http_header.c index 92a2de4..5a9ecd3 100644 --- a/php_http_header.c +++ b/php_http_header.c @@ -33,12 +33,7 @@ STATUS php_http_header_parse(const char *header, size_t length, HashTable *heade php_http_header_parser_dtor(&ctx); php_http_buffer_dtor(&buf); - if (rs == PHP_HTTP_HEADER_PARSER_STATE_FAILURE) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse headers"); - return FAILURE; - } - - return SUCCESS; + return rs == PHP_HTTP_HEADER_PARSER_STATE_FAILURE ? FAILURE : SUCCESS; } void php_http_header_to_callback(HashTable *headers, zend_bool crlf, php_http_pass_format_callback_t cb, void *cb_arg TSRMLS_DC) diff --git a/php_http_header_parser.c b/php_http_header_parser.c index 2526835..cea2dbf 100644 --- a/php_http_header_parser.c +++ b/php_http_header_parser.c @@ -12,6 +12,10 @@ #include "php_http_api.h" +#ifndef DBG_PARSER +# define DBG_PARSER 0 +#endif + typedef struct php_http_header_parser_state_spec { php_http_header_parser_state_t state; unsigned need_data:1; @@ -21,7 +25,7 @@ static const php_http_header_parser_state_spec_t php_http_header_parser_states[] {PHP_HTTP_HEADER_PARSER_STATE_START, 1}, {PHP_HTTP_HEADER_PARSER_STATE_KEY, 1}, {PHP_HTTP_HEADER_PARSER_STATE_VALUE, 1}, - {PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX, 1}, + {PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX, 0}, {PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE, 0}, {PHP_HTTP_HEADER_PARSER_STATE_DONE, 0} }; @@ -79,8 +83,8 @@ void php_http_header_parser_dtor(php_http_header_parser_t *parser) { zend_ptr_stack_destroy(&parser->stack); php_http_info_dtor(&parser->info); - STR_FREE(parser->_key.str); - STR_FREE(parser->_val.str); + PTR_FREE(parser->_key.str); + PTR_FREE(parser->_val.str); } void php_http_header_parser_free(php_http_header_parser_t **parser) @@ -92,18 +96,38 @@ void php_http_header_parser_free(php_http_header_parser_t **parser) } } +/* NOTE: 'str' has to be null terminated */ +static void php_http_header_parser_error(size_t valid_len, char *str, size_t len, const char *eol_str TSRMLS_DC) +{ + int escaped_len; + char *escaped_str; + + escaped_str = php_addcslashes(str, len, &escaped_len, 0, ZEND_STRL("\x0..\x1F\x7F..\xFF") TSRMLS_CC); + + if (valid_len != len && (!eol_str || (str+valid_len) != eol_str)) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected character '\\%03o' at pos %zu of '%.*s'", str[valid_len], valid_len, escaped_len, escaped_str); + } else if (eol_str) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected end of line at pos %zu of '%.*s'", eol_str - str, escaped_len, escaped_str); + } else { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected end of input at pos %zu of '%.*s'", len, escaped_len, escaped_str); + } + + efree(escaped_str); +} + STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg) { TSRMLS_FETCH_FROM_CTX(parser->ts); while (buffer->used || !php_http_header_parser_states[php_http_header_parser_state_is(parser)].need_data) { -#if 0 - const char *state[] = {"START", "KEY", "VALUE", "HEADER_DONE", "DONE"}; - fprintf(stderr, "#HP: %s (avail:%zu, num:%d)\n", php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)], buffer->used, headers?zend_hash_num_elements(headers):0); +#if DBG_PARSER + const char *state[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"}; + fprintf(stderr, "#HP: %s (avail:%zu, num:%d cleanup:%u)\n", php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)], buffer->used, headers?zend_hash_num_elements(headers):0, flags); _dpf(0, buffer->data, buffer->used); #endif switch (php_http_header_parser_state_pop(parser)) { case PHP_HTTP_HEADER_PARSER_STATE_FAILURE: + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers"); return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE); case PHP_HTTP_HEADER_PARSER_STATE_START: { @@ -136,13 +160,28 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE); } else if ((colon = memchr(buffer->data, ':', buffer->used)) && (!eol_str || eol_str > colon)) { /* header: string */ - parser->_key.str = estrndup(buffer->data, parser->_key.len = colon - buffer->data); + size_t valid_len; + + parser->_key.len = colon - buffer->data; + parser->_key.str = estrndup(buffer->data, parser->_key.len); + + valid_len = strspn(parser->_key.str, PHP_HTTP_HEADER_NAME_CHARS); + if (valid_len != parser->_key.len) { + php_http_header_parser_error(valid_len, parser->_key.str, parser->_key.len, eol_str TSRMLS_CC); + PTR_SET(parser->_key.str, NULL); + return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE); + } while (PHP_HTTP_IS_CTYPE(space, *++colon) && *colon != '\n' && *colon != '\r'); php_http_buffer_cut(buffer, 0, colon - buffer->data); php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE); - } else { - /* neither reqeust/response line nor header: string */ + } else if (eol_str || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) { + /* neither reqeust/response line nor 'header:' string, or injected new line or NUL etc. */ + php_http_buffer_fix(buffer); + php_http_header_parser_error(strspn(buffer->data, PHP_HTTP_HEADER_NAME_CHARS), buffer->data, buffer->used, eol_str TSRMLS_CC); return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE); + } else { + /* keep feeding */ + return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY); } break; } @@ -181,40 +220,43 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b if ((eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) { SET_ADD_VAL(eol_str - buffer->data, eol_len); - - if (buffer->used) { - if (*buffer->data != '\t' && *buffer->data != ' ') { - php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE); - break; - } else { - php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE); - break; - } - } - } - - if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) { + php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX); + } else if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) { if (buffer->used) { SET_ADD_VAL(buffer->used, 0); } php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE); } else { - return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX); + return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE); } break; } case PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX: - if (*buffer->data == ' ' || *buffer->data == '\t') { + if (buffer->used && (*buffer->data == ' ' || *buffer->data == '\t')) { php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE); - } else { + } else if (buffer->used || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) { php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE); + } else { + /* keep feeding */ + return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX); } break; case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE: if (parser->_key.str && parser->_val.str) { zval array, **exist; + size_t valid_len = strlen(parser->_val.str); + + /* check for truncation */ + if (valid_len != parser->_val.len) { + php_http_header_parser_error(valid_len, parser->_val.str, parser->_val.len, NULL TSRMLS_CC); + + PTR_SET(parser->_key.str, NULL); + PTR_SET(parser->_val.str, NULL); + + return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE); + } if (!headers && callback_func) { callback_func(callback_arg, &headers, NULL TSRMLS_CC); @@ -231,8 +273,8 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b parser->_val.str = NULL; } - STR_SET(parser->_key.str, NULL); - STR_SET(parser->_val.str, NULL); + PTR_SET(parser->_key.str, NULL); + PTR_SET(parser->_val.str, NULL); php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY); break; @@ -245,6 +287,195 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b return php_http_header_parser_state_is(parser); } +php_http_header_parser_state_t php_http_header_parser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buf, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg) +{ + php_http_message_parser_state_t state = PHP_HTTP_MESSAGE_PARSER_STATE_START; + TSRMLS_FETCH_FROM_CTX(parser->ts); + + if (!buf->data) { + php_http_buffer_resize_ex(buf, 0x1000, 1, 0); + } + while (1) { + size_t justread = 0; +#if DBG_PARSER + const char *states[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"}; + fprintf(stderr, "#SHP: %s (f:%u)\n", states[state], flags); +#endif + /* resize if needed */ + if (buf->free < 0x1000) { + php_http_buffer_resize_ex(buf, 0x1000, 1, 0); + } + switch (state) { + case PHP_HTTP_HEADER_PARSER_STATE_FAILURE: + case PHP_HTTP_HEADER_PARSER_STATE_DONE: + return state; + + default: + /* read line */ + php_stream_get_line(s, buf->data + buf->used, buf->free, &justread); + /* if we fail reading a whole line, try a single char */ + if (!justread) { + int c = php_stream_getc(s); + + if (c != EOF) { + char s[1] = {c}; + justread = php_http_buffer_append(buf, s, 1); + } + } + php_http_buffer_account(buf, justread); + } + + if (justread) { + state = php_http_header_parser_parse(parser, buf, flags, headers, callback_func, callback_arg); + } else if (php_stream_eof(s)) { + return php_http_header_parser_parse(parser, buf, flags | PHP_HTTP_HEADER_PARSER_CLEANUP, headers, callback_func, callback_arg); + } else { + return state; + } + } + + return PHP_HTTP_HEADER_PARSER_STATE_DONE; +} + +zend_class_entry *php_http_header_parser_class_entry; +static zend_object_handlers php_http_header_parser_object_handlers; + +zend_object_value php_http_header_parser_object_new(zend_class_entry *ce TSRMLS_DC) +{ + return php_http_header_parser_object_new_ex(ce, NULL, NULL TSRMLS_CC); +} + +zend_object_value php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser, php_http_header_parser_object_t **ptr TSRMLS_DC) +{ + php_http_header_parser_object_t *o; + + o = ecalloc(1, sizeof(php_http_header_parser_object_t)); + zend_object_std_init((zend_object *) o, ce TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + + if (ptr) { + *ptr = o; + } + + if (parser) { + o->parser = parser; + } else { + o->parser = php_http_header_parser_init(NULL TSRMLS_CC); + } + o->buffer = php_http_buffer_new(); + + o->zv.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_header_parser_object_free, NULL TSRMLS_CC); + o->zv.handlers = &php_http_header_parser_object_handlers; + + return o->zv; +} + +void php_http_header_parser_object_free(void *object TSRMLS_DC) +{ + php_http_header_parser_object_t *o = (php_http_header_parser_object_t *) object; + + if (o->parser) { + php_http_header_parser_free(&o->parser); + } + if (o->buffer) { + php_http_buffer_free(&o->buffer); + } + zend_object_std_dtor((zend_object *) o TSRMLS_CC); + efree(o); +} + +ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_getState, 0, 0, 0) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpHeaderParser, getState) +{ + php_http_header_parser_object_t *parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + zend_parse_parameters_none(); + /* always return the real state */ + RETVAL_LONG(php_http_header_parser_state_is(parser_obj->parser)); +} + +ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_parse, 0, 0, 3) + ZEND_ARG_INFO(0, data) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_ARRAY_INFO(1, headers, 1) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpHeaderParser, parse) +{ + php_http_header_parser_object_t *parser_obj; + zval *zmsg; + char *data_str; + int data_len; + long flags; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slz", &data_str, &data_len, &flags, &zmsg), invalid_arg, return); + + if (Z_TYPE_P(zmsg) != IS_ARRAY) { + zval_dtor(zmsg); + array_init(zmsg); + } + parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC); + php_http_buffer_append(parser_obj->buffer, data_str, data_len); + RETVAL_LONG(php_http_header_parser_parse(parser_obj->parser, parser_obj->buffer, flags, Z_ARRVAL_P(zmsg), NULL, NULL)); +} + +ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_stream, 0, 0, 3) + ZEND_ARG_INFO(0, stream) + ZEND_ARG_INFO(0, flags) + ZEND_ARG_ARRAY_INFO(1, headers, 1) +ZEND_END_ARG_INFO(); +static PHP_METHOD(HttpHeaderParser, stream) +{ + php_http_header_parser_object_t *parser_obj; + zend_error_handling zeh; + zval *zmsg, *zstream; + php_stream *s; + long flags; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlz", &zstream, &flags, &zmsg), invalid_arg, return); + + zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh TSRMLS_CC); + php_stream_from_zval(s, &zstream); + zend_restore_error_handling(&zeh TSRMLS_CC); + + if (Z_TYPE_P(zmsg) != IS_ARRAY) { + zval_dtor(zmsg); + array_init(zmsg); + } + parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC); + RETVAL_LONG(php_http_header_parser_parse_stream(parser_obj->parser, parser_obj->buffer, s, flags, Z_ARRVAL_P(zmsg), NULL, NULL)); +} + +static zend_function_entry php_http_header_parser_methods[] = { + PHP_ME(HttpHeaderParser, getState, ai_HttpHeaderParser_getState, ZEND_ACC_PUBLIC) + PHP_ME(HttpHeaderParser, parse, ai_HttpHeaderParser_parse, ZEND_ACC_PUBLIC) + PHP_ME(HttpHeaderParser, stream, ai_HttpHeaderParser_stream, ZEND_ACC_PUBLIC) + {NULL, NULL, NULL} +}; + +PHP_MINIT_FUNCTION(http_header_parser) +{ + zend_class_entry ce; + + INIT_NS_CLASS_ENTRY(ce, "http\\Header", "Parser", php_http_header_parser_methods); + php_http_header_parser_class_entry = zend_register_internal_class(&ce TSRMLS_CC); + memcpy(&php_http_header_parser_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_http_header_parser_class_entry->create_object = php_http_header_parser_object_new; + php_http_header_parser_object_handlers.clone_obj = NULL; + + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_HEADER_PARSER_CLEANUP TSRMLS_CC); + + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_HEADER_PARSER_STATE_FAILURE TSRMLS_CC); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_HEADER_PARSER_STATE_START TSRMLS_CC); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_KEY"), PHP_HTTP_HEADER_PARSER_STATE_KEY TSRMLS_CC); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE"), PHP_HTTP_HEADER_PARSER_STATE_VALUE TSRMLS_CC); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE_EX"), PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX TSRMLS_CC); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE TSRMLS_CC); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_HEADER_PARSER_STATE_DONE TSRMLS_CC); + + return SUCCESS; +} + /* * Local variables: * tab-width: 4 diff --git a/php_http_header_parser.h b/php_http_header_parser.h index 4c60f6e..ed9ecaf 100644 --- a/php_http_header_parser.h +++ b/php_http_header_parser.h @@ -50,6 +50,22 @@ PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_pop(php PHP_HTTP_API void php_http_header_parser_dtor(php_http_header_parser_t *parser); PHP_HTTP_API void php_http_header_parser_free(php_http_header_parser_t **parser); PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg); +PHP_HTTP_API php_http_header_parser_state_t php_http_headerparser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buffer, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg); + +typedef struct php_http_header_parser_object { + zend_object zo; + zend_object_value zv; + php_http_buffer_t *buffer; + php_http_header_parser_t *parser; +} php_http_header_parser_object_t; + +PHP_HTTP_API zend_class_entry *php_http_header_parser_class_entry; + +PHP_MINIT_FUNCTION(http_header_parser); + +zend_object_value php_http_header_parser_object_new(zend_class_entry *ce TSRMLS_DC); +zend_object_value php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser, php_http_header_parser_object_t **ptr TSRMLS_DC); +void php_http_header_parser_object_free(void *object TSRMLS_DC); #endif /* PHP_HTTP_HEADER_PARSER_H */ diff --git a/php_http_info.c b/php_http_info.c index 10ea0a3..9050919 100644 --- a/php_http_info.c +++ b/php_http_info.c @@ -27,12 +27,12 @@ void php_http_info_dtor(php_http_info_t *i) { switch (i->type) { case PHP_HTTP_REQUEST: - STR_SET(PHP_HTTP_INFO(i).request.method, NULL); - STR_SET(PHP_HTTP_INFO(i).request.url, NULL); + PTR_SET(PHP_HTTP_INFO(i).request.method, NULL); + PTR_SET(PHP_HTTP_INFO(i).request.url, NULL); break; case PHP_HTTP_RESPONSE: - STR_SET(PHP_HTTP_INFO(i).response.status, NULL); + PTR_SET(PHP_HTTP_INFO(i).response.status, NULL); break; default: @@ -65,15 +65,15 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head } /* there must be HTTP/1.x in the line */ - if (!(http = php_http_locate_str(pre_header, end - pre_header, "HTTP/1.", lenof("HTTP/1.")))) { + if (!(http = php_http_locate_str(pre_header, end - pre_header, "HTTP/", lenof("HTTP/")))) { return NULL; } info = php_http_info_init(info TSRMLS_CC); - /* and nothing than SPACE or NUL after HTTP/1.x */ + /* and nothing than SPACE or NUL after HTTP/X.x */ if (!php_http_version_parse(&info->http.version, http TSRMLS_CC) - || (http[lenof("HTTP/1.1")] && (!PHP_HTTP_IS_CTYPE(space, http[lenof("HTTP/1.1")])))) { + || (http[lenof("HTTP/X.x")] && (!PHP_HTTP_IS_CTYPE(space, http[lenof("HTTP/X.x")])))) { if (free_info) { php_http_info_free(&info); } @@ -90,13 +90,22 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head /* is response */ if (pre_header == http) { - char *status = NULL; - const char *code = http + sizeof("HTTP/1.1"); + const char *status = NULL, *code = http + sizeof("HTTP/X.x"); info->type = PHP_HTTP_RESPONSE; while (' ' == *code) ++code; if (code && end > code) { - PHP_HTTP_INFO(info).response.code = strtol(code, &status, 10); + /* rfc7230#3.1.2 The status-code element is a 3-digit integer code */ + PHP_HTTP_INFO(info).response.code = 100*(*code++ - '0'); + PHP_HTTP_INFO(info).response.code += 10*(*code++ - '0'); + PHP_HTTP_INFO(info).response.code += *code++ - '0'; + if (PHP_HTTP_INFO(info).response.code < 100 || PHP_HTTP_INFO(info).response.code > 599) { + if (free_info) { + php_http_info_free(&info); + } + return NULL; + } + status = code; } else { PHP_HTTP_INFO(info).response.code = 0; } @@ -111,18 +120,27 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head } /* is request */ - else if (*(http - 1) == ' ' && (!http[lenof("HTTP/1.x")] || http[lenof("HTTP/1.x")] == '\r' || http[lenof("HTTP/1.x")] == '\n')) { + else if (*(http - 1) == ' ' && (!http[lenof("HTTP/X.x")] || http[lenof("HTTP/X.x")] == '\r' || http[lenof("HTTP/X.x")] == '\n')) { const char *url = strchr(pre_header, ' '); info->type = PHP_HTTP_REQUEST; if (url && http > url) { - PHP_HTTP_INFO(info).request.method = estrndup(pre_header, url - pre_header); + size_t url_len = url - pre_header; + + PHP_HTTP_INFO(info).request.method = estrndup(pre_header, url_len); + while (' ' == *url) ++url; while (' ' == *(http-1)) --http; + if (http > url) { - PHP_HTTP_INFO(info).request.url = estrndup(url, http - url); + /* CONNECT presents an authority only */ + if (strcasecmp(PHP_HTTP_INFO(info).request.method, "CONNECT")) { + PHP_HTTP_INFO(info).request.url = php_http_url_parse(url, http - url, ~0 TSRMLS_CC); + } else { + PHP_HTTP_INFO(info).request.url = php_http_url_parse_authority(url, http - url, ~0 TSRMLS_CC); + } } else { - STR_SET(PHP_HTTP_INFO(info).request.method, NULL); + PTR_SET(PHP_HTTP_INFO(info).request.method, NULL); return NULL; } } else { @@ -133,7 +151,7 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head return info; } - /* some darn header containing HTTP/1.x */ + /* some darn header containing HTTP/X.x */ else { if (free_info) { php_http_info_free(&info); diff --git a/php_http_info.h b/php_http_info.h index afd747c..4f02908 100644 --- a/php_http_info.h +++ b/php_http_info.h @@ -14,14 +14,17 @@ #define PHP_HTTP_INFO_H #include "php_http_version.h" +#include "php_http_url.h" -#define PHP_HTTP_INFO_REQUEST_FMT_ARGS(_http_ptr, eol) "%s %s HTTP/%u.%u" eol, \ +#define PHP_HTTP_INFO_REQUEST_FMT_ARGS(_http_ptr, tmp, eol) "%s %s HTTP/%u.%u" eol, \ (_http_ptr)->info.request.method?(_http_ptr)->info.request.method:"UNKNOWN", \ - (_http_ptr)->info.request.url?(_http_ptr)->info.request.url:"/", \ + (_http_ptr)->info.request.method&&!strcasecmp((_http_ptr)->info.request.method,"CONNECT")?( \ + (_http_ptr)->info.request.url?php_http_url_authority_to_string((_http_ptr)->info.request.url, &(tmp), NULL):"0"):( \ + (_http_ptr)->info.request.url?php_http_url_to_string((_http_ptr)->info.request.url, &(tmp), NULL, 0):"/"), \ (_http_ptr)->version.major||(_http_ptr)->version.major?(_http_ptr)->version.major:1, \ (_http_ptr)->version.major||(_http_ptr)->version.minor?(_http_ptr)->version.minor:1 -#define PHP_HTTP_INFO_RESPONSE_FMT_ARGS(_http_ptr, eol) "HTTP/%u.%u %d%s%s" eol, \ +#define PHP_HTTP_INFO_RESPONSE_FMT_ARGS(_http_ptr, tmp, eol) "HTTP/%u.%u %d%s%s" eol, \ (_http_ptr)->version.major||(_http_ptr)->version.major?(_http_ptr)->version.major:1, \ (_http_ptr)->version.major||(_http_ptr)->version.minor?(_http_ptr)->version.minor:1, \ (_http_ptr)->info.response.code?(_http_ptr)->info.response.code:200, \ @@ -31,7 +34,7 @@ typedef struct php_http_info_data { union { /* GET /foo/bar */ - struct { char *method; char *url; } request; + struct { char *method; php_http_url_t *url; } request; /* 200 Ok */ struct { unsigned code; char *status; } response; } info; diff --git a/php_http_message.c b/php_http_message.c index c965317..b7b500b 100644 --- a/php_http_message.c +++ b/php_http_message.c @@ -69,7 +69,7 @@ php_http_message_t *php_http_message_init_env(php_http_message_t *message, php_h message->http.info.request.method = estrdup(Z_STRVAL_P(sval)); } if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_URI"), 1 TSRMLS_CC))) { - message->http.info.request.url = estrdup(Z_STRVAL_P(sval)); + message->http.info.request.url = php_http_url_parse(Z_STRVAL_P(sval), Z_STRLEN_P(sval), ~0 TSRMLS_CC); } php_http_env_get_request_headers(&message->hdrs TSRMLS_CC); @@ -253,12 +253,12 @@ void php_http_message_set_type(php_http_message_t *message, php_http_message_typ /* free request info */ switch (message->type) { case PHP_HTTP_REQUEST: - STR_FREE(message->http.info.request.method); - STR_FREE(message->http.info.request.url); + PTR_FREE(message->http.info.request.method); + PTR_FREE(message->http.info.request.url); break; case PHP_HTTP_RESPONSE: - STR_FREE(message->http.info.response.status); + PTR_FREE(message->http.info.response.status); break; default: @@ -276,13 +276,13 @@ void php_http_message_set_info(php_http_message_t *message, php_http_info_t *inf message->http.version = info->http.version; switch (message->type) { case PHP_HTTP_REQUEST: - STR_SET(PHP_HTTP_INFO(message).request.url, PHP_HTTP_INFO(info).request.url ? estrdup(PHP_HTTP_INFO(info).request.url) : NULL); - STR_SET(PHP_HTTP_INFO(message).request.method, PHP_HTTP_INFO(info).request.method ? estrdup(PHP_HTTP_INFO(info).request.method) : NULL); + PTR_SET(PHP_HTTP_INFO(message).request.url, PHP_HTTP_INFO(info).request.url ? php_http_url_copy(PHP_HTTP_INFO(info).request.url, 0) : NULL); + PTR_SET(PHP_HTTP_INFO(message).request.method, PHP_HTTP_INFO(info).request.method ? estrdup(PHP_HTTP_INFO(info).request.method) : NULL); break; case PHP_HTTP_RESPONSE: PHP_HTTP_INFO(message).response.code = PHP_HTTP_INFO(info).response.code; - STR_SET(PHP_HTTP_INFO(message).response.status, PHP_HTTP_INFO(info).response.status ? estrdup(PHP_HTTP_INFO(info).response.status) : NULL); + PTR_SET(PHP_HTTP_INFO(message).response.status, PHP_HTTP_INFO(info).response.status ? estrdup(PHP_HTTP_INFO(info).response.status) : NULL); break; default: @@ -297,6 +297,9 @@ void php_http_message_update_headers(php_http_message_t *msg) if (php_http_message_body_stream(msg->body)->readfilters.head) { /* if a read stream filter is attached to the body the caller must also care for the headers */ + } else if ((h = php_http_message_header(msg, ZEND_STRL("Content-Range"), 0))) { + /* don't mess around with a Content-Range message */ + zval_ptr_dtor(&h); } else if ((size = php_http_message_body_size(msg->body))) { MAKE_STD_ZVAL(h); ZVAL_LONG(h, size); @@ -324,6 +327,7 @@ void php_http_message_update_headers(php_http_message_t *msg) zval_ptr_dtor(&h); if (Z_LVAL_P(h_cpy)) { + /* body->size == 0, so get rid of old Content-Length */ zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length")); } zval_ptr_dtor(&h_cpy); @@ -332,15 +336,18 @@ void php_http_message_update_headers(php_http_message_t *msg) static void message_headers(php_http_message_t *msg, php_http_buffer_t *str) { + char *tmp = NULL; TSRMLS_FETCH_FROM_CTX(msg->ts); switch (msg->type) { case PHP_HTTP_REQUEST: - php_http_buffer_appendf(str, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&msg->http, PHP_HTTP_CRLF)); + php_http_buffer_appendf(str, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&msg->http, tmp, PHP_HTTP_CRLF)); + PTR_FREE(tmp); break; case PHP_HTTP_RESPONSE: - php_http_buffer_appendf(str, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&msg->http, PHP_HTTP_CRLF)); + php_http_buffer_appendf(str, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&msg->http, tmp, PHP_HTTP_CRLF)); + PTR_FREE(tmp); break; default: @@ -496,12 +503,12 @@ void php_http_message_dtor(php_http_message_t *message) switch (message->type) { case PHP_HTTP_REQUEST: - STR_SET(message->http.info.request.method, NULL); - STR_SET(message->http.info.request.url, NULL); + PTR_SET(message->http.info.request.method, NULL); + PTR_SET(message->http.info.request.url, NULL); break; case PHP_HTTP_RESPONSE: - STR_SET(message->http.info.response.status, NULL); + PTR_SET(message->http.info.response.status, NULL); break; default: @@ -561,22 +568,23 @@ static void php_http_message_object_prophandler_get_request_method(php_http_mess static void php_http_message_object_prophandler_set_request_method(php_http_message_object_t *obj, zval *value TSRMLS_DC) { if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message)) { zval *cpy = php_http_ztyp(IS_STRING, value); - STR_SET(obj->message->http.info.request.method, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy))); + PTR_SET(obj->message->http.info.request.method, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy))); zval_ptr_dtor(&cpy); } } static void php_http_message_object_prophandler_get_request_url(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { - if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message) && obj->message->http.info.request.url) { - RETVAL_STRING(obj->message->http.info.request.url, 1); + char *url_str; + size_t url_len; + + if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message) && obj->message->http.info.request.url && php_http_url_to_string(obj->message->http.info.request.url, &url_str, &url_len, 0)) { + RETVAL_STRINGL(url_str, url_len, 0); } else { RETVAL_NULL(); } } static void php_http_message_object_prophandler_set_request_url(php_http_message_object_t *obj, zval *value TSRMLS_DC) { if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message)) { - zval *cpy = php_http_ztyp(IS_STRING, value); - STR_SET(obj->message->http.info.request.url, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy))); - zval_ptr_dtor(&cpy); + PTR_SET(obj->message->http.info.request.url, php_http_url_from_zval(value, ~0 TSRMLS_CC)); } } static void php_http_message_object_prophandler_get_response_status(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { @@ -589,7 +597,7 @@ static void php_http_message_object_prophandler_get_response_status(php_http_mes static void php_http_message_object_prophandler_set_response_status(php_http_message_object_t *obj, zval *value TSRMLS_DC) { if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) { zval *cpy = php_http_ztyp(IS_STRING, value); - STR_SET(obj->message->http.info.response.status, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy))); + PTR_SET(obj->message->http.info.response.status, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy))); zval_ptr_dtor(&cpy); } } @@ -604,7 +612,7 @@ static void php_http_message_object_prophandler_set_response_code(php_http_messa if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) { zval *cpy = php_http_ztyp(IS_LONG, value); obj->message->http.info.response.code = Z_LVAL_P(cpy); - STR_SET(obj->message->http.info.response.status, estrdup(php_http_env_get_response_status_for_code(obj->message->http.info.response.code))); + PTR_SET(obj->message->http.info.response.status, estrdup(php_http_env_get_response_status_for_code(obj->message->http.info.response.code))); zval_ptr_dtor(&cpy); } } @@ -939,8 +947,8 @@ static HashTable *php_http_message_object_get_props(zval *object TSRMLS_DC) php_http_message_object_t *obj = zend_object_store_get_object(object TSRMLS_CC); HashTable *props = zend_get_std_object_handlers()->get_properties(object TSRMLS_CC); zval array, *parent, *body; - char *version; - int verlen; + char *ver_str, *url_str = NULL; + size_t ver_len, url_len = 0; PHP_HTTP_MESSAGE_OBJECT_INIT(obj); INIT_PZVAL_ARRAY(&array, props); @@ -964,15 +972,21 @@ static HashTable *php_http_message_object_get_props(zval *object TSRMLS_DC) } while(0) ASSOC_PROP(long, "type", obj->message->type); - verlen = spprintf(&version, 0, "%u.%u", obj->message->http.version.major, obj->message->http.version.minor); - ASSOC_STRINGL_EX("httpVersion", version, verlen, 0); + ver_len = spprintf(&ver_str, 0, "%u.%u", obj->message->http.version.major, obj->message->http.version.minor); + ASSOC_STRINGL_EX("httpVersion", ver_str, ver_len, 0); switch (obj->message->type) { case PHP_HTTP_REQUEST: ASSOC_PROP(long, "responseCode", 0); ASSOC_STRINGL("responseStatus", "", 0); ASSOC_STRING("requestMethod", STR_PTR(obj->message->http.info.request.method)); - ASSOC_STRING("requestUrl", STR_PTR(obj->message->http.info.request.url)); + if (obj->message->http.info.request.url) { + php_http_url_to_string(obj->message->http.info.request.url, &url_str, &url_len, 0); + ASSOC_STRINGL_EX("requestUrl", url_str, url_len, 0); + } else { + ASSOC_STRINGL("requestUrl", "", 0); + } + break; case PHP_HTTP_RESPONSE: @@ -1151,17 +1165,19 @@ static PHP_METHOD(HttpMessage, getHeader) if (!header_ce) { RETURN_ZVAL(header, 1, 1); } else if (instanceof_function(header_ce, php_http_header_class_entry TSRMLS_CC)) { + php_http_object_method_t cb; zval *header_name, **argv[2]; MAKE_STD_ZVAL(header_name); ZVAL_STRINGL(header_name, header_str, header_len, 1); - Z_ADDREF_P(header); argv[0] = &header_name; argv[1] = &header; object_init_ex(return_value, header_ce); - php_http_method_call(return_value, ZEND_STRL("__construct"), 2, argv, NULL TSRMLS_CC); + php_http_object_method_init(&cb, return_value, ZEND_STRL("__construct") TSRMLS_CC); + php_http_object_method_call(&cb, return_value, NULL, 2, argv TSRMLS_CC); + php_http_object_method_dtor(&cb); zval_ptr_dtor(&header_name); zval_ptr_dtor(&header); @@ -1320,16 +1336,19 @@ ZEND_END_ARG_INFO(); static PHP_METHOD(HttpMessage, getInfo) { if (SUCCESS == zend_parse_parameters_none()) { + char *tmp = NULL; php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); PHP_HTTP_MESSAGE_OBJECT_INIT(obj); switch (obj->message->type) { case PHP_HTTP_REQUEST: - Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&obj->message->http, "")); + Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&obj->message->http, tmp, "")); + PTR_FREE(tmp); break; case PHP_HTTP_RESPONSE: - Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&obj->message->http, "")); + Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&obj->message->http, tmp, "")); + PTR_FREE(tmp); break; default: RETURN_NULL(); @@ -1449,7 +1468,7 @@ static PHP_METHOD(HttpMessage, setResponseCode) } obj->message->http.info.response.code = code; - STR_SET(obj->message->http.info.response.status, estrdup(php_http_env_get_response_status_for_code(code))); + PTR_SET(obj->message->http.info.response.status, estrdup(php_http_env_get_response_status_for_code(code))); RETVAL_ZVAL(getThis(), 1, 0); } @@ -1494,7 +1513,7 @@ static PHP_METHOD(HttpMessage, setResponseStatus) php_http_throw(bad_method_call, "http\\Message is not of type response", NULL); } - STR_SET(obj->message->http.info.response.status, estrndup(status, status_len)); + PTR_SET(obj->message->http.info.response.status, estrndup(status, status_len)); RETVAL_ZVAL(getThis(), 1, 0); } @@ -1545,7 +1564,7 @@ static PHP_METHOD(HttpMessage, setRequestMethod) return; } - STR_SET(obj->message->http.info.request.method, estrndup(method, method_len)); + PTR_SET(obj->message->http.info.request.method, estrndup(method, method_len)); RETVAL_ZVAL(getThis(), 1, 0); } @@ -1564,7 +1583,11 @@ static PHP_METHOD(HttpMessage, getRequestUrl) } if (obj->message->http.info.request.url) { - RETURN_STRING(obj->message->http.info.request.url, 1); + char *url_str; + size_t url_len; + + php_http_url_to_string(obj->message->http.info.request.url, &url_str, &url_len, 0); + RETURN_STRINGL(url_str, url_len, 0); } else { RETURN_EMPTY_STRING(); } @@ -1576,11 +1599,12 @@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpMessage_setRequestUrl, 0, 0, 1) ZEND_END_ARG_INFO(); static PHP_METHOD(HttpMessage, setRequestUrl) { - char *url_str; - int url_len; + zval *zurl; + php_http_url_t *url; php_http_message_object_t *obj; + zend_error_handling zeh; - php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &url_str, &url_len), invalid_arg, return); + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zurl), invalid_arg, return); obj = zend_object_store_get_object(getThis() TSRMLS_CC); @@ -1591,12 +1615,17 @@ static PHP_METHOD(HttpMessage, setRequestUrl) return; } - if (url_len < 1) { + zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh TSRMLS_CC); + url = php_http_url_from_zval(zurl, ~0 TSRMLS_CC); + zend_restore_error_handling(&zeh TSRMLS_CC); + + if (php_http_url_is_empty(url)) { + php_http_url_free(&url); php_http_throw(invalid_arg, "Cannot set http\\Message's request url to an empty string", NULL); - return; + } else { + PTR_SET(obj->message->http.info.request.url, url); } - STR_SET(obj->message->http.info.request.url, estrndup(url_str, url_len)); RETVAL_ZVAL(getThis(), 1, 0); } @@ -1648,6 +1677,16 @@ static PHP_METHOD(HttpMessage, toString) RETURN_EMPTY_STRING(); } +#ifdef ZTS +static size_t write_to_stream(void *s, const char *str, size_t len) +{ + TSRMLS_FETCH(); + return php_stream_write(s, str, len); +} +#else +# define write_to_stream (php_http_pass_callback_t)_php_stream_write +#endif + ZEND_BEGIN_ARG_INFO_EX(ai_HttpMessage_toStream, 0, 0, 1) ZEND_ARG_INFO(0, stream) ZEND_END_ARG_INFO(); @@ -1662,7 +1701,7 @@ static PHP_METHOD(HttpMessage, toStream) PHP_HTTP_MESSAGE_OBJECT_INIT(obj); php_stream_from_zval(s, &zstream); - php_http_message_to_callback(obj->message, (php_http_pass_callback_t) _php_stream_write, s); + php_http_message_to_callback(obj->message, write_to_stream, s); } } @@ -1831,7 +1870,7 @@ static PHP_METHOD(HttpMessage, splitMultipartBody) php_http_expect(msg = php_http_message_body_split(obj->message->body, boundary), bad_message, return); - STR_FREE(boundary); + PTR_FREE(boundary); RETURN_OBJVAL(php_http_message_object_new_ex(php_http_message_class_entry, msg, NULL TSRMLS_CC), 0); } diff --git a/php_http_message_body.c b/php_http_message_body.c index 4be45ef..fa91551 100644 --- a/php_http_message_body.c +++ b/php_http_message_body.c @@ -100,7 +100,7 @@ void php_http_message_body_free(php_http_message_body_t **body_ptr) TSRMLS_FETCH_FROM_CTX(body->ts); /* NOFIXME: shows leakinfo in DEBUG mode */ zend_list_delete(body->stream_id); - STR_FREE(body->boundary); + PTR_FREE(body->boundary); efree(body); } *body_ptr = NULL; @@ -128,25 +128,29 @@ const char *php_http_message_body_boundary(php_http_message_body_t *body) char *php_http_message_body_etag(php_http_message_body_t *body) { - const php_stream_statbuf *ssb = php_http_message_body_stat(body); + php_http_etag_t *etag; + php_stream *s = php_http_message_body_stream(body); TSRMLS_FETCH_FROM_CTX(body->ts); /* real file or temp buffer ? */ - if (ssb && ssb->sb.st_mtime) { - char *etag; + if (s->ops != &php_stream_temp_ops && s->ops != &php_stream_memory_ops) { + php_stream_stat(php_http_message_body_stream(body), &body->ssb); - spprintf(&etag, 0, "%lx-%lx-%lx", ssb->sb.st_ino, ssb->sb.st_mtime, ssb->sb.st_size); - return etag; - } else { - php_http_etag_t *etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode TSRMLS_CC); + if (body->ssb.sb.st_mtime) { + char *etag; - if (etag) { - php_http_message_body_to_callback(body, (php_http_pass_callback_t) php_http_etag_update, etag, 0, 0); - return php_http_etag_finish(etag); - } else { - return NULL; + spprintf(&etag, 0, "%lx-%lx-%lx", body->ssb.sb.st_ino, body->ssb.sb.st_mtime, body->ssb.sb.st_size); + return etag; } } + + /* content based */ + if ((etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode TSRMLS_CC))) { + php_http_message_body_to_callback(body, (php_http_pass_callback_t) php_http_etag_update, etag, 0, 0); + return php_http_etag_finish(etag); + } + + return NULL; } void php_http_message_body_to_string(php_http_message_body_t *body, char **buf, size_t *len, off_t offset, size_t forlen) @@ -557,7 +561,7 @@ php_http_message_t *php_http_message_body_split(php_http_message_body_t *body, c php_http_buffer_free(&tmp); php_http_message_parser_free(&arg.parser); php_http_buffer_dtor(&arg.buf); - STR_FREE(arg.boundary_str); + PTR_FREE(arg.boundary_str); return msg; } diff --git a/php_http_message_parser.c b/php_http_message_parser.c index e73360e..3b55efb 100644 --- a/php_http_message_parser.c +++ b/php_http_message_parser.c @@ -23,19 +23,20 @@ typedef struct php_http_message_parser_state_spec { static const php_http_message_parser_state_spec_t php_http_message_parser_states[] = { {PHP_HTTP_MESSAGE_PARSER_STATE_START, 1}, - {PHP_HTTP_MESSAGE_PARSER_STATE_HEADER, 1}, + {PHP_HTTP_MESSAGE_PARSER_STATE_HEADER, 0}, {PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE, 0}, {PHP_HTTP_MESSAGE_PARSER_STATE_BODY, 0}, {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB, 1}, {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH, 1}, {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED, 1}, {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE, 0}, + {PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL, 0}, {PHP_HTTP_MESSAGE_PARSER_STATE_DONE, 0} }; #if DBG_PARSER const char *php_http_message_parser_state_name(php_http_message_parser_state_t state) { - const char *states[] = {"START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "DONE"}; + const char *states[] = {"START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "UPDATE_CL", "DONE"}; if (state < 0 || state > (sizeof(states)/sizeof(char*))-1) { return "FAILURE"; @@ -123,17 +124,30 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me if (!buf->data) { php_http_buffer_resize_ex(buf, 0x1000, 1, 0); } - while (!php_stream_eof(s)) { + while (1) { size_t justread = 0; #if DBG_PARSER fprintf(stderr, "#SP: %s (f:%u)\n", php_http_message_parser_state_name(state), flags); #endif + /* resize if needed */ + if (buf->free < 0x1000) { + php_http_buffer_resize_ex(buf, 0x1000, 1, 0); + } switch (state) { case PHP_HTTP_MESSAGE_PARSER_STATE_START: case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER: case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE: /* read line */ php_stream_get_line(s, buf->data + buf->used, buf->free, &justread); + /* if we fail reading a whole line, try a single char */ + if (!justread) { + int c = php_stream_getc(s); + + if (c != EOF) { + char s[1] = {c}; + justread = php_http_buffer_append(buf, s, 1); + } + } php_http_buffer_account(buf, justread); break; @@ -168,6 +182,7 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me case PHP_HTTP_MESSAGE_PARSER_STATE_BODY: case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE: + case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL: /* should not occur */ abort(); break; @@ -179,7 +194,9 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me if (justread) { state = php_http_message_parser_parse(parser, buf, flags, message); - } else { + } else if (php_stream_eof(s)) { + return php_http_message_parser_parse(parser, buf, flags | PHP_HTTP_MESSAGE_PARSER_CLEANUP, message); + } else { return state; } } @@ -240,9 +257,10 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p break; default: - php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER); - if (buffer->used) { - return PHP_HTTP_MESSAGE_PARSER_STATE_HEADER; + if (buffer->used || !(flags & PHP_HTTP_MESSAGE_PARSER_CLEANUP)) { + return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER); + } else { + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE); } } break; @@ -252,23 +270,32 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p { zval *h, *h_loc = NULL, *h_con = NULL, **h_cl = NULL, **h_cr = NULL, **h_te = NULL; + /* Content-Range has higher precedence than Content-Length, + * and content-length denotes the original length of the entity, + * so let's *NOT* remove CR/CL, because that would fundamentally + * change the meaning of the whole message + */ if ((h = php_http_message_header(*message, ZEND_STRL("Transfer-Encoding"), 1))) { - zend_hash_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), &h, sizeof(zval *), (void *) &h_te); + zend_hash_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &h, sizeof(zval *), (void *) &h_te); zend_hash_del(&(*message)->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding")); + + /* reset */ + MAKE_STD_ZVAL(h); + ZVAL_LONG(h, 0); + zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &h, sizeof(zval *), NULL); + } else if ((h = php_http_message_header(*message, ZEND_STRL("Content-Length"), 1))) { + zend_hash_update(&(*message)->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), (void *) &h, sizeof(zval *), (void *) &h_cl); } - if ((h = php_http_message_header(*message, ZEND_STRL("Content-Length"), 1))) { - zend_hash_update(&(*message)->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), &h, sizeof(zval *), (void *) &h_cl); - } + if ((h = php_http_message_header(*message, ZEND_STRL("Content-Range"), 1))) { - zend_hash_update(&(*message)->hdrs, "X-Original-Content-Range", sizeof("X-Original-Content-Range"), &h, sizeof(zval *), (void *) &h_cr); - zend_hash_del(&(*message)->hdrs, "Content-Range", sizeof("Content-Range")); + zend_hash_find(&(*message)->hdrs, ZEND_STRS("Content-Range"), (void *) &h_cr); + if (h != *h_cr) { + zend_hash_update(&(*message)->hdrs, "Content-Range", sizeof("Content-Range"), &h, sizeof(zval *), (void *) &h_cr); + } else { + zval_ptr_dtor(&h); + } } - /* default */ - MAKE_STD_ZVAL(h); - ZVAL_LONG(h, 0); - zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &h, sizeof(zval *), NULL); - /* so, if curl sees a 3xx code, a Location header and a Connection:close header * it decides not to read the response body. */ @@ -320,30 +347,13 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p } } - if (h_cl) { - char *stop; - - if (Z_TYPE_PP(h_cl) == IS_STRING) { - parser->body_length = strtoul(Z_STRVAL_PP(h_cl), &stop, 10); - - if (stop != Z_STRVAL_PP(h_cl)) { - php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH); - break; - } - } else if (Z_TYPE_PP(h_cl) == IS_LONG) { - parser->body_length = Z_LVAL_PP(h_cl); - php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH); - break; - } - } - if (h_cr) { ulong total = 0, start = 0, end = 0; if (!strncasecmp(Z_STRVAL_PP(h_cr), "bytes", lenof("bytes")) - && ( Z_STRVAL_P(h)[lenof("bytes")] == ':' - || Z_STRVAL_P(h)[lenof("bytes")] == ' ' - || Z_STRVAL_P(h)[lenof("bytes")] == '=' + && ( Z_STRVAL_PP(h_cr)[lenof("bytes")] == ':' + || Z_STRVAL_PP(h_cr)[lenof("bytes")] == ' ' + || Z_STRVAL_PP(h_cr)[lenof("bytes")] == '=' ) ) { char *total_at = NULL, *end_at = NULL; @@ -356,7 +366,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p total = strtoul(total_at + 1, NULL, 10); } - if (end >= start && (!total || end < total)) { + if (end >= start && (!total || end <= total)) { parser->body_length = end + 1 - start; php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH); break; @@ -365,6 +375,22 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p } } + if (h_cl) { + char *stop; + + if (Z_TYPE_PP(h_cl) == IS_STRING) { + parser->body_length = strtoul(Z_STRVAL_PP(h_cl), &stop, 10); + + if (stop != Z_STRVAL_PP(h_cl)) { + php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH); + break; + } + } else if (Z_TYPE_PP(h_cl) == IS_LONG) { + parser->body_length = Z_LVAL_PP(h_cl); + php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH); + break; + } + } if ((*message)->type == PHP_HTTP_REQUEST) { php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE); @@ -378,8 +404,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p case PHP_HTTP_MESSAGE_PARSER_STATE_BODY: { if (len) { - zval *zcl; - + /* FIXME: what if we re-use the parser? */ if (parser->inflate) { char *dec_str = NULL; size_t dec_len; @@ -389,7 +414,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p } if (str != buffer->data) { - STR_FREE(str); + PTR_FREE(str); } str = dec_str; len = dec_len; @@ -397,10 +422,6 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p php_stream_write(php_http_message_body_stream((*message)->body), str, len); - /* keep track */ - MAKE_STD_ZVAL(zcl); - ZVAL_LONG(zcl, php_http_message_body_size((*message)->body)); - zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &zcl, sizeof(zval *), NULL); } if (cut) { @@ -408,7 +429,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p } if (str != buffer->data) { - STR_FREE(str); + PTR_FREE(str); } str = NULL; @@ -473,7 +494,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p { php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE); - if (parser->dechunk) { + if (parser->dechunk && parser->dechunk->ctx) { char *dec_str = NULL; size_t dec_len; @@ -486,14 +507,24 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p str = dec_str; len = dec_len; cut = 0; - php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); + php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); } } break; } - case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: { + case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL: + { + zval *zcl; + MAKE_STD_ZVAL(zcl); + ZVAL_LONG(zcl, php_http_message_body_size((*message)->body)); + zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &zcl, sizeof(zval *), NULL); + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: + { char *ptr = buffer->data; while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) { @@ -655,6 +686,7 @@ PHP_MINIT_FUNCTION(http_message_parser) zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_LENGTH"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH TSRMLS_CC); zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_CHUNKED"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED TSRMLS_CC); zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE TSRMLS_CC); + zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_UPDATE_CL"), PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL TSRMLS_CC); zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_DONE TSRMLS_CC); return SUCCESS; diff --git a/php_http_message_parser.h b/php_http_message_parser.h index c2bee15..0bac9da 100644 --- a/php_http_message_parser.h +++ b/php_http_message_parser.h @@ -27,6 +27,7 @@ typedef enum php_http_message_parser_state { PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE, + PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL, PHP_HTTP_MESSAGE_PARSER_STATE_DONE } php_http_message_parser_state_t; diff --git a/php_http_misc.c b/php_http_misc.c index 51072cf..8e2227d 100644 --- a/php_http_misc.c +++ b/php_http_misc.c @@ -80,8 +80,8 @@ int php_http_match(const char *haystack_str, const char *needle_str, int flags) } } - STR_FREE(haystack); - STR_FREE(needle); + PTR_FREE(haystack); + PTR_FREE(needle); } return result; @@ -188,8 +188,10 @@ int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_lis if ((flags & ARRAY_JOIN_PRETTIFY) && hash_key->nKeyLength) { key = php_http_pretty_key(estrndup(hash_key->arKey, hash_key->nKeyLength - 1), hash_key->nKeyLength - 1, 1, 1); zend_hash_find(dst, key, hash_key->nKeyLength, (void *) &data); - } else { + } else if (hash_key->nKeyLength) { zend_hash_quick_find(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &data); + } else { + zend_hash_index_find(dst, hash_key->h, (void *) &data); } if (flags & ARRAY_JOIN_STRINGIFY) { @@ -205,8 +207,10 @@ int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_lis add_next_index_zval(*data, value); } else if (key) { zend_symtable_update(dst, key, hash_key->nKeyLength, &value, sizeof(zval *), NULL); - } else { + } else if (hash_key->nKeyLength) { zend_hash_quick_add(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, &value, sizeof(zval *), NULL); + } else { + zend_hash_index_update(dst, hash_key->h, (void *) &value, sizeof(zval *), NULL); } if (key) { @@ -238,8 +242,10 @@ int php_http_array_apply_merge_func(void *pDest TSRMLS_DC, int num_args, va_list key = php_http_pretty_key(estrndup(hash_key->arKey, hash_key->nKeyLength - 1), hash_key->nKeyLength - 1, 1, 1); zend_hash_update(dst, key, hash_key->nKeyLength, (void *) &value, sizeof(zval *), NULL); efree(key); - } else { + } else if (hash_key->nKeyLength) { zend_hash_quick_update(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &value, sizeof(zval *), NULL); + } else { + zend_hash_index_update(dst, hash_key->h, (void *) &value, sizeof(zval *), NULL); } } diff --git a/php_http_misc.h b/php_http_misc.h index 3bd77d8..cf00f24 100644 --- a/php_http_misc.h +++ b/php_http_misc.h @@ -27,6 +27,9 @@ /* send buffer size */ #define PHP_HTTP_SENDBUF_SIZE 40960 +/* allowed characters of header field names */ +#define PHP_HTTP_HEADER_NAME_CHARS "!#$%&'*+-.^_`|~1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" + /* SLEEP */ #define PHP_HTTP_DIFFSEC (0.001) @@ -41,10 +44,10 @@ PHP_HTTP_API void php_http_sleep(double s); /* STRING UTILITIES */ -#ifndef STR_SET -# define STR_SET(STR, SET) \ +#ifndef PTR_SET +# define PTR_SET(STR, SET) \ { \ - STR_FREE(STR); \ + PTR_FREE(STR); \ STR = SET; \ } #endif @@ -291,7 +294,7 @@ static inline void php_http_array_hashkey_stringify(php_http_array_hashkey_t *ke static inline void php_http_array_hashkey_stringfree(php_http_array_hashkey_t *key) { if (key->type != HASH_KEY_IS_STRING || key->dup) { - STR_FREE(key->str); + PTR_FREE(key->str); } } diff --git a/php_http_negotiate.c b/php_http_negotiate.c index 76106a8..a74875b 100644 --- a/php_http_negotiate.c +++ b/php_http_negotiate.c @@ -141,7 +141,7 @@ HashTable *php_http_negotiate(const char *value_str, size_t value_len, HashTable add_index_double(&arr, key.num, q); } - STR_FREE(key.str); + PTR_FREE(key.str); } #if 0 diff --git a/php_http_negotiate.h b/php_http_negotiate.h index 3a12ab7..f7405b5 100644 --- a/php_http_negotiate.h +++ b/php_http_negotiate.h @@ -24,7 +24,7 @@ static inline HashTable *php_http_negotiate_language(HashTable *supported, php_h if (value) { result = php_http_negotiate(value, length, supported, "-", 1 TSRMLS_CC); } - STR_FREE(value); + PTR_FREE(value); return result; } @@ -38,7 +38,7 @@ static inline HashTable *php_http_negotiate_encoding(HashTable *supported, php_h if (value) { result = php_http_negotiate(value, length, supported, NULL, 0 TSRMLS_CC); } - STR_FREE(value); + PTR_FREE(value); return result; } @@ -52,7 +52,7 @@ static inline HashTable *php_http_negotiate_charset(HashTable *supported, php_ht if (value) { result = php_http_negotiate(value, length, supported, NULL, 0 TSRMLS_CC); } - STR_FREE(value); + PTR_FREE(value); return result; } @@ -66,7 +66,7 @@ static inline HashTable *php_http_negotiate_content_type(HashTable *supported, p if (value) { result = php_http_negotiate(value, length, supported, "/", 1 TSRMLS_CC); } - STR_FREE(value); + PTR_FREE(value); return result; } diff --git a/php_http_object.c b/php_http_object.c index abf7229..ba6d28c 100644 --- a/php_http_object.c +++ b/php_http_object.c @@ -53,30 +53,80 @@ STATUS php_http_new(zend_object_value *ovp, zend_class_entry *ce, php_http_new_t return SUCCESS; } -STATUS php_http_method_call(zval *object, const char *method_str, size_t method_len, int argc, zval **argv[], zval **retval_ptr TSRMLS_DC) +static inline zend_function *get_object_method(zval *zobject, zval *zmeth TSRMLS_DC) +{ +#if PHP_VERSION_ID >= 50400 + return Z_OBJ_HT_P(zobject)->get_method(&zobject, Z_STRVAL_P(zmeth), Z_STRLEN_P(zmeth), NULL TSRMLS_CC); +#else + return Z_OBJ_HT_P(zobject)->get_method(&zobject, Z_STRVAL_P(zmeth), Z_STRLEN_P(zmeth) TSRMLS_CC); +#endif +} + +php_http_object_method_t *php_http_object_method_init(php_http_object_method_t *cb, zval *zobject, const char *method_str, size_t method_len TSRMLS_DC) +{ + zval *zfn; + + if (!cb) { + cb = ecalloc(1, sizeof(*cb)); + } else { + memset(cb, 0, sizeof(*cb)); + } + + MAKE_STD_ZVAL(zfn); + ZVAL_STRINGL(zfn, method_str, method_len, 1); + + cb->fci.size = sizeof(cb->fci); + cb->fci.function_name = zfn; + cb->fcc.initialized = 1; + cb->fcc.calling_scope = cb->fcc.called_scope = Z_OBJCE_P(zobject); + cb->fcc.function_handler = get_object_method(zobject, cb->fci.function_name TSRMLS_CC); + + return cb; +} + +void php_http_object_method_dtor(php_http_object_method_t *cb) +{ + if (cb->fci.function_name) { + zval_ptr_dtor(&cb->fci.function_name); + cb->fci.function_name = NULL; + } +} + +void php_http_object_method_free(php_http_object_method_t **cb) +{ + if (*cb) { + php_http_object_method_dtor(*cb); + efree(*cb); + *cb = NULL; + } +} + +STATUS php_http_object_method_call(php_http_object_method_t *cb, zval *zobject, zval **retval_ptr, int argc, zval ***args TSRMLS_DC) { - zend_fcall_info fci; - zval zmethod; - zval *retval; STATUS rv; + zval *retval = NULL; + + Z_ADDREF_P(zobject); + cb->fci.object_ptr = zobject; + cb->fcc.object_ptr = zobject; + + cb->fci.retval_ptr_ptr = retval_ptr ? retval_ptr : &retval; - fci.size = sizeof(fci); - fci.object_ptr = object; - fci.function_name = &zmethod; - fci.retval_ptr_ptr = retval_ptr ? retval_ptr : &retval; - fci.param_count = argc; - fci.params = argv; - fci.no_separation = 1; - fci.symbol_table = NULL; - fci.function_table = NULL; + cb->fci.param_count = argc; + cb->fci.params = args; - INIT_PZVAL(&zmethod); - ZVAL_STRINGL(&zmethod, method_str, method_len, 0); - rv = zend_call_function(&fci, NULL TSRMLS_CC); + if (cb->fcc.called_scope != Z_OBJCE_P(zobject)) { + cb->fcc.called_scope = Z_OBJCE_P(zobject); + cb->fcc.function_handler = get_object_method(zobject, cb->fci.function_name TSRMLS_CC); + } + + rv = zend_call_function(&cb->fci, &cb->fcc TSRMLS_CC); + zval_ptr_dtor(&zobject); if (!retval_ptr && retval) { zval_ptr_dtor(&retval); } + return rv; } diff --git a/php_http_object.h b/php_http_object.h index 63730b4..1fbd817 100644 --- a/php_http_object.h +++ b/php_http_object.h @@ -24,7 +24,16 @@ zend_object_value php_http_object_new_ex(zend_class_entry *ce, void *nothing, ph typedef zend_object_value (*php_http_new_t)(zend_class_entry *ce, void *, void ** TSRMLS_DC); STATUS php_http_new(zend_object_value *ov, zend_class_entry *ce, php_http_new_t create, zend_class_entry *parent_ce, void *intern_ptr, void **obj_ptr TSRMLS_DC); -STATUS php_http_method_call(zval *object, const char *method_str, size_t method_len, int argc, zval **argv[], zval **retval_ptr TSRMLS_DC); + +typedef struct php_http_method { + zend_fcall_info fci; + zend_fcall_info_cache fcc; +} php_http_object_method_t; + +php_http_object_method_t *php_http_object_method_init(php_http_object_method_t *cb, zval *zobject, const char *method_str, size_t method_len TSRMLS_DC); +STATUS php_http_object_method_call(php_http_object_method_t *cb, zval *zobject, zval **retval, int argc, zval ***args TSRMLS_DC); +void php_http_object_method_dtor(php_http_object_method_t *cb); +void php_http_object_method_free(php_http_object_method_t **cb); #endif diff --git a/php_http_params.c b/php_http_params.c index 5234244..ce785ec 100644 --- a/php_http_params.c +++ b/php_http_params.c @@ -347,7 +347,7 @@ static inline void sanitize_value(unsigned flags, char *str, size_t len, zval *z ZVAL_COPY_VALUE(tmp, zv); array_init(zv); add_assoc_zval(zv, language, tmp); - STR_FREE(language); + PTR_FREE(language); } } @@ -885,7 +885,7 @@ void php_http_params_separator_free(php_http_params_token_t **separator) php_http_params_token_t **sep = separator; if (sep) { while (*sep) { - STR_FREE((*sep)->str); + PTR_FREE((*sep)->str); efree(*sep); ++sep; } diff --git a/php_http_response_codes.h b/php_http_response_codes.h new file mode 100644 index 0000000..82157f9 --- /dev/null +++ b/php_http_response_codes.h @@ -0,0 +1,83 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2015, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +#ifndef PHP_HTTP_RESPONSE_CODE +# define PHP_HTTP_RESPONSE_CODE(code, status) +#endif + +PHP_HTTP_RESPONSE_CODE(100, "Continue") +PHP_HTTP_RESPONSE_CODE(101, "Switching Protocols") +PHP_HTTP_RESPONSE_CODE(102, "Processing") +PHP_HTTP_RESPONSE_CODE(200, "OK") +PHP_HTTP_RESPONSE_CODE(201, "Created") +PHP_HTTP_RESPONSE_CODE(202, "Accepted") +PHP_HTTP_RESPONSE_CODE(203, "Non-Authoritative Information") +PHP_HTTP_RESPONSE_CODE(204, "No Content") +PHP_HTTP_RESPONSE_CODE(205, "Reset Content") +PHP_HTTP_RESPONSE_CODE(206, "Partial Content") +PHP_HTTP_RESPONSE_CODE(207, "Multi-Status") +PHP_HTTP_RESPONSE_CODE(208, "Already Reported") +PHP_HTTP_RESPONSE_CODE(226, "IM Used") +PHP_HTTP_RESPONSE_CODE(300, "Multiple Choices") +PHP_HTTP_RESPONSE_CODE(301, "Moved Permanently") +PHP_HTTP_RESPONSE_CODE(302, "Found") +PHP_HTTP_RESPONSE_CODE(303, "See Other") +PHP_HTTP_RESPONSE_CODE(304, "Not Modified") +PHP_HTTP_RESPONSE_CODE(305, "Use Proxy") +PHP_HTTP_RESPONSE_CODE(307, "Temporary Redirect") +PHP_HTTP_RESPONSE_CODE(308, "Permanent Redirect") +PHP_HTTP_RESPONSE_CODE(400, "Bad Request") +PHP_HTTP_RESPONSE_CODE(401, "Unauthorized") +PHP_HTTP_RESPONSE_CODE(402, "Payment Required") +PHP_HTTP_RESPONSE_CODE(403, "Forbidden") +PHP_HTTP_RESPONSE_CODE(404, "Not Found") +PHP_HTTP_RESPONSE_CODE(405, "Method Not Allowed") +PHP_HTTP_RESPONSE_CODE(406, "Not Acceptable") +PHP_HTTP_RESPONSE_CODE(407, "Proxy Authentication Required") +PHP_HTTP_RESPONSE_CODE(408, "Request Timeout") +PHP_HTTP_RESPONSE_CODE(409, "Conflict") +PHP_HTTP_RESPONSE_CODE(410, "Gone") +PHP_HTTP_RESPONSE_CODE(411, "Length Required") +PHP_HTTP_RESPONSE_CODE(412, "Precondition Failed") +PHP_HTTP_RESPONSE_CODE(413, "Request Entity Too Large") +PHP_HTTP_RESPONSE_CODE(414, "Request URI Too Long") +PHP_HTTP_RESPONSE_CODE(415, "Unsupported Media Type") +PHP_HTTP_RESPONSE_CODE(416, "Requested Range Not Satisfiable") +PHP_HTTP_RESPONSE_CODE(417, "Expectation Failed") +PHP_HTTP_RESPONSE_CODE(422, "Unprocessible Entity") +PHP_HTTP_RESPONSE_CODE(423, "Locked") +PHP_HTTP_RESPONSE_CODE(424, "Failed Dependency") +PHP_HTTP_RESPONSE_CODE(426, "Upgrade Required") +PHP_HTTP_RESPONSE_CODE(428, "Precondition Required") +PHP_HTTP_RESPONSE_CODE(429, "Too Many Requests") +PHP_HTTP_RESPONSE_CODE(431, "Request Header Fields Too Large") +PHP_HTTP_RESPONSE_CODE(500, "Internal Server Error") +PHP_HTTP_RESPONSE_CODE(501, "Not Implemented") +PHP_HTTP_RESPONSE_CODE(502, "Bad Gateway") +PHP_HTTP_RESPONSE_CODE(503, "Service Unavailable") +PHP_HTTP_RESPONSE_CODE(504, "Gateway Timeout") +PHP_HTTP_RESPONSE_CODE(505, "HTTP Version Not Supported") +PHP_HTTP_RESPONSE_CODE(506, "Variant Also Negotiates") +PHP_HTTP_RESPONSE_CODE(507, "Insufficient Storage") +PHP_HTTP_RESPONSE_CODE(508, "Loop Detected") +PHP_HTTP_RESPONSE_CODE(510, "Not Extended") +PHP_HTTP_RESPONSE_CODE(511, "Network Authentication Required") + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/php_http_strlist.c b/php_http_strlist.c deleted file mode 100644 index f457178..0000000 --- a/php_http_strlist.c +++ /dev/null @@ -1,109 +0,0 @@ -/* - +--------------------------------------------------------------------+ - | PECL :: http | - +--------------------------------------------------------------------+ - | Redistribution and use in source and binary forms, with or without | - | modification, are permitted provided that the conditions mentioned | - | in the accompanying LICENSE file are met. | - +--------------------------------------------------------------------+ - | Copyright (c) 2004-2014, Michael Wallner | - +--------------------------------------------------------------------+ -*/ - -#include "php_http_api.h" - -php_http_strlist_iterator_t *php_http_strlist_iterator_init(php_http_strlist_iterator_t *iter, const char list[], unsigned factor) -{ - if (!iter) { - iter = emalloc(sizeof(*iter)); - } - memset(iter, 0, sizeof(*iter)); - - iter->p = &list[0]; - iter->factor = factor; - - return iter; -} - -const char *php_http_strlist_iterator_this(php_http_strlist_iterator_t *iter, unsigned *id) -{ - if (id) { - *id = (iter->major + 1) * iter->factor + iter->minor; - } - - return iter->p; -} - -const char *php_http_strlist_iterator_next(php_http_strlist_iterator_t *iter) -{ - if (*iter->p) { - while (*iter->p) { - ++iter->p; - } - ++iter->p; - ++iter->minor; - - if (!*iter->p) { - ++iter->p; - ++iter->major; - iter->minor = 0; - } - } - - return iter->p; -} - -void php_http_strlist_iterator_dtor(php_http_strlist_iterator_t *iter) -{ - -} - -void php_http_strlist_iterator_free(php_http_strlist_iterator_t **iter) -{ - if (*iter) { - efree(*iter); - *iter = NULL; - } -} - -const char *php_http_strlist_find(const char list[], unsigned factor, unsigned item) -{ - unsigned M = 0, m = 0, major, minor; - const char *p = &list[0]; - - if (factor) { - major = (item / factor) - 1; - minor = item % factor; - } else { - major = 0; - minor = item; - } - while (*p && major != M++) { - while (*p) { - while (*p) { - ++p; - } - ++p; - } - ++p; - } - - while (*p && minor != m++) { - while (*p) { - ++p; - } - ++p; - } - - return p; -} - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - * vim600: noet sw=4 ts=4 fdm=marker - * vim<600: noet sw=4 ts=4 - */ - diff --git a/php_http_strlist.h b/php_http_strlist.h deleted file mode 100644 index 2685423..0000000 --- a/php_http_strlist.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - +--------------------------------------------------------------------+ - | PECL :: http | - +--------------------------------------------------------------------+ - | Redistribution and use in source and binary forms, with or without | - | modification, are permitted provided that the conditions mentioned | - | in the accompanying LICENSE file are met. | - +--------------------------------------------------------------------+ - | Copyright (c) 2004-2014, Michael Wallner | - +--------------------------------------------------------------------+ -*/ - -#ifndef PHP_HTTP_STRLIST_H -#define PHP_HTTP_STRLIST_H - -#ifdef NUL -# undef NUL -#endif -#define NUL "\0" - -#define PHP_HTTP_STRLIST(name) const char name[] -#define PHP_HTTP_STRLIST_ITEM(item) item NUL -#define PHP_HTTP_STRLIST_NEXT NUL -#define PHP_HTTP_STRLIST_STOP NUL NUL - -PHP_HTTP_API const char *php_http_strlist_find(const char list[], unsigned factor, unsigned item); - -typedef struct php_http_strlist_iterator { - const char *p; - unsigned factor, major, minor; -} php_http_strlist_iterator_t; - -PHP_HTTP_API php_http_strlist_iterator_t *php_http_strlist_iterator_init(php_http_strlist_iterator_t *iter, const char list[], unsigned factor); -PHP_HTTP_API const char *php_http_strlist_iterator_this(php_http_strlist_iterator_t *iter, unsigned *id); -PHP_HTTP_API const char *php_http_strlist_iterator_next(php_http_strlist_iterator_t *iter); -PHP_HTTP_API void php_http_strlist_iterator_dtor(php_http_strlist_iterator_t *iter); -PHP_HTTP_API void php_http_strlist_iterator_free(php_http_strlist_iterator_t **iter); - -#endif /* PHP_HTTP_STRLIST_H */ - -/* - * Local variables: - * tab-width: 4 - * c-basic-offset: 4 - * End: - * vim600: noet sw=4 ts=4 fdm=marker - * vim<600: noet sw=4 ts=4 - */ - diff --git a/php_http_url.c b/php_http_url.c index 5aeefa8..6bfca24 100644 --- a/php_http_url.c +++ b/php_http_url.c @@ -57,7 +57,7 @@ static inline char *localhostname(void) return estrndup("localhost", lenof("localhost")); } -#define url(buf) ((php_http_url_t *) buf.data) +#define url(buf) ((php_http_url_t *) (buf).data) static php_http_url_t *php_http_url_from_env(TSRMLS_D) { @@ -123,29 +123,32 @@ static php_http_url_t *php_http_url_from_env(TSRMLS_D) return url(buf); } -void php_http_url(int flags, const php_url *old_url, const php_url *new_url, php_url **url_ptr, char **url_str, size_t *url_len TSRMLS_DC) -{ - php_http_url_t *url = php_http_url_mod((const php_http_url_t *) old_url, (const php_http_url_t *) new_url, flags TSRMLS_CC); - - if (url_ptr) { - *url_ptr = php_http_url_to_php_url(url); - } - if (url_str) { - php_http_url_to_string(url, url_str, url_len TSRMLS_CC); - } - - php_http_url_free(&url); -} - #define url_isset(u,n) \ ((u)&&(u)->n) +#define url_append(buf, append) do { \ + char *_ptr = (buf)->data; \ + php_http_url_t *_url = (php_http_url_t *) _ptr, _mem = *_url; \ + append; \ + /* relocate */ \ + if (_ptr != (buf)->data) { \ + ptrdiff_t diff = (buf)->data - _ptr; \ + _url = (php_http_url_t *) (buf)->data; \ + if (_mem.scheme) _url->scheme += diff; \ + if (_mem.user) _url->user += diff; \ + if (_mem.pass) _url->pass += diff; \ + if (_mem.host) _url->host += diff; \ + if (_mem.path) _url->path += diff; \ + if (_mem.query) _url->query += diff; \ + if (_mem.fragment) _url->fragment += diff; \ + } \ +} while (0) #define url_copy(n) do { \ if (url_isset(new_url, n)) { \ url(buf)->n = &buf.data[buf.used]; \ - php_http_buffer_append(&buf, new_url->n, strlen(new_url->n) + 1); \ + url_append(&buf, php_http_buffer_append(&buf, new_url->n, strlen(new_url->n) + 1)); \ } else if (url_isset(old_url, n)) { \ url(buf)->n = &buf.data[buf.used]; \ - php_http_buffer_append(&buf, old_url->n, strlen(old_url->n) + 1); \ + url_append(&buf, php_http_buffer_append(&buf, old_url->n, strlen(old_url->n) + 1)); \ } \ } while (0) @@ -196,28 +199,23 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u url(buf)->path = &buf.data[buf.used]; if (path[0] != '/') { - php_http_buffer_append(&buf, "/", 1); + url_append(&buf, php_http_buffer_append(&buf, "/", 1)); } - php_http_buffer_append(&buf, path, strlen(path) + 1); + url_append(&buf, php_http_buffer_append(&buf, path, strlen(path) + 1)); efree(path); } else { const char *path = NULL; - url(buf)->path = &buf.data[buf.used]; - if (url_isset(new_url, path)) { path = new_url->path; } else if (url_isset(old_url, path)) { path = old_url->path; - } else { - php_http_buffer_append(&buf, "/", sizeof("/")); } if (path) { - if (path[0] != '/') { - php_http_buffer_append(&buf, "/", 1); - } - php_http_buffer_append(&buf, path, strlen(path) + 1); + url(buf)->path = &buf.data[buf.used]; + + url_append(&buf, php_http_buffer_append(&buf, path, strlen(path) + 1)); } @@ -241,7 +239,7 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u php_http_querystring_update(&qarr, NULL, &qstr TSRMLS_CC); url(buf)->query = &buf.data[buf.used]; - php_http_buffer_append(&buf, Z_STRVAL(qstr), Z_STRLEN(qstr) + 1); + url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL(qstr), Z_STRLEN(qstr) + 1)); zval_dtor(&qstr); zval_dtor(&qarr); @@ -261,22 +259,6 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u php_http_url_free(&tmp_url); } - /* set some sane defaults */ - - if (!url(buf)->scheme) { - url(buf)->scheme = &buf.data[buf.used]; - php_http_buffer_append(&buf, "http", sizeof("http")); - } - - if (!url(buf)->host) { - url(buf)->host = &buf.data[buf.used]; - php_http_buffer_append(&buf, "localhost", sizeof("localhost")); - } - - if (!url(buf)->path) { - url(buf)->path = &buf.data[buf.used]; - php_http_buffer_append(&buf, "/", sizeof("/")); - } /* replace directory references if path is not a single slash */ if ((flags & PHP_HTTP_URL_SANITIZE_PATH) && url(buf)->path[0] && url(buf)->path[1]) { @@ -329,8 +311,8 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u } /* unset default ports */ if (url(buf)->port) { - if ( ((url(buf)->port == 80) && !strcmp(url(buf)->scheme, "http")) - || ((url(buf)->port ==443) && !strcmp(url(buf)->scheme, "https")) + if ( ((url(buf)->port == 80) && url(buf)->scheme && !strcmp(url(buf)->scheme, "http")) + || ((url(buf)->port ==443) && url(buf)->scheme && !strcmp(url(buf)->scheme, "https")) ) { url(buf)->port = 0; } @@ -339,16 +321,17 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u return url(buf); } -void php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len TSRMLS_DC) +char *php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len, zend_bool persistent) { php_http_buffer_t buf; - php_http_buffer_init(&buf); + php_http_buffer_init_ex(&buf, PHP_HTTP_BUFFER_DEFAULT_SIZE, persistent ? + PHP_HTTP_BUFFER_INIT_PERSISTENT : 0); if (url->scheme && *url->scheme) { php_http_buffer_appendl(&buf, url->scheme); php_http_buffer_appends(&buf, "://"); - } else { + } else if ((url->user && *url->user) || (url->host && *url->host)) { php_http_buffer_appends(&buf, "//"); } @@ -363,16 +346,18 @@ void php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *u if (url->host && *url->host) { php_http_buffer_appendl(&buf, url->host); - } else { - php_http_buffer_appends(&buf, "localhost"); - } - - if (url->port) { - php_http_buffer_appendf(&buf, ":%hu", url->port); + if (url->port) { + php_http_buffer_appendf(&buf, ":%hu", url->port); + } } if (url->path && *url->path) { + if (*url->path != '/') { + php_http_buffer_appends(&buf, "/"); + } php_http_buffer_appendl(&buf, url->path); + } else if (buf.used) { + php_http_buffer_appends(&buf, "/"); } if (url->query && *url->query) { @@ -394,12 +379,68 @@ void php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *u if (url_str) { *url_str = buf.data; - } else { - php_http_buffer_dtor(&buf); } + + return buf.data; } -php_http_url_t *php_http_url_from_struct(HashTable *ht TSRMLS_DC) +char *php_http_url_authority_to_string(const php_http_url_t *url, char **url_str, size_t *url_len) +{ + php_http_buffer_t buf; + + php_http_buffer_init(&buf); + + if (url->user && *url->user) { + php_http_buffer_appendl(&buf, url->user); + if (url->pass && *url->pass) { + php_http_buffer_appends(&buf, ":"); + php_http_buffer_appendl(&buf, url->pass); + } + php_http_buffer_appends(&buf, "@"); + } + + if (url->host && *url->host) { + php_http_buffer_appendl(&buf, url->host); + if (url->port) { + php_http_buffer_appendf(&buf, ":%hu", url->port); + } + } + + php_http_buffer_shrink(&buf); + php_http_buffer_fix(&buf); + + if (url_len) { + *url_len = buf.used; + } + + if (url_str) { + *url_str = buf.data; + } + + return buf.data; +} + +php_http_url_t *php_http_url_from_zval(zval *value, unsigned flags TSRMLS_DC) +{ + zval *zcpy; + php_http_url_t *purl; + + switch (Z_TYPE_P(value)) { + case IS_ARRAY: + case IS_OBJECT: + purl = php_http_url_from_struct(HASH_OF(value)); + break; + + default: + zcpy = php_http_ztyp(IS_STRING, value); + purl = php_http_url_parse(Z_STRVAL_P(zcpy), Z_STRLEN_P(zcpy), flags TSRMLS_CC); + zval_ptr_dtor(&zcpy); + } + + return purl; +} + +php_http_url_t *php_http_url_from_struct(HashTable *ht) { zval **e; php_http_buffer_t buf; @@ -411,25 +452,25 @@ php_http_url_t *php_http_url_from_struct(HashTable *ht TSRMLS_DC) if (SUCCESS == zend_hash_find(ht, "scheme", sizeof("scheme"), (void *) &e)) { zval *cpy = php_http_ztyp(IS_STRING, *e); url(buf)->scheme = &buf.data[buf.used]; - php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1); + url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1)); zval_ptr_dtor(&cpy); } if (SUCCESS == zend_hash_find(ht, "user", sizeof("user"), (void *) &e)) { zval *cpy = php_http_ztyp(IS_STRING, *e); url(buf)->user = &buf.data[buf.used]; - php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1); + url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1)); zval_ptr_dtor(&cpy); } if (SUCCESS == zend_hash_find(ht, "pass", sizeof("pass"), (void *) &e)) { zval *cpy = php_http_ztyp(IS_STRING, *e); url(buf)->pass = &buf.data[buf.used]; - php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1); + url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1)); zval_ptr_dtor(&cpy); } if (SUCCESS == zend_hash_find(ht, "host", sizeof("host"), (void *) &e)) { zval *cpy = php_http_ztyp(IS_STRING, *e); url(buf)->host = &buf.data[buf.used]; - php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1); + url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1)); zval_ptr_dtor(&cpy); } if (SUCCESS == zend_hash_find(ht, "port", sizeof("port"), (void *) &e)) { @@ -440,19 +481,19 @@ php_http_url_t *php_http_url_from_struct(HashTable *ht TSRMLS_DC) if (SUCCESS == zend_hash_find(ht, "path", sizeof("path"), (void *) &e)) { zval *cpy = php_http_ztyp(IS_STRING, *e); url(buf)->path = &buf.data[buf.used]; - php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1); + url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1)); zval_ptr_dtor(&cpy); } if (SUCCESS == zend_hash_find(ht, "query", sizeof("query"), (void *) &e)) { zval *cpy = php_http_ztyp(IS_STRING, *e); url(buf)->query = &buf.data[buf.used]; - php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1); + url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1)); zval_ptr_dtor(&cpy); } if (SUCCESS == zend_hash_find(ht, "fragment", sizeof("fragment"), (void *) &e)) { zval *cpy = php_http_ztyp(IS_STRING, *e); url(buf)->fragment = &buf.data[buf.used]; - php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1); + url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1)); zval_ptr_dtor(&cpy); } @@ -562,6 +603,43 @@ void php_http_url_free(php_http_url_t **url) } } +php_http_url_t *php_http_url_copy(const php_http_url_t *url, zend_bool persistent) +{ + php_http_url_t *cpy; + const char *end = NULL, *url_ptr = (const char *) url; + char *cpy_ptr; + + end = MAX(url->scheme, end); + end = MAX(url->pass, end); + end = MAX(url->user, end); + end = MAX(url->host, end); + end = MAX(url->path, end); + end = MAX(url->query, end); + end = MAX(url->fragment, end); + + if (end) { + end += strlen(end) + 1; + cpy_ptr = pecalloc(1, end - url_ptr, persistent); + cpy = (php_http_url_t *) cpy_ptr; + + memcpy(cpy_ptr + sizeof(*cpy), url_ptr + sizeof(*url), end - url_ptr - sizeof(*url)); + + cpy->scheme = url->scheme ? cpy_ptr + (url->scheme - url_ptr) : NULL; + cpy->pass = url->pass ? cpy_ptr + (url->pass - url_ptr) : NULL; + cpy->user = url->user ? cpy_ptr + (url->user - url_ptr) : NULL; + cpy->host = url->host ? cpy_ptr + (url->host - url_ptr) : NULL; + cpy->path = url->path ? cpy_ptr + (url->path - url_ptr) : NULL; + cpy->query = url->query ? cpy_ptr + (url->query - url_ptr) : NULL; + cpy->fragment = url->fragment ? cpy_ptr + (url->fragment - url_ptr) : NULL; + } else { + cpy = ecalloc(1, sizeof(*url)); + } + + cpy->port = url->port; + + return cpy; +} + static size_t parse_mb_utf8(unsigned *wc, const char *ptr, const char *end) { unsigned wchar; @@ -834,7 +912,9 @@ static STATUS parse_hostinfo(struct parse_state *state, const char *ptr) break; default: - if (port) { + if (ptr == end) { + break; + } else if (port) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse port; unexpected byte 0x%02x at pos %u in '%s'", (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp); @@ -905,6 +985,7 @@ static const char *parse_authority(struct parse_state *state) case '?': case '#': case '\0': + EOD: /* host delimiter */ if (tmp != state->ptr && SUCCESS != parse_hostinfo(state, tmp)) { return NULL; @@ -913,7 +994,8 @@ static const char *parse_authority(struct parse_state *state) } } while (++state->ptr <= state->end); - return NULL; + --state->ptr; + goto EOD; } static const char *parse_path(struct parse_state *state) @@ -933,14 +1015,7 @@ static const char *parse_path(struct parse_state *state) switch (*state->ptr) { case '#': case '?': - case '\0': - /* did we have any path component ? */ - if (tmp != state->ptr) { - state->buffer[state->offset++] = 0; - } else { - state->url.path = NULL; - } - return state->ptr; + goto done; case '%': if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) { @@ -979,9 +1054,16 @@ static const char *parse_path(struct parse_state *state) } state->ptr += mb - 1; } - } while (++state->ptr <= state->end); + } while (++state->ptr < state->end); - return NULL; + done: + /* did we have any path component ? */ + if (tmp != state->ptr) { + state->buffer[state->offset++] = 0; + } else { + state->url.path = NULL; + } + return state->ptr; } static const char *parse_query(struct parse_state *state) @@ -1002,9 +1084,7 @@ static const char *parse_query(struct parse_state *state) do { switch (*state->ptr) { case '#': - case '\0': - state->buffer[state->offset++] = 0; - return state->ptr; + goto done; case '%': if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) { @@ -1053,9 +1133,11 @@ static const char *parse_query(struct parse_state *state) } state->ptr += mb - 1; } - } while (++state->ptr <= state->end); + } while (++state->ptr < state->end); - return NULL; + done: + state->buffer[state->offset++] = 0; + return state->ptr; } static const char *parse_fragment(struct parse_state *state) @@ -1075,10 +1157,6 @@ static const char *parse_fragment(struct parse_state *state) do { switch (*state->ptr) { - case '\0': - state->buffer[state->offset++] = 0; - return state->ptr; - case '%': if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) { php_error_docref(NULL TSRMLS_CC, E_WARNING, @@ -1116,9 +1194,10 @@ static const char *parse_fragment(struct parse_state *state) } state->ptr += mb - 1; } - } while (++state->ptr <= state->end); + } while (++state->ptr < state->end); - return NULL; + state->buffer[state->offset++] = 0; + return state->ptr; } static const char *parse_hier(struct parse_state *state) @@ -1217,6 +1296,33 @@ php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags T return (php_http_url_t *) state; } +php_http_url_t *php_http_url_parse_authority(const char *str, size_t len, unsigned flags TSRMLS_DC) +{ + size_t maxlen = 3 * len; + struct parse_state *state = ecalloc(1, sizeof(*state) + maxlen); + + state->end = str + len; + state->ptr = str; + state->flags = flags; + state->maxlen = maxlen; + TSRMLS_SET_CTX(state->ts); + + if (!(state->ptr = parse_authority(state))) { + efree(state); + return NULL; + } + + if (state->ptr != state->end) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, + "Failed to parse URL authority, unexpected character at pos %u in '%s'", + (unsigned) (state->ptr - str), str); + efree(state); + return NULL; + } + + return (php_http_url_t *) state; +} + ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl___construct, 0, 0, 0) ZEND_ARG_INFO(0, old_url) ZEND_ARG_INFO(0, new_url) @@ -1235,38 +1341,14 @@ PHP_METHOD(HttpUrl, __construct) php_http_url_t *res_purl, *new_purl = NULL, *old_purl = NULL; if (new_url) { - switch (Z_TYPE_P(new_url)) { - case IS_OBJECT: - case IS_ARRAY: - new_purl = php_http_url_from_struct(HASH_OF(new_url) TSRMLS_CC); - break; - default: { - zval *cpy = php_http_ztyp(IS_STRING, new_url); - - new_purl = php_http_url_parse(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy), flags TSRMLS_CC); - zval_ptr_dtor(&cpy); - break; - } - } + new_purl = php_http_url_from_zval(new_url, flags TSRMLS_CC); if (!new_purl) { zend_restore_error_handling(&zeh TSRMLS_CC); return; } } if (old_url) { - switch (Z_TYPE_P(old_url)) { - case IS_OBJECT: - case IS_ARRAY: - old_purl = php_http_url_from_struct(HASH_OF(old_url) TSRMLS_CC); - break; - default: { - zval *cpy = php_http_ztyp(IS_STRING, old_url); - - old_purl = php_http_url_parse(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy), flags TSRMLS_CC); - zval_ptr_dtor(&cpy); - break; - } - } + old_purl = php_http_url_from_zval(old_url, flags TSRMLS_CC); if (!old_purl) { if (new_purl) { php_http_url_free(&new_purl); @@ -1307,26 +1389,14 @@ PHP_METHOD(HttpUrl, mod) php_http_url_t *new_purl = NULL, *old_purl = NULL; if (new_url) { - switch (Z_TYPE_P(new_url)) { - case IS_OBJECT: - case IS_ARRAY: - new_purl = php_http_url_from_struct(HASH_OF(new_url) TSRMLS_CC); - break; - default: { - zval *cpy = php_http_ztyp(IS_STRING, new_url); - - new_purl = php_http_url_parse(Z_STRVAL_P(new_url), Z_STRLEN_P(new_url), flags TSRMLS_CC); - zval_ptr_dtor(&cpy); - break; - } - } + new_purl = php_http_url_from_zval(new_url, flags TSRMLS_CC); if (!new_purl) { zend_restore_error_handling(&zeh TSRMLS_CC); return; } } - if ((old_purl = php_http_url_from_struct(HASH_OF(getThis()) TSRMLS_CC))) { + if ((old_purl = php_http_url_from_struct(HASH_OF(getThis())))) { php_http_url_t *res_purl; ZVAL_OBJVAL(return_value, zend_objects_clone_obj(getThis() TSRMLS_CC), 0); @@ -1351,11 +1421,11 @@ PHP_METHOD(HttpUrl, toString) if (SUCCESS == zend_parse_parameters_none()) { php_http_url_t *purl; - if ((purl = php_http_url_from_struct(HASH_OF(getThis()) TSRMLS_CC))) { + if ((purl = php_http_url_from_struct(HASH_OF(getThis())))) { char *str; size_t len; - php_http_url_to_string(purl, &str, &len TSRMLS_CC); + php_http_url_to_string(purl, &str, &len, 0); php_http_url_free(&purl); RETURN_STRINGL(str, len, 0); } @@ -1374,72 +1444,17 @@ PHP_METHOD(HttpUrl, toArray) } /* strip any non-URL properties */ - purl = php_http_url_from_struct(HASH_OF(getThis()) TSRMLS_CC); + purl = php_http_url_from_struct(HASH_OF(getThis())); php_http_url_to_struct(purl, return_value TSRMLS_CC); php_http_url_free(&purl); } -ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_parse, 0, 0, 1) - ZEND_ARG_INFO(0, url) - ZEND_ARG_INFO(0, flags) -ZEND_END_ARG_INFO(); -PHP_METHOD(HttpUrl, parse) -{ - char *str; - int len; - long flags = 0; - php_http_url_t *url; - zend_error_handling zeh; - - php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &len, &flags), invalid_arg, return); - - zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh TSRMLS_CC); - if ((url = php_http_url_parse(str, len, flags TSRMLS_CC))) { - object_init_ex(return_value, php_http_url_class_entry); - if (url->scheme) { - zend_update_property_string(php_http_url_class_entry, return_value, - ZEND_STRL("scheme"), url->scheme TSRMLS_CC); - } - if (url->user) { - zend_update_property_string(php_http_url_class_entry, return_value, - ZEND_STRL("user"), url->user TSRMLS_CC); - } - if (url->pass) { - zend_update_property_string(php_http_url_class_entry, return_value, - ZEND_STRL("pass"), url->pass TSRMLS_CC); - } - if (url->host) { - zend_update_property_string(php_http_url_class_entry, return_value, - ZEND_STRL("host"), url->host TSRMLS_CC); - } - if (url->port) { - zend_update_property_long(php_http_url_class_entry, return_value, - ZEND_STRL("port"), url->port TSRMLS_CC); - } - if (url->path) { - zend_update_property_string(php_http_url_class_entry, return_value, - ZEND_STRL("path"), url->path TSRMLS_CC); - } - if (url->query) { - zend_update_property_string(php_http_url_class_entry, return_value, - ZEND_STRL("query"), url->query TSRMLS_CC); - } - if (url->fragment) { - zend_update_property_string(php_http_url_class_entry, return_value, - ZEND_STRL("fragment"), url->fragment TSRMLS_CC); - } - php_http_url_free(&url); - } - zend_restore_error_handling(&zeh TSRMLS_CC); -} - static zend_function_entry php_http_url_methods[] = { PHP_ME(HttpUrl, __construct, ai_HttpUrl___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) PHP_ME(HttpUrl, mod, ai_HttpUrl_mod, ZEND_ACC_PUBLIC) PHP_ME(HttpUrl, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC) ZEND_MALIAS(HttpUrl, __toString, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC) PHP_ME(HttpUrl, toArray, ai_HttpUrl_toArray, ZEND_ACC_PUBLIC) - PHP_ME(HttpUrl, parse, ai_HttpUrl_parse, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) EMPTY_FUNCTION_ENTRY }; diff --git a/php_http_url.h b/php_http_url.h index a6dda53..303b936 100644 --- a/php_http_url.h +++ b/php_http_url.h @@ -15,6 +15,7 @@ #include +/* php_http_url_mod() */ #define PHP_HTTP_URL_REPLACE 0x000 #define PHP_HTTP_URL_JOIN_PATH 0x001 #define PHP_HTTP_URL_JOIN_QUERY 0x002 @@ -57,43 +58,28 @@ typedef struct php_http_url { } php_http_url_t; PHP_HTTP_API php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags TSRMLS_DC); -PHP_HTTP_API void php_http_url_free(php_http_url_t **url); - -/* deprecated */ -PHP_HTTP_API void php_http_url(int flags, const php_url *old_url, const php_url *new_url, php_url **url_ptr, char **url_str, size_t *url_len TSRMLS_DC); -/* use this instead */ +PHP_HTTP_API php_http_url_t *php_http_url_parse_authority(const char *str, size_t len, unsigned flags TSRMLS_DC); PHP_HTTP_API php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_url_t *new_url, unsigned flags TSRMLS_DC); +PHP_HTTP_API php_http_url_t *php_http_url_copy(const php_http_url_t *url, zend_bool persistent); +PHP_HTTP_API php_http_url_t *php_http_url_from_struct(HashTable *ht); +PHP_HTTP_API php_http_url_t *php_http_url_from_zval(zval *value, unsigned flags TSRMLS_DC); +PHP_HTTP_API HashTable *php_http_url_to_struct(const php_http_url_t *url, zval *strct TSRMLS_DC); +PHP_HTTP_API char *php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len, zend_bool persistent); +PHP_HTTP_API char *php_http_url_authority_to_string(const php_http_url_t *url, char **url_str, size_t *url_len); +PHP_HTTP_API void php_http_url_free(php_http_url_t **url); PHP_HTTP_API STATUS php_http_url_encode_hash(HashTable *hash, const char *pre_encoded_str, size_t pre_encoded_len, char **encoded_str, size_t *encoded_len TSRMLS_DC); PHP_HTTP_API STATUS php_http_url_encode_hash_ex(HashTable *hash, php_http_buffer_t *qstr, const char *arg_sep_str, size_t arg_sep_len, const char *val_sep_str, size_t val_sep_len, const char *pre_encoded_str, size_t pre_encoded_len TSRMLS_DC); static inline void php_http_url_argsep(const char **str, size_t *len TSRMLS_DC) { - if (SUCCESS != php_http_ini_entry(ZEND_STRL("arg_separator.output"), str, len, 0 TSRMLS_CC) || !*len) { - *str = PHP_HTTP_URL_ARGSEP; - *len = lenof(PHP_HTTP_URL_ARGSEP); - } + php_http_ini_entry(ZEND_STRL("arg_separator.output"), str, len, 0 TSRMLS_CC); } -static inline php_url *php_http_url_to_php_url(php_http_url_t *url) -{ - php_url *purl = ecalloc(1, sizeof(*purl)); - - if (url->scheme) purl->scheme = estrdup(url->scheme); - if (url->pass) purl->pass = estrdup(url->pass); - if (url->user) purl->user = estrdup(url->user); - if (url->host) purl->host = estrdup(url->host); - if (url->path) purl->path = estrdup(url->path); - if (url->query) purl->query = estrdup(url->query); - if (url->fragment) purl->fragment = estrdup(url->fragment); - - return purl; +static inline zend_bool php_http_url_is_empty(const php_http_url_t *url) { + return !(url->scheme || url->pass || url->user || url->host || url->port || url->path || url->query || url->fragment); } -PHP_HTTP_API php_http_url_t *php_http_url_from_struct(HashTable *ht TSRMLS_DC); -PHP_HTTP_API HashTable *php_http_url_to_struct(const php_http_url_t *url, zval *strct TSRMLS_DC); -PHP_HTTP_API void php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len TSRMLS_DC); - PHP_HTTP_API zend_class_entry *php_http_url_class_entry; PHP_MINIT_FUNCTION(http_url); diff --git a/php_http_utf8.h b/php_http_utf8.h index c7bcb49..2503f06 100644 --- a/php_http_utf8.h +++ b/php_http_utf8.h @@ -555,7 +555,7 @@ static inline size_t utf8towc(unsigned *wc, const unsigned char *uc, size_t len) { unsigned char ub = utf8_mblen[*uc]; - if (!ub || ub > len || ub > 3) { + if (!ub || ub > len || ub > 4) { return 0; } @@ -595,9 +595,9 @@ static inline size_t utf8towc(unsigned *wc, const unsigned char *uc, size_t len) static inline zend_bool isualpha(unsigned ch) { - unsigned i, j; + unsigned i = 0, j; - for (i = 0; i < sizeof(utf8_ranges)/sizeof(utf8_range_t); ++i) { + PHP_HTTP_DUFF(sizeof(utf8_ranges)/sizeof(utf8_range_t), if (utf8_ranges[i].start == ch) { return 1; } else if (utf8_ranges[i].start <= ch && utf8_ranges[i].end >= ch) { @@ -611,7 +611,8 @@ static inline zend_bool isualpha(unsigned ch) } return 0; } - } + ++i; + ); return 0; } diff --git a/php_http_version.c b/php_http_version.c index f4fcfc8..7adef9d 100644 --- a/php_http_version.c +++ b/php_http_version.c @@ -27,7 +27,7 @@ php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major, php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str TSRMLS_DC) { long major, minor; - char separator = 0, *stop = NULL; + char separator = 0; register const char *ptr = str; switch (*ptr) { @@ -40,16 +40,16 @@ php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *st ++ptr; /* no break */ default: - major = strtol(ptr, &stop, 10); - if (stop && stop != ptr && major != LONG_MIN && major != LONG_MAX) { - separator = *stop; + /* rfc7230#2.6 The HTTP version number consists of two decimal digits separated by a "." (period or decimal point) */ + major = *ptr++ - '0'; + if (major >= 0 && major <= 9) { + separator = *ptr++; if (separator) { if (separator != '.' && separator != ',') { - php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr); + php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr - 2); } - ptr = stop + 1; - minor = strtol(ptr, &stop, 10); - if (minor != LONG_MIN && minor != LONG_MAX) { + minor = *ptr - '0'; + if (minor >= 0 && minor <= 9) { return php_http_version_init(v, major, minor TSRMLS_CC); } } diff --git a/tests/bug66891.phpt b/tests/bug66891.phpt index 0fd84f8..30f9d7e 100644 --- a/tests/bug66891.phpt +++ b/tests/bug66891.phpt @@ -12,7 +12,7 @@ header("WWW-Authenticate: none"); $r = new http\Env\Response; $r->setResponseCode(200); $r->send(); -var_dump(http_response_code()); +var_dump(http\Env::getResponseCode()); ?> --EXPECT-- int(200) \ No newline at end of file diff --git a/tests/bug69000.phpt b/tests/bug69000.phpt new file mode 100644 index 0000000..9b23e47 --- /dev/null +++ b/tests/bug69000.phpt @@ -0,0 +1,17 @@ +--TEST-- +Bug #69000 (http\Url breaks down with very long URL query strings) +--SKIPIF-- + +--FILE-- + + +===DONE=== +--EXPECT-- +Test +http://foo.bar/?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +===DONE=== diff --git a/tests/client001.phpt b/tests/client001.phpt index 8071afa..dea89a8 100644 --- a/tests/client001.phpt +++ b/tests/client001.phpt @@ -3,6 +3,7 @@ client drivers --SKIPIF-- --FILE-- --FILE-- attach($observer); - $client->enqueue($request); - $client->send(); -} +server("proxy.inc", function($port, $stdin, $stdout, $stderr) { + foreach (http\Client::getAvailableDrivers() as $driver) { + $client = new http\Client($driver); + $client->attach(new Observer); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/")); + $client->send(); + } +}); ?> diff --git a/tests/client003.phpt b/tests/client003.phpt index 62d0bca..a6b1a4c 100644 --- a/tests/client003.phpt +++ b/tests/client003.phpt @@ -3,26 +3,31 @@ client once & wait --SKIPIF-- --FILE-- enqueue($request); +server("proxy.inc", function($port) { + $request = new http\Client\Request("GET", "http://www.example.org/"); - while ($client->once()) { - $client->wait(.1); - } + foreach (http\Client::getAvailableDrivers() as $driver) { + $client = new http\Client($driver); + $client->enqueue($request); + + while ($client->once()) { + $client->wait(.1); + } - if (!$client->getResponse()) { - var_dump($client); + if (!$client->getResponse()) { + var_dump($client); + } } -} +}); ?> Done --EXPECT-- diff --git a/tests/client004.phpt b/tests/client004.phpt index 1342efa..1b3bb5e 100644 --- a/tests/client004.phpt +++ b/tests/client004.phpt @@ -3,31 +3,36 @@ client reset --SKIPIF-- --FILE-- enqueue($request)->send(); - if (!($client->getResponse($request) instanceof http\Client\Response)) { - var_dump($client); - } - try { +echo "Test\n"; + +server("proxy.inc", function($port) { + $request = new http\Client\Request("GET", "http://localhost:$port"); + + foreach (http\Client::getAvailableDrivers() as $driver) { + $client = new http\Client($driver); + $client->enqueue($request)->send(); + if (!($client->getResponse($request) instanceof http\Client\Response)) { + var_dump($client); + } + try { + $client->enqueue($request); + } catch (Exception $e) { + echo $e->getMessage(),"\n"; + } + $client->reset(); + if (($response = $client->getResponse())) { + var_dump($response); + } $client->enqueue($request); - } catch (Exception $e) { - echo $e->getMessage(),"\n"; - } - $client->reset(); - if (($response = $client->getResponse())) { - var_dump($response); } - $client->enqueue($request); -} + }); ?> Done --EXPECTREGEX-- diff --git a/tests/client005.phpt b/tests/client005.phpt index 4576f16..b73b5bb 100644 --- a/tests/client005.phpt +++ b/tests/client005.phpt @@ -3,22 +3,27 @@ client response callback --SKIPIF-- --FILE-- enqueue(new http\Client\Request("GET", "http://www.example.org"), function($response) { - echo "R\n"; - if (!($response instanceof http\Client\Response)) { - var_dump($response); - } - }); - $client->send(); -} +server("proxy.inc", function($port) { + foreach (http\Client::getAvailableDrivers() as $driver) { + $client = new http\Client($driver); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port"), function($response) { + echo "R\n"; + if (!($response instanceof http\Client\Response)) { + var_dump($response); + } + }); + $client->send(); + } +}); ?> Done diff --git a/tests/client006.phpt b/tests/client006.phpt index 7b3986e..1621cc7 100644 --- a/tests/client006.phpt +++ b/tests/client006.phpt @@ -3,10 +3,13 @@ client response callback + dequeue --SKIPIF-- --FILE-- enqueue($request, "response"); - $client->send(); - try { - $client->dequeue($request); - } catch (Exception $e) { - echo $e->getMessage(),"\n"; +server("proxy.inc", function($port) { + $request = new http\Client\Request("GET", "http://localhost:$port"); + + foreach (http\Client::getAvailableDrivers() as $driver) { + $client = new http\Client($driver); + for ($i=0; $i < 2; ++ $i) { + $client->enqueue($request, "response"); + $client->send(); + try { + $client->dequeue($request); + } catch (Exception $e) { + echo $e->getMessage(),"\n"; + } } } -} +}); ?> Done diff --git a/tests/client007.phpt b/tests/client007.phpt index 74cdbcd..f0100a6 100644 --- a/tests/client007.phpt +++ b/tests/client007.phpt @@ -3,10 +3,13 @@ client response callback + requeue --SKIPIF-- --FILE-- requeue($request, "response"); - $client->send(); +server("proxy.inc", function($port) { + $request = new http\Client\Request("GET", "http://localhost:$port"); + + foreach (http\Client::getAvailableDrivers() as $driver) { + $client = new http\Client($driver); + for ($i=0; $i < 2; ++ $i) { + $client->requeue($request, "response"); + $client->send(); + } } -} +}); ?> Done diff --git a/tests/client008.phpt b/tests/client008.phpt index 31584d3..98fc8f8 100644 --- a/tests/client008.phpt +++ b/tests/client008.phpt @@ -3,29 +3,35 @@ client features --SKIPIF-- --FILE-- enablePipelining(true); - $client->enableEvents(true); +server("pipeline.inc", function($port, $stdin) { + fputs($stdin, "2\n"); + $request = new http\Client\Request("GET", "http://localhost:$port"); + + $client = new http\Client(); + $client->configure(array("pipelining" => true, "use_eventloop" => true)); + $client->enqueue($request); + $client->send(); + $client->enqueue(clone $request); $client->enqueue(clone $request); - + $client->send(); - + while ($client->getResponse()) { echo "R\n"; } -} +}); ?> Done diff --git a/tests/client009.phpt b/tests/client009.phpt index e1553d6..9b5579d 100644 --- a/tests/client009.phpt +++ b/tests/client009.phpt @@ -4,6 +4,7 @@ client static cookies --FILE-- --FILE-- --FILE-- getHistory()->toString(true); Done --EXPECTF-- Test -POST /ext-http/.print_request.php HTTP/1.1 -User-Agent: %s -Host: dev.iworks.at -Accept: */* +POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1 Content-Length: 6 -Content-Type: application/x-www-form-urlencoded -X-Original-Content-Length: 6 foobar HTTP/1.1 200 OK @@ -50,13 +46,8 @@ Content-Length: 19 string(6) "foobar" -POST /ext-http/.print_request.php HTTP/1.1 -User-Agent: %s -Host: dev.iworks.at -Accept: */* +POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1 Content-Length: 6 -Content-Type: application/x-www-form-urlencoded -X-Original-Content-Length: 6 foobar HTTP/1.1 200 OK @@ -69,13 +60,8 @@ Content-Length: 19 string(6) "foobar" -POST /ext-http/.print_request.php HTTP/1.1 -User-Agent: %s -Host: dev.iworks.at -Accept: */* +POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1 Content-Length: 6 -Content-Type: application/x-www-form-urlencoded -X-Original-Content-Length: 6 foobar HTTP/1.1 200 OK diff --git a/tests/client012.phpt b/tests/client012.phpt index ad6c2eb..306f26e 100644 --- a/tests/client012.phpt +++ b/tests/client012.phpt @@ -4,20 +4,20 @@ client ssl --FILE-- setSslOptions(array("verify_peer" => true)); -$client->addSslOptions(array("verify_host" => 2)); +$client->setSslOptions(array("verifypeer" => true)); +$client->addSslOptions(array("verifyhost" => 2)); var_dump( array( - "verify_peer" => true, - "verify_host" => 2, + "verifypeer" => true, + "verifyhost" => 2, ) === $client->getSslOptions() ); @@ -26,7 +26,7 @@ $client->send(); $ti = (array) $client->getTransferInfo($req); var_dump(array_key_exists("ssl_engines", $ti)); -var_dump(0 < count($ti["ssl_engines"])); +var_dump(0 < count($ti["ssl_engines"] || $ti["tls_session"]["backend"] != "openssl")); ?> Done --EXPECTF-- diff --git a/tests/client013.phpt b/tests/client013.phpt index 00bae4e..fdf6c96 100644 --- a/tests/client013.phpt +++ b/tests/client013.phpt @@ -3,11 +3,13 @@ client observers --SKIPIF-- --FILE-- attach($o1 = new ProgressObserver1); -$client->attach($o2 = new ProgressObserver2); -$client->attach( - $o3 = new CallbackObserver( - function ($c, $r) { - $p = (array) $c->getProgressInfo($r); - var_dump(array_key_exists("started", $p)); - var_dump(array_key_exists("finished", $p)); - var_dump(array_key_exists("dlnow", $p)); - var_dump(array_key_exists("ulnow", $p)); - var_dump(array_key_exists("dltotal", $p)); - var_dump(array_key_exists("ultotal", $p)); - var_dump(array_key_exists("info", $p)); - } - ) -); - -$client->enqueue(new http\Client\Request("GET", "http://www.example.com/"))->send(); -var_dump(1 === preg_match("/(\.-)+/", $client->pi)); -var_dump(3 === count($client->getObservers())); -$client->detach($o1); -var_dump(2 === count($client->getObservers())); -$client->detach($o2); -var_dump(1 === count($client->getObservers())); -$client->detach($o3); -var_dump(0 === count($client->getObservers())); +server("proxy.inc", function($port) { + $client = new Client; + $client->attach($o1 = new ProgressObserver1); + $client->attach($o2 = new ProgressObserver2); + $client->attach( + $o3 = new CallbackObserver( + function ($c, $r) { + $p = (array) $c->getProgressInfo($r); + var_dump(array_key_exists("started", $p)); + var_dump(array_key_exists("finished", $p)); + var_dump(array_key_exists("dlnow", $p)); + var_dump(array_key_exists("ulnow", $p)); + var_dump(array_key_exists("dltotal", $p)); + var_dump(array_key_exists("ultotal", $p)); + var_dump(array_key_exists("info", $p)); + } + ) + ); + + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/"))->send(); + var_dump(1 === preg_match("/(\.-)+/", $client->pi)); + var_dump(3 === count($client->getObservers())); + $client->detach($o1); + var_dump(2 === count($client->getObservers())); + $client->detach($o2); + var_dump(1 === count($client->getObservers())); + $client->detach($o3); + var_dump(0 === count($client->getObservers())); + +}); ?> Done diff --git a/tests/client014.phpt b/tests/client014.phpt index d0ff471..a4504c1 100644 --- a/tests/client014.phpt +++ b/tests/client014.phpt @@ -3,6 +3,7 @@ reset content length when resetting body --SKIPIF-- --FILE-- enableEvents()) - throw new Exception("need events support"); -} catch (Exception $e) { - die("skip ".$e->getMessage()); -} -skip_online_test(); +skip_client_test(); ?> --FILE-- enableEvents(); -$client2->enableEvents(); -$client1->enqueue(new http\Client\Request("GET", "http://www.google.ca/")); -$client2->enqueue(new http\Client\Request("GET", "http://www.google.co.uk/")); +include "helper/server.inc"; -$client1->send(); - -if (($r = $client1->getResponse())) { - var_dump($r->getTransferInfo("response_code")); -} -if (($r = $client2->getResponse())) { - var_dump($r->getTransferInfo("response_code")); -} +echo "Test\n"; +server("proxy.inc", function($port) { + $client1 = new http\Client; + $client2 = new http\Client; + + $client1->configure(array("use_eventloop" => true)); + $client2->configure(array("use_eventloop" => true)); + + $client1->enqueue(new http\Client\Request("GET", "http://localhost:$port/")); + $client2->enqueue(new http\Client\Request("GET", "http://localhost:$port/")); + + $client1->send(); + + if (($r = $client1->getResponse())) { + var_dump($r->getTransferInfo("response_code")); + } + if (($r = $client2->getResponse())) { + var_dump($r->getTransferInfo("response_code")); + } + +}); ?> DONE --EXPECT-- diff --git a/tests/client016.phpt b/tests/client016.phpt index f50d9bb..d2aef07 100644 --- a/tests/client016.phpt +++ b/tests/client016.phpt @@ -3,34 +3,32 @@ client once & wait with events --SKIPIF-- enableEvents()) - throw new Exception("need events support"); -} catch (Exception $e) { - die("skip ".$e->getMessage()); -} -skip_online_test(); +skip_client_test(); ?> --FILE-- enableEvents(true); - $client->enqueue($request); +server("proxy.inc", function($port) { + $request = new http\Client\Request("GET", "http://localhost:$port/"); - while ($client->once()) { - $client->wait(.1); - } + foreach (http\Client::getAvailableDrivers() as $driver) { + $client = new http\Client($driver); + $client->configure(array("use_eventloop" => true)); + $client->enqueue($request); + + while ($client->once()) { + $client->wait(.1); + } - if (!$client->getResponse()) { - var_dump($client); + if (!$client->getResponse()) { + var_dump($client); + } } -} +}); ?> Done --EXPECT-- diff --git a/tests/client017.phpt b/tests/client017.phpt new file mode 100644 index 0000000..a505028 --- /dev/null +++ b/tests/client017.phpt @@ -0,0 +1,33 @@ +--TEST-- +client request gzip +--SKIPIF-- + +--FILE-- +setOptions(array("compress" => true)); + +$client->enqueue(new http\Client\Request("GET", "http://dev.iworks.at/ext-http/.print_request.php")); +$client->send(); + +echo $client->getResponse(); + +?> +===DONE=== +--EXPECTF-- +Test +HTTP/1.1 200 OK +Vary: Accept-Encoding +Content-Type: text/html +Date: %s +Server: %s +X-Original-Transfer-Encoding: chunked +X-Original-Content-Encoding: gzip +===DONE=== \ No newline at end of file diff --git a/tests/client018.phpt b/tests/client018.phpt new file mode 100644 index 0000000..c3ca9f9 --- /dev/null +++ b/tests/client018.phpt @@ -0,0 +1,56 @@ +--TEST-- +client pipelining +--SKIPIF-- + +--FILE-- +configure(array("pipelining" => true, "max_host_connections" => 0)); + + /* this is just to let curl know the server may be capable of pipelining */ + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port")); + $client->send(); + + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/1")); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/2")); + $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/3")); + $client->send(); + + while (($response = $client->getResponse())) { + echo $response; + } +}); + +?> +===DONE=== +--EXPECT-- +Test +HTTP/1.1 200 OK +X-Req: /3 +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +X-Req: /2 +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +X-Req: /1 +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +X-Req: / +Etag: "" +X-Original-Transfer-Encoding: chunked +===DONE=== diff --git a/tests/client019.phpt b/tests/client019.phpt new file mode 100644 index 0000000..e60e5aa --- /dev/null +++ b/tests/client019.phpt @@ -0,0 +1,45 @@ +--TEST-- +client proxy - send proxy headers for a proxy request +--SKIPIF-- + +--FILE-- +setOptions(array( + "timeout" => 10, + "proxytunnel" => true, + "proxyheader" => array("Hello" => "there!"), + "proxyhost" => "localhost", + "proxyport" => $port, + )); + try { + $c->enqueue($r)->send(); + } catch (Exception $e) { + echo $e; + } + echo $c->getResponse()->getBody(); +}); + +?> +===DONE=== +--EXPECTF-- +Test +Server on port %d +CONNECT www.example.com:80 HTTP/1.1 +Host: www.example.com:80 +User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s +Proxy-Connection: Keep-Alive +Hello: there! +===DONE=== diff --git a/tests/client020.phpt b/tests/client020.phpt new file mode 100644 index 0000000..7ea5d60 --- /dev/null +++ b/tests/client020.phpt @@ -0,0 +1,41 @@ +--TEST-- +client proxy - don't send proxy headers for a standard request +--SKIPIF-- + +--FILE-- +setOptions(array( + "timeout" => 3, + "proxyheader" => array("Hello" => "there!"), + )); + try { + $c->enqueue($r)->send(); + } catch (Exception $e) { + echo $e; + } + echo $c->getResponse()->getBody(); + unset($r, $client); +}); + +?> +===DONE=== +--EXPECTF-- +Test +Server on port %d +GET / HTTP/1.1 +User-Agent: PECL_HTTP/%s PHP/%s libcurl/%s +Host: localhost:%d +Accept: */* +===DONE=== diff --git a/tests/client021.phpt b/tests/client021.phpt new file mode 100644 index 0000000..1335aaa --- /dev/null +++ b/tests/client021.phpt @@ -0,0 +1,105 @@ +--TEST-- +client cookies +--SKIPIF-- + +--FILE-- +setOptions(array("cookiestore" => $tmpfile)); + +server("cookie.inc", function($port) use($request) { + $request->setOptions(array("port" => $port)); + $client = new http\Client; + echo $client->requeue($request)->send()->getResponse(); + echo $client->requeue($request)->send()->getResponse(); + echo $client->requeue($request)->send()->getResponse(); +}); + +server("cookie.inc", function($port) use($request) { + $request->setOptions(array("port" => $port)); + $client = new http\Client; + echo $client->requeue($request)->send()->getResponse(); + echo $client->requeue($request)->send()->getResponse(); + echo $client->requeue($request)->send()->getResponse(); +}); + +server("cookie.inc", function($port) use($request) { + $request->setOptions(array("port" => $port, "cookiesession" => true)); + $client = new http\Client; + echo $client->requeue($request)->send()->getResponse(); + echo $client->requeue($request)->send()->getResponse(); + echo $client->requeue($request)->send()->getResponse(); +}); + +server("cookie.inc", function($port) use($request) { + $request->setOptions(array("port" => $port, "cookiesession" => false)); + $client = new http\Client; + echo $client->requeue($request)->send()->getResponse(); + echo $client->requeue($request)->send()->getResponse(); + echo $client->requeue($request)->send()->getResponse(); +}); + +unlink($tmpfile); + +?> +===DONE=== +--EXPECT-- +Test +HTTP/1.1 200 OK +Set-Cookie: counter=1; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=2; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=3; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=4; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=5; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=6; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=1; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=1; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=1; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=2; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=3; +Etag: "" +X-Original-Transfer-Encoding: chunked +HTTP/1.1 200 OK +Set-Cookie: counter=4; +Etag: "" +X-Original-Transfer-Encoding: chunked +===DONE=== diff --git a/tests/client022.phpt b/tests/client022.phpt new file mode 100644 index 0000000..255de08 --- /dev/null +++ b/tests/client022.phpt @@ -0,0 +1,45 @@ +--TEST-- +client http2 +--SKIPIF-- + +--FILE-- +setOptions([ + "protocol" => http\Client\Curl\HTTP_VERSION_2_0, + "ssl" => [ + "cainfo" => __DIR__."/helper/http2.crt", + ] + ]); + $client->enqueue(new http\Client\Request("GET", "https://localhost:$port")); + echo $client->send()->getResponse(); +}); + +?> +===DONE=== +--EXPECTF-- +Test +HTTP/2.0 200 +%a + + + + + + HTTP2 + + + Nothing to see here. + + +===DONE=== diff --git a/tests/client023.phpt b/tests/client023.phpt new file mode 100644 index 0000000..77ea7ce --- /dev/null +++ b/tests/client023.phpt @@ -0,0 +1,39 @@ +--TEST-- +client available options and configuration +--SKIPIF-- + +--FILE-- +getOptions())) { + var_dump($options); +} +$client->setOptions($avail = $client->getAvailableOptions()); +$opt = $client->getOptions(); + +foreach ($avail as $k => $v) { + if (is_array($v)) { + $oo = $opt[$k]; + foreach ($v as $kk => $vv) { + if (isset($vv) && $oo[$kk] !== $vv) { + var_dump(array($kk => array($vv, $oo[$kk]))); + } + } + } else if (isset($v) && $opt[$k] !== $v) { + var_dump(array($k => array($v, $opt[$k]))); + } +} +var_dump($client === $client->configure($client->getAvailableConfiguration())); + +?> +===DONE=== +--EXPECT-- +Test +bool(true) +===DONE=== diff --git a/tests/client024.phpt b/tests/client024.phpt new file mode 100644 index 0000000..27a68b4 --- /dev/null +++ b/tests/client024.phpt @@ -0,0 +1,25 @@ +--TEST-- +client deprecated methods +--SKIPIF-- + +--FILE-- +enableEvents(false); +$client->enablePipelining(false); + +?> +===DONE=== +--EXPECTF-- +Test + +Deprecated: Function http\Client::enableEvents() is deprecated in %sclient024.php on line %d + +Deprecated: Function http\Client::enablePipelining() is deprecated in %sclient024.php on line %d +===DONE=== diff --git a/tests/client025.phpt b/tests/client025.phpt new file mode 100644 index 0000000..866cd2e --- /dev/null +++ b/tests/client025.phpt @@ -0,0 +1,41 @@ +--TEST-- +client seek +--SKIPIF-- + +--FILE-- +setOptions(array("resume" => 1, "expect_100_timeout" => 0)); + $request->getBody()->append("123"); + echo $client->enqueue($request)->send()->getResponse(); +}); + +?> +===DONE=== +--EXPECTF-- +Test +HTTP/1.1 200 OK +Accept-Ranges: bytes +Etag: "%x" +X-Original-Transfer-Encoding: chunked +Content-Length: %d + +PUT / HTTP/1.1 +Content-Range: bytes 1-2/3 +User-Agent: %s +Host: localhost:%d +Accept: */* +Content-Length: 3 +Expect: 100-continue +X-Original-Content-Length: 3 + +23===DONE=== diff --git a/tests/client026.phpt b/tests/client026.phpt new file mode 100644 index 0000000..1bafdc1 --- /dev/null +++ b/tests/client026.phpt @@ -0,0 +1,44 @@ +--TEST-- +client stream 128M +--SKIPIF-- + +--FILE-- +setContentType("application/octet-stream"); + for ($i = 0, $data = str_repeat("a",1024); $i < 128*1024; ++$i) { + $request->getBody()->append($data); + } + $request->setOptions(array("timeout" => 10, "expect_100_timeout" => 0)); + $client->enqueue($request); + $client->send(); + var_dump($client->getResponse()->getHeaders()); +}); + +?> +===DONE=== +--EXPECTF-- +Test +array(5) { + ["Accept-Ranges"]=> + string(5) "bytes" + ["Etag"]=> + string(%d) "%s" + ["Last-Modified"]=> + string(%d) "%s" + ["X-Original-Transfer-Encoding"]=> + string(7) "chunked" + ["Content-Length"]=> + int(134217%d%d%d) +} +===DONE=== diff --git a/tests/clientresponse001.phpt b/tests/clientresponse001.phpt index 270512b..705ee2e 100644 --- a/tests/clientresponse001.phpt +++ b/tests/clientresponse001.phpt @@ -4,6 +4,7 @@ client response cookie --FILE-- --FILE-- --FILE-- enqueue($request)->send()->getResponse(); - var_dump($response->getTransferInfo("response_code")); - var_dump(count((array)$response->getTransferInfo())); -} +server("proxy.inc", function($port) { + $request = new http\Client\Request("GET", "http://localhost:$port"); + + foreach (http\Client::getAvailableDrivers() as $driver) { + $client = new http\Client($driver); + $response = $client->enqueue($request)->send()->getResponse(); + var_dump($response->getTransferInfo("response_code")); + var_dump(count((array)$response->getTransferInfo())); + } +}); ?> Done --EXPECTREGEX-- diff --git a/tests/data/message_r_content_range.txt b/tests/data/message_r_content_range.txt new file mode 100644 index 0000000..d42dbff --- /dev/null +++ b/tests/data/message_r_content_range.txt @@ -0,0 +1,9 @@ +PUT / HTTP/1.1 +User-Agent: PECL_HTTP/2.3.0dev PHP/5.6.6-dev libcurl/7.41.0-DEV +Host: localhost:8000 +Accept: */* +Expect: 100-continue +Content-Length: 3 +Content-Range: bytes 1-2/3 + +23 \ No newline at end of file diff --git a/tests/envrequestcookie001.phpt b/tests/envrequestcookie001.phpt new file mode 100644 index 0000000..00ac9df --- /dev/null +++ b/tests/envrequestcookie001.phpt @@ -0,0 +1,32 @@ +--TEST-- +env request cookie +--SKIPIF-- + +--COOKIE-- +foo=bar;bar=123 +--FILE-- +getCookie()->toArray()); +var_dump($r->getCookie("foo", "s")); +var_dump($r->getCookie("bar", "i")); +var_dump($r->getCookie("baz", "b", true)); + +?> +DONE +--EXPECT-- +Test +array(2) { + ["foo"]=> + string(3) "bar" + ["bar"]=> + string(3) "123" +} +string(3) "bar" +int(123) +bool(true) +DONE diff --git a/tests/envresponse004.phpt b/tests/envresponse004.phpt index deee3f2..da5801d 100644 --- a/tests/envresponse004.phpt +++ b/tests/envresponse004.phpt @@ -20,6 +20,7 @@ echo "bar"; ob_end_flush(); $r->send(); +?> --EXPECTHEADERS-- Accept-Ranges: bytes Cache-Control: public,must-revalidate,max-age=0 diff --git a/tests/envresponse006.phpt b/tests/envresponse006.phpt index 76141c1..248b999 100644 --- a/tests/envresponse006.phpt +++ b/tests/envresponse006.phpt @@ -22,10 +22,15 @@ var_dump(stream_get_contents($f)); Done --EXPECT-- Test -string(77) "HTTP/1.1 200 OK +string(115) "HTTP/1.1 200 OK Accept-Ranges: bytes Foo: bar, baz ETag: "8c736521" +Transfer-Encoding: chunked -foo" +3 +foo +0 + +" Done diff --git a/tests/envresponse007.phpt b/tests/envresponse007.phpt index 5495fe2..d41be58 100644 --- a/tests/envresponse007.phpt +++ b/tests/envresponse007.phpt @@ -34,6 +34,11 @@ Accept-Ranges: bytes%c X-Powered-By: %s%c Content-Type: text/plain%c Content-Range: bytes 2-4/9%c +Transfer-Encoding: chunked%c %c -234" +3%c +234%c +0%c +%c +" Done diff --git a/tests/envresponse008.phpt b/tests/envresponse008.phpt index 379ab57..acbda89 100644 --- a/tests/envresponse008.phpt +++ b/tests/envresponse008.phpt @@ -18,6 +18,7 @@ $r->send($f); rewind($f); var_dump(stream_get_contents($f)); +?> --EXPECTREGEX-- string\(\d+\) "HTTP\/1\.1 200 OK Accept-Ranges: bytes @@ -26,5 +27,10 @@ Content-Encoding: gzip Vary: Accept-Encoding ETag: "\w+-\w+-\w+" Last-Modified: \w+, \d+ \w+ \d{4} \d\d:\d\d:\d\d GMT +Transfer-Encoding: chunked +d0 \x1f\x8b\x08.+ +0 + +" diff --git a/tests/envresponse009.phpt b/tests/envresponse009.phpt index 2a337e4..1ca5fad 100644 --- a/tests/envresponse009.phpt +++ b/tests/envresponse009.phpt @@ -24,7 +24,9 @@ Accept-Ranges: bytes%c X-Powered-By: %s%c ETag: "abc"%c Last-Modified: %s%c +Transfer-Encoding: chunked%c %c +e1%c send($f); rewind($f); var_dump(stream_get_contents($f)); ?> +%c +0%c +%c " diff --git a/tests/envresponse010.phpt b/tests/envresponse010.phpt index d365497..9b040d5 100644 --- a/tests/envresponse010.phpt +++ b/tests/envresponse010.phpt @@ -25,5 +25,10 @@ Accept-Ranges: bytes%c X-Powered-By: PHP/%s%c Content-Type: text/plain%c Content-Range: bytes 2-4/311%c +Transfer-Encoding: chunked%c %c -php" +3%c +php%c +0%c +%c +" diff --git a/tests/envresponse014.phpt b/tests/envresponse014.phpt index ef85e2c..6b71aac 100644 --- a/tests/envresponse014.phpt +++ b/tests/envresponse014.phpt @@ -16,9 +16,12 @@ $res->send($f); rewind($f); var_dump(stream_get_contents($f)); --EXPECTF-- -string(96) "HTTP/1.1 416 Requested Range Not Satisfiable +string(129) "HTTP/1.1 416 Requested Range Not Satisfiable Accept-Ranges: bytes Content-Range: bytes */6 +Transfer-Encoding: chunked + +0 " diff --git a/tests/envresponse015.phpt b/tests/envresponse015.phpt index abad2bb..1e8b15e 100644 --- a/tests/envresponse015.phpt +++ b/tests/envresponse015.phpt @@ -32,5 +32,10 @@ echo stream_get_contents($f); HTTP/1.1 200 OK Accept-Ranges: bytes ETag: "fc8305a1" +Transfer-Encoding: chunked +9 foo: bar + +0 + diff --git a/tests/envresponse017.phpt b/tests/envresponse017.phpt new file mode 100644 index 0000000..4c832f2 --- /dev/null +++ b/tests/envresponse017.phpt @@ -0,0 +1,28 @@ +--TEST-- +env response stream: no chunked transfer encoding for CONNECTs +--SKIPIF-- + +--FILE-- +setRequestMethod("CONNECT"); +$req->setRequestUrl(array("host"=>"www.example.com", "port"=>80)); + +echo $req; + +$res = new http\Env\Response; +$res->setEnvRequest($req); +$res->send(STDOUT); + +?> +===DONE=== +--EXPECTF-- +Test +CONNECT www.example.com:80 HTTP/1.1 +HTTP/1.1 200 OK + +===DONE=== diff --git a/tests/envresponse018.phpt b/tests/envresponse018.phpt new file mode 100644 index 0000000..bed8526 --- /dev/null +++ b/tests/envresponse018.phpt @@ -0,0 +1,33 @@ +--TEST-- +env response don't generate stat based etag for temp stream +--SKIPIF-- + +--FILE-- +append("1234567890\n"); + +$r = new http\Env\Response; +$r->setBody($b); +$r->send(STDOUT); + +?> +===DONE=== +--EXPECTF-- +Test +HTTP/1.1 200 OK +Accept-Ranges: bytes +ETag: "%x" +Last-Modified: %s +Transfer-Encoding: chunked + +b +1234567890 + +0 + +===DONE=== diff --git a/tests/envresponsecookie001.phpt b/tests/envresponsecookie001.phpt new file mode 100644 index 0000000..c0e93ed --- /dev/null +++ b/tests/envresponsecookie001.phpt @@ -0,0 +1,29 @@ +--TEST-- +env response cookie +--SKIPIF-- + +--FILE-- +addCookie("foo","bar"); +$c->setMaxAge(60); +$r->setCookie($c); +$r->setCookie("baz"); +$r->setCookie(123); +$r->send(STDOUT); + +?> +--EXPECT-- +HTTP/1.1 200 OK +Set-Cookie: foo=bar; max-age=60; +Set-Cookie: baz=1; +Set-Cookie: 123=1; +ETag: "" +Transfer-Encoding: chunked + +0 + diff --git a/tests/header007.phpt b/tests/header007.phpt index aa91901..7fb4dd1 100644 --- a/tests/header007.phpt +++ b/tests/header007.phpt @@ -17,5 +17,5 @@ Done --EXPECTF-- Test -Warning: http\Header::parse(): Could not parse headers in %s on line %d +Warning: http\Header::parse(): Failed to parse headers: unexpected end of line at pos 4 of 'wass\nup' in %s on line %d Done diff --git a/tests/headerparser001.phpt b/tests/headerparser001.phpt new file mode 100644 index 0000000..cdd55e4 --- /dev/null +++ b/tests/headerparser001.phpt @@ -0,0 +1,61 @@ +--TEST-- +header parser +--SKIPIF-- + +--FILE-- +"FAILURE",0=>"START","KEY","VALUE","VALUE_EX","HEADER_DONE","DONE"); +$parser = new http\Header\Parser; +do { + $state = $parser->parse($part = array_shift($headers), + $headers ? 0 : http\Header\Parser::CLEANUP, + $result); + printf("%2\$-32s | %1\$s\n", $states[$state], addcslashes($part, "\r\n\t\0")); +} while ($headers && $state !== http\Header\Parser::STATE_FAILURE); + +var_dump($result); + +?> +===DONE=== +--EXPECT-- +Test +One: | VALUE +header\n | VALUE_EX +Two: header\n\tlines\n | VALUE_EX +Three | KEY +: header\n lines\n here\n | VALUE_EX +More: than one header\n | VALUE_EX +More: | VALUE +than: | VALUE +you: | VALUE +expect\n | VALUE_EX +\n | DONE +array(4) { + ["One"]=> + string(6) "header" + ["Two"]=> + string(12) "header lines" + ["Three"]=> + string(17) "header lines here" + ["More"]=> + array(2) { + [0]=> + string(15) "than one header" + [1]=> + string(17) "than: you: expect" + } +} +===DONE=== diff --git a/tests/headerparser002.phpt b/tests/headerparser002.phpt new file mode 100644 index 0000000..67e6681 --- /dev/null +++ b/tests/headerparser002.phpt @@ -0,0 +1,59 @@ +--TEST-- +header parser errors +--SKIPIF-- + +--FILE-- +parse($header, http\Header\Parser::CLEANUP, $parsed), $parsed); +} +?> +===DONE=== +--EXPECTF-- +Test + +Warning: http\Header\Parser::parse(): Failed to parse headers: unexpected character '\000' at pos 2 of 'Na\000me' in %sheaderparser002.php on line %d +int(-1) +array(0) { +} + +Warning: http\Header\Parser::parse(): Failed to parse headers: unexpected end of line at pos 2 of 'Na\nme: value' in %sheaderparser002.php on line %d +int(-1) +array(0) { +} + +Warning: http\Header\Parser::parse(): Failed to parse headers: unexpected character '\000' at pos 0 of '\000value' in %sheaderparser002.php on line %d +int(-1) +array(0) { +} + +Warning: http\Header\Parser::parse(): Failed to parse headers: unexpected end of input at pos 5 of 'value' in %sheaderparser002.php on line %d +int(-1) +array(0) { +} + +Warning: http\Header\Parser::parse(): Failed to parse headers: unexpected character '\000' at pos 3 of 'val\000ue' in %sheaderparser002.php on line %d +int(-1) +array(0) { +} + +Warning: http\Header\Parser::parse(): Failed to parse headers: unexpected character '\000' at pos 5 of 'value\000' in %sheaderparser002.php on line %d +int(-1) +array(0) { +} +===DONE=== \ No newline at end of file diff --git a/tests/headerparser003.phpt b/tests/headerparser003.phpt new file mode 100644 index 0000000..1216fac --- /dev/null +++ b/tests/headerparser003.phpt @@ -0,0 +1,54 @@ +--TEST-- +header parser with nonblocking stream +--SKIPIF-- + +--FILE-- +stream($socket[0], 0, $hdrs); + fwrite($socket[1], $line); + var_dump($parser->getState()); + var_dump($parser->stream($socket[0], 0, $hdrs)); +} + +var_dump($hdrs); + +?> +DONE +--EXPECT-- +Test +int(0) +int(1) +int(1) +int(2) +int(2) +int(3) +int(3) +int(1) +int(1) +int(3) +int(3) +int(5) +array(2) { + ["Host"]=> + string(9) "localhost" + ["Content-Length"]=> + string(1) "3" +} +DONE diff --git a/tests/helper/cookie.inc b/tests/helper/cookie.inc new file mode 100644 index 0000000..d33666b --- /dev/null +++ b/tests/helper/cookie.inc @@ -0,0 +1,11 @@ +getHeader("cookie")); + $response = new http\Env\Response; + $response->setCookie($cookies->setCookie("counter", $cookies->getCookie("counter")+1)); + $response->send($client); +}); diff --git a/tests/helper/html/index.html b/tests/helper/html/index.html new file mode 100644 index 0000000..11280ba --- /dev/null +++ b/tests/helper/html/index.html @@ -0,0 +1,10 @@ + + + + + HTTP2 + + + Nothing to see here. + + diff --git a/tests/helper/http2.crt b/tests/helper/http2.crt new file mode 100644 index 0000000..c297162 --- /dev/null +++ b/tests/helper/http2.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDNzCCAh+gAwIBAgIJAKOw1awbt7aIMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNV +BAYTAkFUMQ8wDQYDVQQKDAZQSFAgUUExEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0x +NTAyMTIxMjQ2NTRaFw0xNzExMDcxMjQ2NTRaMDIxCzAJBgNVBAYTAkFUMQ8wDQYD +VQQKDAZQSFAgUUExEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB +BQADggEPADCCAQoCggEBAMxa+A6xEKQOYme55nQyu0qpvvGB4c4wGBNa6X6YAEzE +Hc19Nbix02UZWQgHM1dmBhbVDW3stO42CQmcViIKfAy5R1A9UZjUcn9nQpaxL/sp +9LrrCANyljISXS40QcBvhCzmcUvDWBRhVTGjV+QTaYmmaM+8hJJJM6W7ByIP22Zv +jHoAnzTx9sjs+OMyWmjYT9pWv6aE+u5iSC8bn3B0GgtfPwoPqDF8ePxIMBpmtbk7 +JqXFzHxTVywkypnsF34XK94QjpvRcFGSg/Dc1TopJG2Wfq8hxwtoKerSlL5tyKy0 +ZrltxCiLkfVZixwNZEqkg7jPSUvLS299mBrwy+ikmr8CAwEAAaNQME4wHQYDVR0O +BBYEFDiHynoXXjMChfYhc1k7TNtU8ey0MB8GA1UdIwQYMBaAFDiHynoXXjMChfYh +c1k7TNtU8ey0MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGD9GERC +uJv+oHfMwkNkDV5ZB4F+SQPzXVxDx+rJm1aGJs9PcwPNiV5GweXbvgcnvRAL4h8h +uv3MLQPgVOq0iqp1QPFCoUXxbYYjYzi9FVbR/154sg0uWEHElU2e3fSjcinNRfXD +12232k6HNwSeeZUFQqn2fuk+cae5BsYT8GfsyMq5EfPtG2d8IG+Ji4eEOJeKu4gl +Y33yQnHhw6QKbx8vWaDpZK8qtpapCtLrTtw/nRhX5YV6kMGK+htQUK1etV2O0VQQ +OtDrhOWWaDxQULsHIvCMgTTnZqNQD+Xz4MBm3PGEGi/QUsrEaSQYppb8xxQKZU4X +MyTh7rO5TdNSXmo= +-----END CERTIFICATE----- diff --git a/tests/helper/http2.key b/tests/helper/http2.key new file mode 100644 index 0000000..bcf4f3f --- /dev/null +++ b/tests/helper/http2.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAzFr4DrEQpA5iZ7nmdDK7Sqm+8YHhzjAYE1rpfpgATMQdzX01 +uLHTZRlZCAczV2YGFtUNbey07jYJCZxWIgp8DLlHUD1RmNRyf2dClrEv+yn0uusI +A3KWMhJdLjRBwG+ELOZxS8NYFGFVMaNX5BNpiaZoz7yEkkkzpbsHIg/bZm+MegCf +NPH2yOz44zJaaNhP2la/poT67mJILxufcHQaC18/Cg+oMXx4/EgwGma1uTsmpcXM +fFNXLCTKmewXfhcr3hCOm9FwUZKD8NzVOikkbZZ+ryHHC2gp6tKUvm3IrLRmuW3E +KIuR9VmLHA1kSqSDuM9JS8tLb32YGvDL6KSavwIDAQABAoIBAQCzUdAB9FYJ36Vy +J6qVpD69EZ7ABZzDdWhq84eY0oDQ2/ba7lhJraE2QbviU481zgzh1CponyFVNo1P +paPfUxvvflWZj3Ueiq2+JjpESU81MmfR7ZOmktJBNeQWOzzHRBPT4pLgTJXprE85 +s3/YX0BozWGDiIU8aIetkiR8OzXm97+BrJWiPFl733dGnHvfpHAZu/PwKZc2+8ft +CnQw4GHRhTBWCVNj29HLwm+CA66zQqiAXItgijWMKzs+9ciPn+aCsCnZDNF+o1zs +4pWt60CtIJQtD3r3rSRy7nBaCKcTrr/JU3FvwqKdunuUBUsuYeSaMBokW67kpVzS +dO9L9p6BAoGBAP+pvcAd8qfC1WIrn9vka3aK25ulbYSCae3YaWmABc93INJPBMvO +GrcUuaLKiQC1oou+E64vGyJ9MeEFwxh2zbvU75a9ezeKAajgaq92ciMX2QqREh0N +IntNOc4w7eB6BK8NpaDXNvTtxNWMvlYyhVN0M7FVQRmYJfCJdnTZAkzvAoGBAMyf +6rvWuc/wmIcAtBVe+jIeQ69EJJ/Idcjk0JUm6lFECAAraPpsCglha+wTHWWRQZ9u +pPqBnb7QPjevU+3bZHnMvGoEzd5F4Rw74J+p5EZeMUJpuKmon7Ekzemcb0DV+qX9 +NorB781D2Z0MG9SCptKyKpN5TPHTjGz4BB3mLC8xAoGAdq99/ynn9ClmleRameI4 +YRelS2RIqzM/qcLFbMyZ5e4PtpIoT9SmYkekxgXwA/xOMUFUMZB8sE4eUbAzGbBN +Yd1APGJKSUYv7w3/eOUrp07y2wzts77dOxBmvWnJhGQguINFWJ2QTbPzpI9p7OoX +Kt7PAIvrZM5VDo1CCIyVnNECgYEAgLmpZXlzcwicK3GZ2EfjhVvcoIlxsMLetf6b +6PiON4lgrxqf88m7lqMezWhI+fgjHDTyvFSF89/1A/rcBaoazzSo4tka2VWEg8p3 +SHoMDOh8fJcdgD2AGGRa1TeAFX2HLJzajvfp72tbnpxbdZircah7eEK60PaQRIzR +qi1+ZkECgYEAi7GkO7Ey98DppLnt7567veQoEj0u8ixTlCyJ4V278nHR5+6eAZP5 +PfdZVC3MtKGLnxrrPTVUy5wU0hR6Gk9EVDmrAF8TgJdnZFlBK7X23eWZ0q4qO/eO +3xIi+UrNwLag8BjYOr32nP/i+F+TLikgRIFR4oiZjk867+ofkTXmNFA= +-----END RSA PRIVATE KEY----- diff --git a/tests/helper/pipeline.inc b/tests/helper/pipeline.inc new file mode 100644 index 0000000..b7175c1 --- /dev/null +++ b/tests/helper/pipeline.inc @@ -0,0 +1,26 @@ +setEnvRequest($msg) + ->setHeader("X-Req", $msg->getRequestUrl()) + ->send($client); +} + +serve(function($client) { + $count = trim(fgets(STDIN)); + + /* the peek message */ + respond($client, new http\Message($client, false)); + + /* pipelined messages */ + $req = array(); + for ($i=0; $i < $count; ++ $i) { + $req[] = new http\Message($client, false); + } + foreach ($req as $msg) { + respond($client, $msg); + } +}); diff --git a/tests/helper/proxy.inc b/tests/helper/proxy.inc new file mode 100644 index 0000000..80a0073 --- /dev/null +++ b/tests/helper/proxy.inc @@ -0,0 +1,23 @@ +getHeader("Proxy-Connection")) { + $response = new http\Env\Response; + $response->setEnvRequest($request); + $response->send($client); + + /* soak up the request following the connect */ + new http\Message($client, false); + } + + /* return the initial message as response body */ + $response = new http\Env\Response; + /* avoid OOM with $response->getBody()->append($request); */ + $request->toStream($response->getBody()->getResource()); + $response->send($client); +}); diff --git a/tests/helper/server.inc b/tests/helper/server.inc new file mode 100644 index 0000000..aa8559f --- /dev/null +++ b/tests/helper/server.inc @@ -0,0 +1,99 @@ +getMessage() !== "Empty message received from stream") { + fprintf(STDERR, "%s\n", $ex); + } + break; + } + } + } while ($select !== false); + return; + } + } +} + +function server($handler, $cb) { + proc(PHP_BINARY, array(__DIR__."/$handler"), $cb); +} + +function nghttpd($cb) { + $spec = array(array("pipe","r"), array("pipe","w"), array("pipe","w")); + foreach (range(8000, 9000) as $port) { + $comm = "exec nghttpd -d html $port http2.key http2.crt"; + if (($proc = proc_open($comm, $spec, $pipes, __DIR__))) { + $stdin = $pipes[0]; + $stdout = $pipes[1]; + $stderr = $pipes[2]; + + usleep(50000); + $status = proc_get_status($proc); + + if (!$status["running"]) { + continue; + } + + try { + $cb($port, $stdin, $stdout, $stderr); + } catch (Exception $e) { + echo $e,"\n"; + } + + proc_terminate($proc); + + fpassthru($stderr); + fpassthru($stdout); + return; + } + } + +} + +function proc($bin, $args, $cb) { + $spec = array(array("pipe","r"), array("pipe","w"), array("pipe","w")); + $comm = escapeshellcmd($bin) . " ". implode(" ", array_map("escapeshellarg", $args)); + if (($proc = proc_open($comm, $spec, $pipes, __DIR__))) { + $stdin = $pipes[0]; + $stdout = $pipes[1]; + $stderr = $pipes[2]; + + do { + $port = trim(fgets($stderr)); + $R = array($stderr); $W = array(); $E = array(); + } while (is_numeric($port) && stream_select($R, $W, $E, 0, 10000)); + + if (is_numeric($port)) { + try { + $cb($port, $stdin, $stdout, $stderr); + } catch (Exception $e) { + echo $e,"\n"; + } + } + + proc_terminate($proc); + + fpassthru($stderr); + fpassthru($stdout); + } +} \ No newline at end of file diff --git a/tests/info.phpt b/tests/info.phpt deleted file mode 100644 index 373cb45..0000000 --- a/tests/info.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -phpinfo ---SKIPIF-- - ---FILE-- - -Done ---EXPECTF-- -Test -%a -HTTP Support => enabled -Extension Version => 2.%s -%a -Done diff --git a/tests/info001.phpt b/tests/info001.phpt new file mode 100644 index 0000000..8209669 --- /dev/null +++ b/tests/info001.phpt @@ -0,0 +1,60 @@ +--TEST-- +invalid HTTP info +--SKIPIF-- + +--FILE-- + +DONE +--EXPECTF-- +exception 'http\Exception\BadMessageException' with message 'http\Message::__construct(): Failed to parse headers: unexpected character '\040' at pos 3 of 'GET HTTP/1.1'' in %s +Stack trace: +#0 %s: http\Message->__construct('GET HTTP/1.1') +#1 {main} +exception 'http\Exception\BadMessageException' with message 'http\Message::__construct(): Failed to parse headers: unexpected character '\040' at pos 3 of 'GET HTTP/1.123'' in %s +Stack trace: +#0 %s: http\Message->__construct('GET HTTP/1.123') +#1 {main} +exception 'http\Exception\BadMessageException' with message 'http\Message::__construct(): Failed to parse headers: unexpected character '\057' at pos 7 of 'GETHTTP/1.1'' %s +Stack trace: +#0 %s: http\Message->__construct('GETHTTP/1.1') +#1 {main} +object(http\Message)#%d (9) { + ["type":protected]=> + int(1) + ["body":protected]=> + NULL + ["requestMethod":protected]=> + string(3) "GET" + ["requestUrl":protected]=> + string(1) "/" + ["responseStatus":protected]=> + string(0) "" + ["responseCode":protected]=> + int(0) + ["httpVersion":protected]=> + string(3) "1.1" + ["headers":protected]=> + array(0) { + } + ["parentMessage":protected]=> + NULL +} +DONE diff --git a/tests/info002.phpt b/tests/info002.phpt new file mode 100644 index 0000000..093dcd1 --- /dev/null +++ b/tests/info002.phpt @@ -0,0 +1,49 @@ +--TEST-- +invalid HTTP info +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECTF-- +Test +exception 'http\Exception\BadMessageException' with message 'http\Message::__construct(): Failed to parse headers: unexpected character '\057' at pos 4 of 'HTTP/1.1 99 Apples in my Basket'' in %sinfo002.php:%d +Stack trace: +#0 %sinfo002.php(%d): http\Message->__construct('HTTP/1.1 99 App...') +#1 %sinfo002.php(%d): {closure}() +#2 %sinfo002.php(%d): trap(Object(Closure)) +#3 {main} +exception 'http\Exception\BadMessageException' with message 'http\Message::__construct(): Failed to parse headers: unexpected character '\040' at pos 7 of 'CONNECT HTTP/1.1'' in %sinfo002.php:%d +Stack trace: +#0 %sinfo002.php(%d): http\Message->__construct('CONNECT HTTP/1....') +#1 %sinfo002.php(%d): {closure}() +#2 %sinfo002.php(%d): trap(Object(Closure)) +#3 {main} +HTTP/1.1 200 +CONNECT www.example.org:80 HTTP/1.1 +===DONE=== diff --git a/tests/info_001.phpt b/tests/info_001.phpt deleted file mode 100644 index 370d70e..0000000 --- a/tests/info_001.phpt +++ /dev/null @@ -1,60 +0,0 @@ ---TEST-- -invalid HTTP info ---SKIPIF-- - ---FILE-- - -DONE ---EXPECTF-- -exception 'http\Exception\BadMessageException' with message 'Could not parse message: GET HTTP/1.1' in %s -Stack trace: -#0 %s: http\Message->__construct('GET HTTP/1.1') -#1 {main} -exception 'http\Exception\BadMessageException' with message 'Could not parse message: GET HTTP/1.123' in %s -Stack trace: -#0 %s: http\Message->__construct('GET HTTP/1.123') -#1 {main} -exception 'http\Exception\BadMessageException' with message 'Could not parse message: GETHTTP/1.1' in %s -Stack trace: -#0 %s: http\Message->__construct('GETHTTP/1.1') -#1 {main} -object(http\Message)#%d (9) { - ["type":protected]=> - int(1) - ["body":protected]=> - NULL - ["requestMethod":protected]=> - string(3) "GET" - ["requestUrl":protected]=> - string(1) "/" - ["responseStatus":protected]=> - string(0) "" - ["responseCode":protected]=> - int(0) - ["httpVersion":protected]=> - string(3) "1.1" - ["headers":protected]=> - array(0) { - } - ["parentMessage":protected]=> - NULL -} -DONE diff --git a/tests/message001.phpt b/tests/message001.phpt index 230fd6b..0214dfa 100644 --- a/tests/message001.phpt +++ b/tests/message001.phpt @@ -184,7 +184,7 @@ array(10) { ["Accept-Ranges"]=> string(5) "bytes" ["Content-Length"]=> - int(0) + string(1) "0" ["Vary"]=> string(15) "Accept-Encoding" ["Connection"]=> @@ -197,7 +197,6 @@ array(10) { GET /default/empty.txt HTTP/1.1 Host: localhost Connection: close -Content-Length: 0 HTTP/1.1 200 OK Date: Thu, 26 Aug 2010 09:55:09 GMT Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6 @@ -205,7 +204,6 @@ Last-Modified: Wed, 28 Apr 2010 10:54:37 GMT Etag: "2002a-0-48549d615a35c" Accept-Ranges: bytes Vary: Accept-Encoding -Content-Length: 0 Connection: close Content-Type: text/plain X-Original-Content-Length: 20 @@ -214,7 +212,7 @@ string(3) "1.1" bool(true) int(200) string(2) "OK" -array(11) { +array(10) { ["Date"]=> string(29) "Thu, 26 Aug 2010 09:55:09 GMT" ["Server"]=> @@ -227,8 +225,6 @@ array(11) { string(5) "bytes" ["Vary"]=> string(15) "Accept-Encoding" - ["Content-Length"]=> - int(0) ["Connection"]=> string(5) "close" ["Content-Type"]=> @@ -242,7 +238,6 @@ GET /default/empty.txt HTTP/1.1 Host: localhost Accept-Encoding: gzip Connection: close -Content-Length: 0 HTTP/1.1 200 OK Date: Thu, 26 Aug 2010 11:41:02 GMT Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6 @@ -277,7 +272,6 @@ array(8) { GET /default/empty.php HTTP/1.1 Connection: close Host: localhost -Content-Length: 0 HTTP/1.1 200 OK Date: Thu, 26 Aug 2010 12:51:28 GMT Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6 @@ -311,7 +305,6 @@ array(7) { GET /cgi-bin/chunked.sh HTTP/1.1 Host: localhost Connection: close -Content-Length: 0 --- HTTP/1.1 200 OK Date: Wed, 25 Aug 2010 12:11:44 GMT @@ -340,7 +333,7 @@ array(10) { ["Accept-Ranges"]=> string(5) "bytes" ["Content-Length"]=> - int(0) + string(1) "0" ["Vary"]=> string(15) "Accept-Encoding" ["Connection"]=> @@ -353,7 +346,6 @@ array(10) { GET /default/empty.txt HTTP/1.1 Host: localhost Connection: close -Content-Length: 0 HTTP/1.1 200 OK Date: Thu, 26 Aug 2010 09:55:09 GMT Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6 @@ -361,7 +353,6 @@ Last-Modified: Wed, 28 Apr 2010 10:54:37 GMT Etag: "2002a-0-48549d615a35c" Accept-Ranges: bytes Vary: Accept-Encoding -Content-Length: 0 Connection: close Content-Type: text/plain X-Original-Content-Length: 20 @@ -370,7 +361,7 @@ string(3) "1.1" bool(true) int(200) string(2) "OK" -array(11) { +array(10) { ["Date"]=> string(29) "Thu, 26 Aug 2010 09:55:09 GMT" ["Server"]=> @@ -383,8 +374,6 @@ array(11) { string(5) "bytes" ["Vary"]=> string(15) "Accept-Encoding" - ["Content-Length"]=> - int(0) ["Connection"]=> string(5) "close" ["Content-Type"]=> @@ -398,7 +387,6 @@ GET /default/empty.txt HTTP/1.1 Host: localhost Accept-Encoding: gzip Connection: close -Content-Length: 0 HTTP/1.1 200 OK Date: Thu, 26 Aug 2010 11:41:02 GMT Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6 @@ -433,7 +421,6 @@ array(8) { GET /default/empty.php HTTP/1.1 Connection: close Host: localhost -Content-Length: 0 HTTP/1.1 200 OK Date: Thu, 26 Aug 2010 12:51:28 GMT Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6 @@ -467,5 +454,4 @@ array(7) { GET /cgi-bin/chunked.sh HTTP/1.1 Host: localhost Connection: close -Content-Length: 0 Done diff --git a/tests/message002.phpt b/tests/message002.phpt index 0809676..e16f8dc 100644 --- a/tests/message002.phpt +++ b/tests/message002.phpt @@ -33,7 +33,7 @@ var_dump(file_get_contents("php://input")); Done --EXPECTF-- Test -object(%s)#%d (12) { +object(%s)#%d (13) { ["type":protected]=> int(1) ["body":protected]=> @@ -62,17 +62,25 @@ object(%s)#%d (12) { ["parentMessage":protected]=> NULL ["query":protected]=> - object(http\QueryString)#2 (1) { + object(http\QueryString)#%d (1) { ["queryArray":"http\QueryString":private]=> array(0) { } } ["form":protected]=> - object(http\QueryString)#3 (1) { + object(http\QueryString)#%d (1) { ["queryArray":"http\QueryString":private]=> array(0) { } } + ["cookie":protected]=> + object(http\QueryString)#%d (1) { + ["queryArray":"http\QueryString":private]=> + array(1) { + ["foo"]=> + string(3) "bar" + } + } ["files":protected]=> array(0) { } diff --git a/tests/message016.phpt b/tests/message016.phpt new file mode 100644 index 0000000..c29d8d6 --- /dev/null +++ b/tests/message016.phpt @@ -0,0 +1,24 @@ +--TEST-- +message content range +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECT-- +Test +PUT / HTTP/1.1 +User-Agent: PECL_HTTP/2.3.0dev PHP/5.6.6-dev libcurl/7.41.0-DEV +Host: localhost:8000 +Accept: */* +Expect: 100-continue +Content-Length: 3 +Content-Range: bytes 1-2/3 +X-Original-Content-Length: 3 + +23===DONE=== diff --git a/tests/messageparser001.phpt b/tests/messageparser001.phpt index f4ec2d9..1167c64 100644 --- a/tests/messageparser001.phpt +++ b/tests/messageparser001.phpt @@ -12,6 +12,7 @@ echo "Test\n"; use http\Message\Parser; foreach (glob(__DIR__."/data/message_*.txt") as $file) { + $string = ""; $parser = new Parser; $fd = fopen($file, "r") or die("Could not open $file"); while (!feof($fd)) { @@ -23,6 +24,11 @@ foreach (glob(__DIR__."/data/message_*.txt") as $file) { throw new Exception(($e = error_get_last()) ? $e["message"] : "Could not parse $file"); } } + + if (!$string) { + $s = array("START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "UPDATE_CL", "DONE"); + printf("Unexpected state: %s (%s)\n", $s[$parser->getState()], $file); + } $parser = new Parser; rewind($fd); @@ -38,12 +44,14 @@ foreach (glob(__DIR__."/data/message_*.txt") as $file) { if ($string !== (string) $message) { $a = explode("\n", $string); $b = explode("\n", (string) $message); - while ((null !== ($aa = array_shift($a))) || (null !== ($bb = array_shift($b)))) { + do { + $aa = array_shift($a); + $bb = array_shift($b); if ($aa !== $bb) { isset($aa) and printf("-- %s\n", $aa); isset($bb) and printf("++ %s\n", $bb); } - } + } while ($a || $b); } } ?> diff --git a/tests/messageparser002.phpt b/tests/messageparser002.phpt index 2030e93..71f2b5e 100644 --- a/tests/messageparser002.phpt +++ b/tests/messageparser002.phpt @@ -54,7 +54,7 @@ object(http\Message)#%d (9) { ["Host"]=> string(9) "localhost" ["Content-Length"]=> - int(3) + string(1) "3" ["X-Original-Content-Length"]=> string(1) "3" } diff --git a/tests/phpinfo.phpt b/tests/phpinfo.phpt new file mode 100644 index 0000000..373cb45 --- /dev/null +++ b/tests/phpinfo.phpt @@ -0,0 +1,19 @@ +--TEST-- +phpinfo +--SKIPIF-- + +--FILE-- + +Done +--EXPECTF-- +Test +%a +HTTP Support => enabled +Extension Version => 2.%s +%a +Done diff --git a/tests/propertyproxy001.phpt b/tests/propertyproxy001.phpt index 552a713..1001f8e 100644 --- a/tests/propertyproxy001.phpt +++ b/tests/propertyproxy001.phpt @@ -4,6 +4,8 @@ property proxy +--XFAIL-- +TBD --FILE-- diff --git a/tests/urlparser002.phpt b/tests/urlparser002.phpt index be1cd66..92114dc 100644 --- a/tests/urlparser002.phpt +++ b/tests/urlparser002.phpt @@ -22,7 +22,7 @@ $urls = array( foreach ($urls as $url) { printf("\n%s\n", $url); - var_dump(http\Url::parse($url)); + var_dump(new http\Url($url, null, 0)); } ?> DONE diff --git a/tests/urlparser003.phpt b/tests/urlparser003.phpt index 68b1e4a..9a368a7 100644 --- a/tests/urlparser003.phpt +++ b/tests/urlparser003.phpt @@ -25,7 +25,7 @@ $urls = array( foreach ($urls as $url) { printf("\n%s\n", $url); - var_dump(http\Url::parse($url)); + var_dump(new http\Url($url, null, 0)); } ?> DONE diff --git a/tests/urlparser004.phpt b/tests/urlparser004.phpt index 3aa57fd..d1cd1ee 100644 --- a/tests/urlparser004.phpt +++ b/tests/urlparser004.phpt @@ -20,7 +20,7 @@ $urls = array( foreach ($urls as $url) { printf("\n%s\n", $url); - var_dump(http\Url::parse($url, http\Url::PARSE_MBLOC)); + var_dump(new http\Url($url, null, http\Url::PARSE_MBLOC)); } ?> DONE diff --git a/tests/urlparser005.phpt b/tests/urlparser005.phpt index ff18fe4..d6ec61f 100644 --- a/tests/urlparser005.phpt +++ b/tests/urlparser005.phpt @@ -16,7 +16,7 @@ $urls = array( foreach ($urls as $url) { printf("\n%s\n", $url); - var_dump(http\Url::parse($url, http\Url::PARSE_MBUTF8)); + var_dump(new http\Url($url, null, http\Url::PARSE_MBUTF8)); } ?> DONE diff --git a/tests/urlparser006.phpt b/tests/urlparser006.phpt index 72ee358..c47e6a2 100644 --- a/tests/urlparser006.phpt +++ b/tests/urlparser006.phpt @@ -21,7 +21,7 @@ $urls = array( foreach ($urls as $url) { printf("\n%s\n", $url); - var_dump(http\Url::parse($url, http\Url::PARSE_MBLOC|http\Url::PARSE_TOIDN)); + var_dump(new http\Url($url, null, http\Url::PARSE_MBLOC|http\Url::PARSE_TOIDN)); } ?> DONE diff --git a/tests/urlparser007.phpt b/tests/urlparser007.phpt index 518bb72..d7ec657 100644 --- a/tests/urlparser007.phpt +++ b/tests/urlparser007.phpt @@ -19,7 +19,7 @@ $urls = array( foreach ($urls as $url) { printf("\n%s\n", $url); - var_dump(http\Url::parse($url, http\Url::PARSE_MBUTF8|http\Url::PARSE_TOIDN)); + var_dump(new http\Url($url, null, http\Url::PARSE_MBUTF8|http\Url::PARSE_TOIDN)); } ?> DONE diff --git a/tests/urlparser008.phpt b/tests/urlparser008.phpt index 98382f4..9646fa0 100644 --- a/tests/urlparser008.phpt +++ b/tests/urlparser008.phpt @@ -18,7 +18,7 @@ $urls = array( foreach ($urls as $url) { try { printf("\n%s\n", $url); - var_dump(http\Url::parse($url)); + var_dump(new http\Url($url, null, 0)); } catch (Exception $e) { echo $e->getMessage(),"\n"; } @@ -29,10 +29,10 @@ DONE Test s://[a:80 -http\Url::parse(): Failed to parse hostinfo; expected ']' +http\Url::__construct(): Failed to parse hostinfo; expected ']' s://[0] -http\Url::parse(): Failed to parse hostinfo; unexpected '[' +http\Url::__construct(): Failed to parse hostinfo; unexpected '[' s://[::1]:80 object(http\Url)#%d (8) { diff --git a/tests/urlparser009.phpt b/tests/urlparser009.phpt index f3e2b83..57080f4 100644 --- a/tests/urlparser009.phpt +++ b/tests/urlparser009.phpt @@ -26,7 +26,7 @@ $urls = array( foreach ($urls as $url) { try { printf("\n%s\n", $url); - var_dump(http\Url::parse($url)); + var_dump(new http\Url($url, null, 0)); } catch (Exception $e) { echo $e->getMessage(),"\n"; } diff --git a/tests/urlparser010.phpt b/tests/urlparser010.phpt index a82b7a8..ae63ae7 100644 --- a/tests/urlparser010.phpt +++ b/tests/urlparser010.phpt @@ -1,19 +1,24 @@ --TEST-- -url parser multibyte/utf-8/topct +url parser multibyte/locale/topct --SKIPIF-- --FILE-- DONE @@ -27,7 +32,7 @@ object(http\Url)#%d (8) { ["pass"]=> string(12) "pa%C3%9Fwort" ["host"]=> - string(11) "sörver.net" + string(15) "𐌀𐌁𐌂.it" ["port"]=> NULL ["path"]=> diff --git a/tests/urlparser011.phpt b/tests/urlparser011.phpt new file mode 100644 index 0000000..5dbd82a --- /dev/null +++ b/tests/urlparser011.phpt @@ -0,0 +1,40 @@ +--TEST-- +url parser multibyte/utf-8/topct +--SKIPIF-- + +--FILE-- + +DONE +--EXPECTF-- +Test +object(http\Url)#%d (8) { + ["scheme"]=> + string(4) "http" + ["user"]=> + string(4) "mike" + ["pass"]=> + string(12) "pa%C3%9Fwort" + ["host"]=> + string(15) "𐌀𐌁𐌂.it" + ["port"]=> + NULL + ["path"]=> + string(15) "/for/%E2%82%AC/" + ["query"]=> + string(9) "by=%C2%A2" + ["fragment"]=> + string(6) "%C3%B8" +} +DONE