From: Michael Wallner Date: Wed, 18 Feb 2015 09:33:35 +0000 (+0100) Subject: Merge branch 'master' into phpng X-Git-Tag: RELEASE_3_0_0_RC1~56 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=commitdiff_plain;h=95c6c900d04096d332c422e3f597186b7184c5ab;hp=1bef05c9e90799c57722bdb4865e6829373fee20 Merge branch 'master' into phpng Conflicts: package.xml php_http_client_curl.c php_http_env.c php_http_env_response.c php_http_header_parser.c php_http_message.c php_http_message_body.c php_http_message_parser.c php_http_misc.c php_http_strlist.c php_http_url.h tests/info.phpt --- 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/config9.m4 b/config9.m4 index 46f70f9..8a10118 100644 --- a/config9.m4 +++ b/config9.m4 @@ -547,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 \ " @@ -599,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/package.xml b/package.xml index 726d2c2..8fcb537 100644 --- a/package.xml +++ b/package.xml @@ -38,11 +38,51 @@ http://devel-m6w6.rhcloud.com/mdref/http 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 ]]> @@ -110,8 +150,7 @@ http://devel-m6w6.rhcloud.com/mdref/http - - + @@ -120,17 +159,31 @@ http://devel-m6w6.rhcloud.com/mdref/http - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + @@ -147,6 +200,16 @@ http://devel-m6w6.rhcloud.com/mdref/http + + + + + + + + + + @@ -202,6 +265,8 @@ http://devel-m6w6.rhcloud.com/mdref/http + + @@ -220,8 +285,11 @@ http://devel-m6w6.rhcloud.com/mdref/http - - + + + + + @@ -237,6 +305,7 @@ http://devel-m6w6.rhcloud.com/mdref/http + @@ -265,6 +334,7 @@ http://devel-m6w6.rhcloud.com/mdref/http + @@ -284,6 +354,7 @@ http://devel-m6w6.rhcloud.com/mdref/http + diff --git a/php_http.c b/php_http.c index 7a28933..98df843 100644 --- a/php_http.c +++ b/php_http.c @@ -134,6 +134,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_api.h b/php_http_api.h index 5aa96b7..68fdac8 100644 --- a/php_http_api.h +++ b/php_http_api.h @@ -76,7 +76,6 @@ #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_client.c b/php_http_client.c index 22fc6ca..467ed2a 100644 --- a/php_http_client.c +++ b/php_http_client.c @@ -814,6 +814,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(), "|H!", &settings), invalid_arg, return); + obj = PHP_HTTP_OBJ(NULL, getThis()); + + 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(); @@ -1139,6 +1155,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 = PHP_HTTP_OBJ(NULL, getThis()); + + 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 = PHP_HTTP_OBJ(NULL, getThis()); + + 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) @@ -1151,8 +1191,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) @@ -1168,6 +1209,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 41671b9..792581a 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 { diff --git a/php_http_client_curl.c b/php_http_client_curl.c index c507799..df832ec 100644 --- a/php_http_client_curl.c +++ b/php_http_client_curl.c @@ -13,7 +13,7 @@ #include "php_http_api.h" #include "php_http_client.h" -#if 1||PHP_HTTP_HAVE_CURL +#if PHP_HTTP_HAVE_CURL #if PHP_HTTP_HAVE_EVENT # if !PHP_HTTP_HAVE_EVENT2 && /* just be really sure */ !(LIBEVENT_VERSION_NUMBER >= 0x02000000) @@ -833,7 +833,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 @@ -962,6 +962,9 @@ static ZEND_RESULT_CODE php_http_curle_option_set_compress(php_http_option_t *op php_http_client_curl_handler_t *curl = userdata; CURL *ch = curl->handle; +#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_TYPE_P(val) == IS_TRUE ? "" : NULL)) { return FAILURE; } @@ -1509,8 +1512,9 @@ static zval *php_http_curle_get_option(php_http_option_t *opt, HashTable *option zval zopt; ZVAL_DUP(&zopt, option); - convert_to_explicit_type(option, opt->type); + convert_to_explicit_type(&zopt, opt->type); zend_hash_update(&curl->options.cache, opt->name, &zopt); + return zend_hash_find(&curl->options.cache, opt->name); } return option; } @@ -1599,6 +1603,187 @@ static ZEND_RESULT_CODE php_http_curle_set_option(php_http_option_t *opt, zval * return rv; } +#if PHP_HTTP_CURL_VERSION(7,30,0) +static ZEND_RESULT_CODE 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; + 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); + + ZEND_HASH_FOREACH_VAL(&tmp_ht, entry) + { + *ptr++ = Z_STRVAL_P(entry); + } + ZEND_HASH_FOREACH_END(); + } + + 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 ZEND_RESULT_CODE 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 ZEND_RESULT_CODE 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_TYPE_P(value) == IS_TRUE); +} +#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 ZEND_RESULT_CODE 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; + ZEND_RESULT_CODE rv = SUCCESS; + + 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)) { + zval zopt; + + ZVAL_DUP(&zopt, val); + convert_to_explicit_type(&zopt, opt->type); + + val = &zopt; + } + + 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) zend_is_true(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->val, curl_easy_strerror(rc)); + } + return rv; +} /* client ops */ @@ -1912,24 +2097,40 @@ static void queue_dtor(php_http_client_enqueue_t *e) php_http_client_curl_handler_dtor(handler); } -static php_resource_factory_t *create_rf(php_http_url_t *url) +static php_resource_factory_t *create_rf(php_http_client_t *h, php_http_client_enqueue_t *enqueue) { - php_persistent_handle_factory_t *pf; + php_persistent_handle_factory_t *pf = NULL; php_resource_factory_t *rf = NULL; - zend_string *id; - char *id_str = NULL; - size_t id_len; + php_http_url_t *url = enqueue->request->http.info.request.url; if (!url || (!url->host && !url->path)) { php_error_docref(NULL, E_WARNING, "Cannot request empty URL"); return NULL; } - id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), url->port ? url->port : 80); - id = php_http_cs2zs(id_str, id_len); + /* only if the client itself is setup for persistence */ + if (h->rf->dtor == (void (*)(void*)) php_persistent_handle_abandon) { + zend_string *id; + char *id_str = NULL; + size_t id_len; + int port = url->port ? url->port : 80; + zval *zport; + + id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), url->port ? url->port : 80); + id = php_http_cs2zs(id_str, id_len); + + if ((zport = zend_hash_str_find(enqueue->options, ZEND_STRL("port")))) { + zend_long lport = zval_get_long(zport); + + if (lport > 0) { + port = lport; + } + } - pf = php_persistent_handle_concede(NULL, PHP_HTTP_G->client.curl.driver.request_name, id, NULL, NULL); - zend_string_release(id); + id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), port); + pf = php_persistent_handle_concede(NULL, PHP_HTTP_G->client.curl.driver.request_name, id, NULL, NULL); + zend_string_release(id); + } if (pf) { rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon); @@ -1948,7 +2149,7 @@ static ZEND_RESULT_CODE php_http_client_curl_enqueue(php_http_client_t *h, php_h php_http_client_progress_state_t *progress; php_resource_factory_t *rf; - rf = create_rf(enqueue->request->http.info.request.url); + rf = create_rf(h, enqueue); if (!rf) { return FAILURE; } @@ -2133,6 +2334,10 @@ static ZEND_RESULT_CODE php_http_client_curl_setopt(php_http_client_t *h, php_ht 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; @@ -2141,23 +2346,7 @@ static ZEND_RESULT_CODE php_http_client_curl_setopt(php_http_client_t *h, php_ht 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 @@ -2167,9 +2356,40 @@ static ZEND_RESULT_CODE php_http_client_curl_setopt(php_http_client_t *h, php_ht return SUCCESS; } +static int apply_available_options(zval *pDest, int num_args, va_list args, zend_hash_key *hash_key) +{ + php_http_option_t *opt = Z_PTR_P(pDest); + HashTable *ht; + zval entry; + int c; + + ht = va_arg(args, HashTable*); + + if ((c = zend_hash_num_elements(&opt->suboptions.options))) { + array_init_size(&entry, c); + zend_hash_apply_with_arguments(&opt->suboptions.options, apply_available_options, 1, Z_ARRVAL(entry)); + } else { + /* catch deliberate NULL options */ + if (Z_TYPE(opt->defval) == IS_STRING && !Z_STRVAL(opt->defval)) { + ZVAL_NULL(&entry); + } else { + ZVAL_ZVAL(&entry, &opt->defval, 1, 0); + } + } + + if (hash_key->key) { + zend_hash_update(ht, hash_key->key, &entry); + } else { + zend_hash_index_update(ht, hash_key->h, &entry); + } + + return ZEND_HASH_APPLY_KEEP; +} + static ZEND_RESULT_CODE 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: @@ -2190,6 +2410,14 @@ static ZEND_RESULT_CODE php_http_client_curl_getopt(php_http_client_t *h, php_ht } 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; } @@ -2243,6 +2471,12 @@ PHP_MINIT_FUNCTION(http_client_curl) php_http_curle_options_init(options); } + 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); + } /* * HTTP Protocol Version Constants @@ -2266,7 +2500,7 @@ 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) +#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 @@ -2328,6 +2562,7 @@ PHP_MSHUTDOWN_FUNCTION(http_client_curl) zend_string_release(PHP_HTTP_G->client.curl.driver.driver_name); php_http_options_dtor(&php_http_curle_options); + php_http_options_dtor(&php_http_curlm_options); return SUCCESS; } diff --git a/php_http_env.c b/php_http_env.c index 311541d..0ede201 100644 --- a/php_http_env.c +++ b/php_http_env.c @@ -566,100 +566,15 @@ ZEND_RESULT_CODE php_http_env_set_response_header_value(long http_code, const ch } } -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) @@ -710,32 +625,29 @@ ZEND_END_ARG_INFO(); static PHP_METHOD(HttpEnv, getResponseStatusForCode) { zend_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)); + + if ((status = php_http_env_get_response_status_for_code(code))) { + RETURN_STRING(status); + } } 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); - } +#define PHP_HTTP_RESPONSE_CODE(code, status) add_index_string(return_value, code, status); +#include "php_http_response_codes.h" +#undef PHP_HTTP_RESPONSE_CODE } ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseHeader, 0, 0, 0) diff --git a/php_http_env_response.c b/php_http_env_response.c index 7c76c08..5b46820 100644 --- a/php_http_env_response.c +++ b/php_http_env_response.c @@ -834,6 +834,7 @@ 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; @@ -844,6 +845,7 @@ typedef struct php_http_env_response_stream_ctx { static ZEND_RESULT_CODE 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; ctx = ecalloc(1, sizeof(*ctx)); @@ -851,6 +853,7 @@ static ZEND_RESULT_CODE php_http_env_response_stream_init(php_http_env_response_ ++GC_REFCOUNT(ctx->stream->res); ZEND_INIT_SYMTABLE(&ctx->header); php_http_version_init(&ctx->version, 1, 1); + php_stream_set_option(ctx->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buffer_size); ctx->status_code = 200; ctx->chunked = 1; ctx->request = get_request(&r->options); @@ -868,19 +871,22 @@ static void php_http_env_response_stream_dtor(php_http_env_response_t *r) { php_http_env_response_stream_ctx_t *ctx = r->ctx; + if (ctx->chunked_filter) { + ctx->chunked_filter = php_stream_filter_remove(ctx->chunked_filter, 1); + } zend_hash_destroy(&ctx->header); zend_list_delete(ctx->stream->res); efree(ctx); r->ctx = NULL; } -static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header) +static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header, php_http_buffer_t *buf) { zval *val; ZEND_HASH_FOREACH_VAL(header, val) { if (Z_TYPE_P(val) == IS_ARRAY) { - php_http_env_response_stream_header(ctx, Z_ARRVAL_P(val)); + php_http_env_response_stream_header(ctx, Z_ARRVAL_P(val), buf); } else { zend_string *zs = zval_get_string(val); @@ -890,8 +896,8 @@ static void php_http_env_response_stream_header(php_http_env_response_stream_ctx ctx->chunked = 0; } } - php_stream_write(ctx->stream, zs->val, zs->len); - php_stream_write_string(ctx->stream, PHP_HTTP_CRLF); + php_http_buffer_append(buf, zs->val, zs->len); + php_http_buffer_appends(buf, PHP_HTTP_CRLF); zend_string_release(zs); } } @@ -899,11 +905,14 @@ static void php_http_env_response_stream_header(php_http_env_response_stream_ctx } static ZEND_RESULT_CODE php_http_env_response_stream_start(php_http_env_response_stream_ctx_t *ctx) { + php_http_buffer_t header_buf; + if (ctx->started || ctx->finished) { return FAILURE; } - php_stream_printf(ctx->stream, "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_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) { @@ -914,18 +923,27 @@ static ZEND_RESULT_CODE php_http_env_response_stream_start(php_http_env_response ctx->chunked = 0; } - php_http_env_response_stream_header(ctx, &ctx->header); + php_http_env_response_stream_header(ctx, &ctx->header, &header_buf); /* enable chunked transfer encoding */ if (ctx->chunked) { - php_stream_write_string(ctx->stream, "Transfer-Encoding: chunked" PHP_HTTP_CRLF); + php_http_buffer_appends(&header_buf, "Transfer-Encoding: chunked" PHP_HTTP_CRLF); } - php_stream_write_string(ctx->stream, PHP_HTTP_CRLF); + php_http_buffer_appends(&header_buf, PHP_HTTP_CRLF); - ctx->started = 1; + 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); - return SUCCESS; + if (ctx->chunked) { + ctx->chunked_filter = php_stream_filter_create("http.chunked_encode", NULL, 0); + 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) { @@ -1039,18 +1057,10 @@ static ZEND_RESULT_CODE php_http_env_response_stream_write(php_http_env_response } } - if (stream_ctx->chunked && 0 == php_stream_printf(stream_ctx->stream TSRMLS_CC, "%lx" PHP_HTTP_CRLF, (unsigned long) data_len)) { - return FAILURE; - } - if (data_len != php_stream_write(stream_ctx->stream, data_str, data_len)) { return FAILURE; } - if (stream_ctx->chunked && 2 != php_stream_write_string(stream_ctx->stream, PHP_HTTP_CRLF)) { - return FAILURE; - } - return SUCCESS; } static ZEND_RESULT_CODE php_http_env_response_stream_flush(php_http_env_response_t *r) @@ -1070,22 +1080,24 @@ static ZEND_RESULT_CODE php_http_env_response_stream_flush(php_http_env_response } static ZEND_RESULT_CODE 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; - if (stream_ctx->finished) { + if (ctx->finished) { return FAILURE; } - if (!stream_ctx->started) { - if (SUCCESS != php_http_env_response_stream_start(stream_ctx)) { + if (!ctx->started) { + if (SUCCESS != php_http_env_response_stream_start(ctx)) { return FAILURE; } } - if (stream_ctx->chunked && 5 != php_stream_write_string(stream_ctx->stream, "0" PHP_HTTP_CRLF PHP_HTTP_CRLF)) { - return FAILURE; + 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); } - stream_ctx->finished = 1; + ctx->finished = 1; return SUCCESS; } diff --git a/php_http_header.c b/php_http_header.c index 4651ef5..19cb936 100644 --- a/php_http_header.c +++ b/php_http_header.c @@ -33,12 +33,7 @@ ZEND_RESULT_CODE php_http_header_parse(const char *header, size_t length, HashTa 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 e14b443..cf30c84 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} }; @@ -90,19 +94,35 @@ 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 ) +{ + zend_string *escaped_str = zend_string_init(str, len, 0); + + escaped_str = php_addcslashes(escaped_str, 1, ZEND_STRL("\x0..\x1F\x7F..\xFF")); + + if (valid_len != len && (!eol_str || (str+valid_len) != eol_str)) { + php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected character '\\%03o' at pos %zu of '%s'", str[valid_len], valid_len, escaped_str->val); + } else if (eol_str) { + php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected end of line at pos %zu of '%s'", eol_str - str, escaped_str->val); + } else { + php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected end of input at pos %zu of '%s'", len, escaped_str->val); + } + + efree(escaped_str); +} + 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) { while (buffer->used || !php_http_header_parser_states[php_http_header_parser_state_is(parser)].need_data) { -#if 0 +#if DBG_PARSER const char *state[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"}; - int num_headers = headers ? zend_hash_num_elements(headers) : 0; - fprintf(stderr, "#HP: (%d) %s (avail:%zu, num:%d)\n", php_http_header_parser_state_is(parser), - php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)], - buffer->used, num_headers); + 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: { @@ -135,13 +155,28 @@ php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_pars 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; } @@ -180,40 +215,43 @@ php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_pars 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 tmp, *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); @@ -244,6 +282,193 @@ php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_pars 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 *php_http_header_parser_object_new(zend_class_entry *ce) +{ + return &php_http_header_parser_object_new_ex(ce, NULL)->zo; +} + +php_http_header_parser_object_t *php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser) +{ + php_http_header_parser_object_t *o; + + o = ecalloc(1, sizeof(php_http_header_parser_object_t) + zend_object_properties_size(ce)); + zend_object_std_init(&o->zo, ce); + object_properties_init(&o->zo, ce); + + if (parser) { + o->parser = parser; + } else { + o->parser = php_http_header_parser_init(NULL); + } + o->buffer = php_http_buffer_new(); + + o->zo.handlers = &php_http_header_parser_object_handlers; + + return o; +} + +void php_http_header_parser_object_free(zend_object *object) +{ + php_http_header_parser_object_t *o = PHP_HTTP_OBJ(object, NULL); + + if (o->parser) { + php_http_header_parser_free(&o->parser); + } + if (o->buffer) { + php_http_buffer_free(&o->buffer); + } + zend_object_std_dtor(object); +} + +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 = PHP_HTTP_OBJ(NULL, getThis()); + + 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; + size_t data_len; + zend_long flags; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &data_str, &data_len, &flags, &zmsg), invalid_arg, return); + + ZVAL_DEREF(zmsg); + if (Z_TYPE_P(zmsg) != IS_ARRAY) { + zval_dtor(zmsg); + array_init(zmsg); + } + parser_obj = PHP_HTTP_OBJ(NULL, getThis()); + 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; + zend_long flags; + + php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "rlz", &zstream, &flags, &zmsg), invalid_arg, return); + + zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh); + php_stream_from_zval(s, zstream); + zend_restore_error_handling(&zeh); + + ZVAL_DEREF(zmsg); + if (Z_TYPE_P(zmsg) != IS_ARRAY) { + zval_dtor(zmsg); + array_init(zmsg); + } + parser_obj = PHP_HTTP_OBJ(NULL, getThis()); + 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); + 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.offset = XtOffsetOf(php_http_header_parser_object_t, zo); + php_http_header_parser_object_handlers.clone_obj = NULL; + php_http_header_parser_object_handlers.free_obj = php_http_header_parser_object_free; + + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_HEADER_PARSER_CLEANUP); + + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_HEADER_PARSER_STATE_FAILURE); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_HEADER_PARSER_STATE_START); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_KEY"), PHP_HTTP_HEADER_PARSER_STATE_KEY); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE"), PHP_HTTP_HEADER_PARSER_STATE_VALUE); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE_EX"), PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE); + zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_HEADER_PARSER_STATE_DONE); + + return SUCCESS; +} + /* * Local variables: * tab-width: 4 diff --git a/php_http_header_parser.h b/php_http_header_parser.h index 5b4993a..33707bd 100644 --- a/php_http_header_parser.h +++ b/php_http_header_parser.h @@ -47,6 +47,21 @@ 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 { + php_http_buffer_t *buffer; + php_http_header_parser_t *parser; + zend_object zo; +} 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 *php_http_header_parser_object_new(zend_class_entry *ce); +php_http_header_parser_object_t *php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser); +void php_http_header_parser_object_free(zend_object *object); #endif /* PHP_HTTP_HEADER_PARSER_H */ diff --git a/php_http_message.c b/php_http_message.c index 6ecf6e7..3d3822c 100644 --- a/php_http_message.c +++ b/php_http_message.c @@ -275,6 +275,8 @@ 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 (php_http_message_header(msg, ZEND_STRL("Content-Range"))) { + /* don't mess around with a Content-Range message */ } else if ((size = php_http_message_body_size(msg->body))) { ZVAL_LONG(&h, size); zend_hash_str_update(&msg->hdrs, "Content-Length", lenof("Content-Length"), &h); @@ -299,6 +301,7 @@ void php_http_message_update_headers(php_http_message_t *msg) } } else if ((cl = php_http_message_header_string(msg, ZEND_STRL("Content-Length")))) { if (!zend_string_equals_literal(cl, "0")) { + /* body->size == 0, so get rid of old Content-Length */ zend_hash_str_del(&msg->hdrs, ZEND_STRL("Content-Length")); } zend_string_release(cl); diff --git a/php_http_message_body.c b/php_http_message_body.c index 1c540c7..4a3174a 100644 --- a/php_http_message_body.c +++ b/php_http_message_body.c @@ -113,7 +113,7 @@ const char *php_http_message_body_boundary(php_http_message_body_t *body) if (!body->boundary) { union { double dbl; int num[2]; } data; - data.dbl = php_combined_lcg(TSRMLS_C); + data.dbl = php_combined_lcg(); spprintf(&body->boundary, 0, "%x.%x", data.num[0], data.num[1]); } return body->boundary; @@ -121,24 +121,28 @@ 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); /* real file or temp buffer ? */ - if (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); + 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))) { + 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; } zend_string *php_http_message_body_to_string(php_http_message_body_t *body, off_t offset, size_t forlen) @@ -212,7 +216,7 @@ size_t php_http_message_body_append(php_http_message_body_t *body, const char *b written = php_stream_write(s, buf, len); if (written != len) { - php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to append %zu bytes to body; wrote %zu", len, written); + php_error_docref(NULL, E_WARNING, "Failed to append %zu bytes to body; wrote %zu", len, written); } return len; @@ -584,7 +588,7 @@ void php_http_message_body_object_free(zend_object *object) php_http_message_body_object_t *obj = PHP_HTTP_OBJ(object, NULL); php_http_message_body_free(&obj->body); - zend_object_std_dtor(object TSRMLS_CC); + zend_object_std_dtor(object); } #define PHP_HTTP_MESSAGE_BODY_OBJECT_INIT(obj) \ @@ -813,7 +817,7 @@ PHP_METHOD(HttpMessageBody, stat) char *field_str = NULL; size_t field_len = 0; - if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &field_str, &field_len)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &field_str, &field_len)) { php_http_message_body_object_t *obj = PHP_HTTP_OBJ(NULL, getThis()); const php_stream_statbuf *sb; diff --git a/php_http_message_parser.c b/php_http_message_parser.c index e40e7b3..7bf9a16 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"; @@ -120,18 +121,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; @@ -166,6 +179,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; @@ -177,7 +191,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; } } @@ -237,9 +253,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,6 +269,11 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p zend_long content_length = -1; zend_string *content_range = 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_ptr = php_http_message_header(*message, ZEND_STRL("Transfer-Encoding")))) { zend_string *zs = zval_get_string(h_ptr); @@ -262,25 +284,20 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p zend_hash_str_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", lenof("X-Original-Transfer-Encoding"), h_ptr); zend_hash_str_del(&(*message)->hdrs, "Transfer-Encoding", lenof("Transfer-Encoding")); - } - if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Length")))) { + /* reset */ + ZVAL_LONG(&h, 0); + zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &h); + } else if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Length")))) { content_length = zval_get_long(h_ptr); - Z_TRY_ADDREF_P(h_ptr); zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Length", lenof("X-Original-Content-Length"), h_ptr); } - if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Range")))) { - content_range = zval_get_string(h_ptr); - Z_TRY_ADDREF_P(h_ptr); - zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Range", lenof("X-Original-Content-Range"), h_ptr); - zend_hash_str_del(&(*message)->hdrs, "Content-Range", lenof("Content-Range")); + if ((content_range = php_http_message_header_string(*message, ZEND_STRL("Content-Range")))) { + ZVAL_STR_COPY(&h, content_range); + zend_hash_str_update(&(*message)->hdrs, "Content-Range", lenof("Content-Range"), &h); } - /* default */ - ZVAL_LONG(&h, 0); - zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &h); - /* so, if curl sees a 3xx code, a Location header and a Connection:close header * it decides not to read the response body. */ @@ -290,16 +307,22 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p && (h_loc = php_http_message_header(*message, ZEND_STRL("Location"))) && (h_con = php_http_message_header(*message, ZEND_STRL("Connection"))) ) { - if (php_http_match(Z_STRVAL_P(h_con), "close", PHP_HTTP_MATCH_WORD)) { + zend_string *con = zval_get_string(h_con); + + if (php_http_match(con->val, "close", PHP_HTTP_MATCH_WORD)) { + zend_string_release(con); php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE); break; } + zend_string_release(con); } if ((h_ce = php_http_message_header(*message, ZEND_STRL("Content-Encoding")))) { - if (php_http_match(Z_STRVAL_P(h_ce), "gzip", PHP_HTTP_MATCH_WORD) - || php_http_match(Z_STRVAL_P(h_ce), "x-gzip", PHP_HTTP_MATCH_WORD) - || php_http_match(Z_STRVAL_P(h_ce), "deflate", PHP_HTTP_MATCH_WORD) + zend_string *ce = zval_get_string(h_ce); + + if (php_http_match(ce->val, "gzip", PHP_HTTP_MATCH_WORD) + || php_http_match(ce->val, "x-gzip", PHP_HTTP_MATCH_WORD) + || php_http_match(ce->val, "deflate", PHP_HTTP_MATCH_WORD) ) { if (parser->inflate) { php_http_encoding_stream_reset(&parser->inflate); @@ -310,6 +333,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Encoding", lenof("X-Original-Content-Encoding"), h_ce); zend_hash_str_del(&(*message)->hdrs, "Content-Encoding", lenof("Content-Encoding")); } + zend_string_release(ce); } if ((flags & PHP_HTTP_MESSAGE_PARSER_DUMB_BODIES)) { @@ -321,12 +345,6 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p break; } - if (content_length >= 0) { - parser->body_length = content_length; - 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 (content_range) { ulong total = 0, start = 0, end = 0; @@ -346,7 +364,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); zend_string_release(content_range); @@ -358,6 +376,11 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p zend_string_release(content_range); } + if (content_length >= 0) { + parser->body_length = content_length; + 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); @@ -371,8 +394,6 @@ 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; - if (parser->inflate) { char *dec_str = NULL; size_t dec_len; @@ -389,10 +410,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 */ - ZVAL_LONG(&zcl, php_http_message_body_size((*message)->body)); - zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &zcl); } if (cut) { @@ -465,7 +482,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; @@ -478,14 +495,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; + + ZVAL_LONG(&zcl, php_http_message_body_size((*message)->body)); + zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &zcl); + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: + { char *ptr = buffer->data; while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) { @@ -648,6 +675,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); zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_CHUNKED"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED); zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE); + zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_UPDATE_CL"), PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL); zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_DONE); return SUCCESS; diff --git a/php_http_message_parser.h b/php_http_message_parser.h index 71d91f0..fa63858 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.h b/php_http_misc.h index d9b9e4e..421c991 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) 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.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.h b/php_http_url.h index b7402e9..6ae0ac3 100644 --- a/php_http_url.h +++ b/php_http_url.h @@ -79,21 +79,6 @@ static inline void php_http_url_argsep(const char **str, size_t *len) } } -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); } 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/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..f8b8774 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(["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-- --FILE-- 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(["use_eventloop" => true]); + $client2->configure(["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..58f97c1 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(["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/client018.phpt b/tests/client018.phpt new file mode 100644 index 0000000..7acb911 --- /dev/null +++ b/tests/client018.phpt @@ -0,0 +1,56 @@ +--TEST-- +client pipelining +--SKIPIF-- + +--FILE-- +configure(["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..41a220a --- /dev/null +++ b/tests/client021.phpt @@ -0,0 +1,105 @@ +--TEST-- +client cookies +--SKIPIF-- + +--FILE-- +setOptions(["cookiestore" => $tmpfile]); + +server("cookie.inc", function($port) use($request) { + $request->setOptions(["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(["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(["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(["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..b09c2e2 --- /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([$kk => [$vv, $oo[$kk]]]); + } + } + } else if (isset($v) && $opt[$k] !== $v) { + var_dump([$k => [$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..18c5095 --- /dev/null +++ b/tests/client025.phpt @@ -0,0 +1,41 @@ +--TEST-- +client seek +--SKIPIF-- + +--FILE-- +setOptions(["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..d89e98e --- /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(["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/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/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..0a1eb37 --- /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..c5a02f1 --- /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..e1954e7 --- /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..815b463 --- /dev/null +++ b/tests/helper/pipeline.inc @@ -0,0 +1,25 @@ +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..0605adc --- /dev/null +++ b/tests/helper/server.inc @@ -0,0 +1,94 @@ +getMessage() !== "Empty message received from stream") { + fprintf(STDERR, "%s\n", $ex); + } + break; + } + } + } while ($select !== false); + return; + } + } +} + +function server($handler, callable $cb) { + proc(PHP_BINARY, [__DIR__."/$handler"], $cb); +} + +function nghttpd(callable $cb) { + $spec = [["pipe","r"], ["pipe","w"], ["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, callable $cb) { + $spec = [["pipe","r"], ["pipe","w"], ["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 = [$stderr]; $W = []; $E = []; + } 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 c1f58d9..0000000 --- a/tests/info.phpt +++ /dev/null @@ -1,19 +0,0 @@ ---TEST-- -phpinfo ---SKIPIF-- - ---FILE-- - -Done ---EXPECTF-- -Test -%a -HTTP Support => enabled -Extension Version => 3.%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..72690e4 --- /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/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..d2d22a5 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 = ["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 c704766..acac6cd 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" } @@ -63,4 +63,4 @@ object(http\Message)#%d (9) { } string(3) "OK " -DONE \ No newline at end of file +DONE diff --git a/tests/phpinfo.phpt b/tests/phpinfo.phpt new file mode 100644 index 0000000..c1f58d9 --- /dev/null +++ b/tests/phpinfo.phpt @@ -0,0 +1,19 @@ +--TEST-- +phpinfo +--SKIPIF-- + +--FILE-- + +Done +--EXPECTF-- +Test +%a +HTTP Support => enabled +Extension Version => 3.%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-- getHeader("Proxy-Connection")) { - $response = new http\Env\Response; - $response->setHeader("Content-Length", 0); - $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; - $response->getBody()->append($request); - $response->send($client); - } - return; - } -} \ No newline at end of file diff --git a/tests/proxy001.phpt b/tests/proxy001.phpt deleted file mode 100644 index c8a2e63..0000000 --- a/tests/proxy001.phpt +++ /dev/null @@ -1,48 +0,0 @@ ---TEST-- -proxy - send proxy headers for a proxy request ---SKIPIF-- - ---FILE-- -setOptions(array( - "timeout" => 3, - "proxytunnel" => true, - "proxyheader" => array("Hello" => "there!"), - "proxyhost" => "localhost", - "proxyport" => $port, - )); - try { - $c->enqueue($r)->send(); - } catch (Exception $e) { - echo $e; - } - echo $c->getResponse()->getBody(); - while (!feof($pipes[1])) { - echo fgets($pipes[1]); - } - unset($r, $client); -} -?> -===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! -Content-Length: 0 -===DONE=== diff --git a/tests/proxy002.phpt b/tests/proxy002.phpt deleted file mode 100644 index 86bee61..0000000 --- a/tests/proxy002.phpt +++ /dev/null @@ -1,44 +0,0 @@ ---TEST-- -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(); - while (!feof($pipes[1])) { - echo fgets($pipes[1]); - } - 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: */* -Content-Length: 0 -===DONE=== diff --git a/tests/skipif.inc b/tests/skipif.inc index 4b2627a..cceeaf0 100644 --- a/tests/skipif.inc +++ b/tests/skipif.inc @@ -19,3 +19,12 @@ function skip_client_test($message = "skip need a client driver\n") { die($message); } } + +function skip_http2_test($message = "skip need http2 support (nghttpd in PATH)\n") { + foreach (explode(":", $_ENV["PATH"]) as $path) { + if (is_executable($path . "/nghttpd")) { + return; + } + } + die($message); +} \ No newline at end of file diff --git a/tests/urlparser010.phpt b/tests/urlparser010.phpt index 71f6943..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