modules/
pecl_http-*.tgz
*.lo
+*.o
run-tests.php
tests/*.diff
tests/*.exp
tests/*.php
tests/*.sh
lcov_data
+*~
php_http_options.c \
php_http_params.c \
php_http_querystring.c \
- php_http_strlist.c \
php_http_url.c \
php_http_version.c \
"
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)
</version>
<stability>
<release>beta</release>
- <api>stable</api>
+ <api>beta</api>
</stability>
<license>BSD, revised</license>
<notes><![CDATA[
-*
++ Preliminiary HTTP2 support for http\Client (libcurl with nghttp2 support)
++ Improved performance of HTTP info parser (request/response line)
++ Improved performance of updating client observers
++ Improved performance of http\Env\Response output to streams
++ Improved the error messages of the header parser
++ Added http\Header\Parser class
++ Added http\Client::configure() method accepting an array with the following options for libcurl:
+ . maxconnects (int, size of the connection cache)
+ . max_host_connections (int, max number of connections to a single host, libcurl >= 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
]]></notes>
<contents>
<dir name="/">
<file role="src" name="php_http_params.h"/>
<file role="src" name="php_http_querystring.c"/>
<file role="src" name="php_http_querystring.h"/>
- <file role="src" name="php_http_strlist.c"/>
- <file role="src" name="php_http_strlist.h"/>
+ <file role="src" name="php_http_response_codes.h"/>
<file role="src" name="php_http_url.c"/>
<file role="src" name="php_http_url.h"/>
<file role="src" name="php_http_utf8.h"/>
<dir name="tests">
<file role="test" name="skipif.inc"/>
- <dir name="data">
- <file role="test" name="message_r_multipart_put.txt"/>
- <file role="test" name="message_rr_empty.txt"/>
- <file role="test" name="message_rr_empty_chunked.txt"/>
- <file role="test" name="message_rr_empty_gzip.txt"/>
- <file role="test" name="message_rr_helloworld_chunked.txt"/>
- <file role="test" name="urls.txt"/>
- </dir>
+ <dir name="data">
+ <file role="test" name="message_r_content_range.txt"/>
+ <file role="test" name="message_r_multipart_put.txt"/>
+ <file role="test" name="message_rr_empty.txt"/>
+ <file role="test" name="message_rr_empty_chunked.txt"/>
+ <file role="test" name="message_rr_empty_gzip.txt"/>
+ <file role="test" name="message_rr_helloworld_chunked.txt"/>
+ <file role="test" name="urls.txt"/>
+ </dir>
+ <dir name="helper">
+ <file role="test" name="cookie.inc"/>
+ <file role="test" name="http2.crt"/>
+ <file role="test" name="http2.key"/>
+ <file role="test" name="pipeline.inc"/>
+ <file role="test" name="proxy.inc"/>
+ <file role="test" name="server.inc"/>
+ <dir name="html">
+ <file role="test" name="index.html"/>
+ </dir>
+ </dir>
<file role="test" name="bug61444.phpt"/>
<file role="test" name="bug66388.phpt"/>
+ <file role="test" name="bug66891.phpt"/>
<file role="test" name="bug67932.phpt"/>
+ <file role="test" name="bug69000.phpt"/>
<file role="test" name="client001.phpt"/>
<file role="test" name="client002.phpt"/>
<file role="test" name="client003.phpt"/>
<file role="test" name="client014.phpt"/>
<file role="test" name="client015.phpt"/>
<file role="test" name="client016.phpt"/>
+ <file role="test" name="client017.phpt"/>
+ <file role="test" name="client018.phpt"/>
+ <file role="test" name="client019.phpt"/>
+ <file role="test" name="client020.phpt"/>
+ <file role="test" name="client021.phpt"/>
+ <file role="test" name="client022.phpt"/>
+ <file role="test" name="client023.phpt"/>
+ <file role="test" name="client024.phpt"/>
+ <file role="test" name="client025.phpt"/>
+ <file role="test" name="client026.phpt"/>
<file role="test" name="clientrequest001.phpt"/>
<file role="test" name="clientrequest002.phpt"/>
<file role="test" name="clientrequest003.phpt"/>
<file role="test" name="envresponse014.phpt"/>
<file role="test" name="envresponse015.phpt"/>
<file role="test" name="envresponse016.phpt"/>
+ <file role="test" name="envresponse017.phpt"/>
+ <file role="test" name="envresponse018.phpt"/>
<file role="test" name="envresponsebody001.phpt"/>
<file role="test" name="envresponsebody002.phpt"/>
<file role="test" name="envresponsecodes.phpt"/>
<file role="test" name="header007.phpt"/>
<file role="test" name="header008.phpt"/>
<file role="test" name="header009.phpt"/>
- <file role="test" name="info_001.phpt"/>
- <file role="test" name="info.phpt"/>
+ <file role="test" name="headerparser001.phpt"/>
+ <file role="test" name="headerparser002.phpt"/>
+ <file role="test" name="headerparser003.phpt"/>
+ <file role="test" name="info001.phpt"/>
+ <file role="test" name="info002.phpt"/>
<file role="test" name="message001.phpt"/>
<file role="test" name="message002.phpt"/>
<file role="test" name="message003.phpt"/>
<file role="test" name="message013.phpt"/>
<file role="test" name="message014.phpt"/>
<file role="test" name="message015.phpt"/>
+ <file role="test" name="message016.phpt"/>
<file role="test" name="messagebody001.phpt"/>
<file role="test" name="messagebody002.phpt"/>
<file role="test" name="messagebody003.phpt"/>
<file role="test" name="params013.phpt"/>
<file role="test" name="params014.phpt"/>
<file role="test" name="params015.phpt"/>
+ <file role="test" name="phpinfo.phpt"/>
<file role="test" name="propertyproxy001.phpt"/>
<file role="test" name="querystring001.phpt"/>
<file role="test" name="querystring002.phpt"/>
<file role="test" name="urlparser008.phpt"/>
<file role="test" name="urlparser009.phpt"/>
<file role="test" name="urlparser010.phpt"/>
+ <file role="test" name="urlparser011.phpt"/>
<file role="test" name="version001.phpt"/>
</dir>
</dir>
|| 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)
#include "php_http.h"
#include "php_http_buffer.h"
-#include "php_http_strlist.h"
#include "php_http_misc.h"
#include "php_http_options.h"
}
}
+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();
}
}
+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)
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)
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
};
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 {
#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)
/* 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
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;
}
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;
}
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 */
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);
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;
}
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;
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
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:
}
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;
}
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
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
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;
}
}
}
-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)
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)
long status_code;
php_stream *stream;
+ php_stream_filter *chunked_filter;
php_http_message_t *request;
unsigned started:1;
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));
++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);
{
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);
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);
}
}
}
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) {
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)
{
}
}
- 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)
}
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;
}
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)
#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;
{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}
};
}
}
+/* 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: {
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;
}
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);
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
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 */
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);
}
} 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);
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;
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)
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;
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) \
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;
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";
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;
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;
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;
}
}
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;
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);
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.
*/
&& (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);
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)) {
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;
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);
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);
case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
{
if (len) {
- zval zcl;
-
if (parser->inflate) {
char *dec_str = NULL;
size_t dec_len;
}
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) {
{
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;
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)) {
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;
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;
/* 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)
--- /dev/null
+/*
+ +--------------------------------------------------------------------+
+ | 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 <mike@php.net> |
+ +--------------------------------------------------------------------+
+*/
+
+#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
+ */
+++ /dev/null
-/*
- +--------------------------------------------------------------------+
- | 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 <mike@php.net> |
- +--------------------------------------------------------------------+
-*/
-
-#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
- */
-
}
}
-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);
}
{
unsigned char ub = utf8_mblen[*uc];
- if (!ub || ub > len || ub > 3) {
+ if (!ub || ub > len || ub > 4) {
return 0;
}
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) {
}
return 0;
}
- }
+ ++i;
+ );
return 0;
}
--SKIPIF--
<?php
include "skipif.inc";
+skip_client_test();
?>
--FILE--
<?php
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
+
+include "helper/server.inc";
+
echo "Test\n";
class Observer implements SplObserver
}
}
-$observer = new Observer;
-$request = new http\Client\Request("GET", "http://www.example.org/");
-
-foreach (http\Client::getAvailableDrivers() as $driver) {
- $client = new http\Client($driver);
- $client->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();
+ }
+});
?>
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
-echo "Test\n";
-$request = new http\Client\Request("GET", "http://www.example.org/");
+include "helper/server.inc";
+
+echo "Test\n";
-foreach (http\Client::getAvailableDrivers() as $driver) {
- $client = new http\Client($driver);
- $client->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--
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
-echo "Test\n";
-$request = new http\Client\Request("GET", "http://www.example.org");
+include "helper/server.inc";
-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 {
+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--
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
+
+include "helper/server.inc";
+
echo "Test\n";
-foreach (http\Client::getAvailableDrivers() as $driver) {
- $client = new http\Client($driver);
- $client->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
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
+
+include "helper/server.inc";
+
echo "Test\n";
function response($response) {
return true;
}
-$request = new http\Client\Request("GET", "http://www.example.org");
-
-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";
+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
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
+
+include "helper/server.inc";
+
echo "Test\n";
function response($response) {
}
}
-$request = new http\Client\Request("GET", "http://www.example.org");
-
-foreach (http\Client::getAvailableDrivers() as $driver) {
- $client = new http\Client($driver);
- for ($i=0; $i < 2; ++ $i) {
- $client->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
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
-echo "Test\n";
-$request = new http\Client\Request("GET", "http://www.example.org");
+include "helper/server.inc";
+
+echo "Test\n";
-foreach (http\Client::getAvailableDrivers() as $driver) {
- $client = new http\Client($driver);
- $client->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
<?php
include "skipif.inc";
skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
<?php
include "skipif.inc";
skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
<?php
include "skipif.inc";
skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
<?php
include "skipif.inc";
skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
-
echo "Test\n";
$client = new http\Client;
$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--
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
+include "helper/server.inc";
+
echo "Test\n";
class Client extends http\Client {
}
}
-$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://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
--SKIPIF--
<?php
include "skipif.inc";
+skip_client_test();
?>
--FILE--
<?php
--SKIPIF--
<?php
include "skipif.inc";
-try {
- $client = new http\Client;
- if (!$client->enableEvents())
- throw new Exception("need events support");
-} catch (Exception $e) {
- die("skip ".$e->getMessage());
-}
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
-echo "Test\n";
-
-$client1 = new http\Client;
-$client2 = new http\Client;
-
-$client1->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--
--SKIPIF--
<?php
include "skipif.inc";
-try {
- $client = new http\Client;
- if (!$client->enableEvents())
- throw new Exception("need events support");
-} catch (Exception $e) {
- die("skip ".$e->getMessage());
-}
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
-echo "Test\n";
-$request = new http\Client\Request("GET", "http://www.example.org/");
+include "helper/server.inc";
+
+echo "Test\n";
-foreach (http\Client::getAvailableDrivers() as $driver) {
- $client = new http\Client($driver);
- $client->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--
--- /dev/null
+--TEST--
+client pipelining
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+include "helper/server.inc";
+
+echo "Test\n";
+
+server("pipeline.inc", function($port, $stdin, $stdout, $stderr) {
+ /* tell the server we're about to send 3 pipelined messages */
+ fputs($stdin, "3\n");
+
+ $client = new http\Client(null);
+ $client->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===
--- /dev/null
+--TEST--
+client proxy - send proxy headers for a proxy request
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+include "helper/server.inc";
+
+echo "Test\n";
+
+server("proxy.inc", function($port, $stdin, $stdout, $stderr) {
+ echo "Server on port $port\n";
+
+ $c = new http\Client;
+ $r = new http\Client\Request("GET", "http://www.example.com/");
+ $r->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===
--- /dev/null
+--TEST--
+client proxy - don't send proxy headers for a standard request
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+include "helper/server.inc";
+
+echo "Test\n";
+
+server("proxy.inc", function($port, $stdin, $stdout, $stderr) {
+ echo "Server on port $port\n";
+ $c = new http\Client;
+ $r = new http\Client\Request("GET", "http://localhost:$port/");
+ $r->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===
--- /dev/null
+--TEST--
+client cookies
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+include "helper/server.inc";
+
+echo "Test\n";
+
+$tmpfile = tempnam(sys_get_temp_dir(), "cookie.");
+$request = new http\Client\Request("GET", "http://localhost");
+$request->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===
--- /dev/null
+--TEST--
+client http2
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+skip_http2_test();
+?>
+--FILE--
+<?php
+
+include "helper/server.inc";
+
+echo "Test\n";
+
+nghttpd(function($port) {
+ $client = new http\Client;
+ $client->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
+
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTTP2</title>
+ </head>
+ <body>
+ Nothing to see here.
+ </body>
+</html>
+===DONE===
--- /dev/null
+--TEST--
+client available options and configuration
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+echo "Test\n";
+
+$client = new http\Client;
+if (($opt = $client->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===
--- /dev/null
+--TEST--
+client deprecated methods
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+echo "Test\n";
+
+$client = new http\Client;
+$client->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===
--- /dev/null
+--TEST--
+client seek
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+include "helper/server.inc";
+
+echo "Test\n";
+
+server("proxy.inc", function($port) {
+ $client = new http\Client;
+ $request = new http\Client\Request("PUT", "http://localhost:$port");
+ $request->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===
--- /dev/null
+--TEST--
+client stream 128M
+--SKIPIF--
+<?php
+include "skipif.inc";
+skip_client_test();
+?>
+--FILE--
+<?php
+
+include "helper/server.inc";
+
+echo "Test\n";
+
+server("proxy.inc", function($port) {
+ $client = new http\Client;
+ $request = new http\Client\Request("PUT", "http://localhost:$port");
+ $request->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===
<?php
include "skipif.inc";
skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
<?php
include "skipif.inc";
skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
--SKIPIF--
<?php
include "skipif.inc";
-skip_online_test();
+skip_client_test();
?>
--FILE--
<?php
-echo "Test\n";
-$request = new http\Client\Request("GET", "http://www.example.org");
+include "helper/server.inc";
+
+echo "Test\n";
-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()));
-}
+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--
--- /dev/null
+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
--- /dev/null
+--TEST--
+env response don't generate stat based etag for temp stream
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$b = new http\Message\Body(fopen("php://temp/maxmemory:8", "r+"));
+$b->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===
--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
--- /dev/null
+--TEST--
+header parser
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$headers = [
+ "One: ","header\n",
+ "Two: header\n\tlines\n",
+ "Three",": header\n lines\n here\n",
+ "More: than one header\n",
+ "More: ", "than: ", "you: ", "expect\n",
+ "\n",
+];
+
+$states = [-1=>"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===
--- /dev/null
+--TEST--
+header parser errors
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$headers = [
+ "Na\0me: value",
+ "Na\nme: value",
+ "Name:\0value",
+ "Name:\nvalue",
+ "Name: val\0ue",
+ "Name: value\0",
+];
+
+foreach ($headers as $header) {
+ $parsed = null;
+ $parser = new http\Header\Parser;
+ var_dump($parser->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
--- /dev/null
+--TEST--
+header parser with nonblocking stream
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$parser = new http\Header\Parser;
+$socket = stream_socket_pair(STREAM_PF_UNIX, STREAM_SOCK_STREAM, STREAM_IPPROTO_IP);
+stream_set_blocking($socket[0], 0);
+
+$headers = [
+"GET / HTTP/1.1\n",
+"Host: localhost","\n",
+"Content","-length: 3\n",
+"\n",
+];
+
+while ($headers) {
+ $line = array_shift($headers);
+ $parser->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
--- /dev/null
+<?php
+
+include "server.inc";
+
+serve(function($client) {
+ $request = new http\Message($client, false);
+ $cookies = new http\Cookie($request->getHeader("cookie"));
+ $response = new http\Env\Response;
+ $response->setCookie($cookies->setCookie("counter", $cookies->getCookie("counter")+1));
+ $response->send($client);
+});
--- /dev/null
+<!doctype html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>HTTP2</title>
+ </head>
+ <body>
+ Nothing to see here.
+ </body>
+</html>
--- /dev/null
+-----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-----
--- /dev/null
+-----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-----
--- /dev/null
+<?php
+
+include "server.inc";
+
+function respond($client, $msg) {
+ (new http\Env\Response)->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);
+ }
+});
--- /dev/null
+<?php
+
+include "server.inc";
+
+serve(function($client) {
+ /* this might be a proxy connect or a standard request */
+ $request = new http\Message($client, false);
+
+ if ($request->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);
+});
--- /dev/null
+<?php
+
+function serve(callable $cb) {
+ foreach (range(8000, 9000) as $port) {
+ if (($server = @stream_socket_server("tcp://localhost:$port"))) {
+ fprintf(STDERR, "%s\n", $port);
+ do {
+ $R = [$server]; $W = []; $E = [];
+ $select = stream_select($R, $E, $E, 0, 10000);
+ if ($select && ($client = stream_socket_accept($server, 1))) {
+ if (getenv("PHP_HTTP_TEST_SSL")) {
+ stream_socket_enable_crypto($client, true, STREAM_CRYPTO_METHOD_SSLv23_SERVER);
+ }
+ try {
+ while (!feof($client)) {
+ $cb($client);
+ }
+ } catch (Exception $ex) {
+ /* ignore disconnect */
+ if ($ex->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
+++ /dev/null
---TEST--
-phpinfo
---SKIPIF--
-<?php
-include "skipif.inc";
-?>
---FILE--
-<?php
-echo "Test\n";
-phpinfo(INFO_MODULES);
-?>
-Done
---EXPECTF--
-Test
-%a
-HTTP Support => enabled
-Extension Version => 3.%s
-%a
-Done
--- /dev/null
+--TEST--
+invalid HTTP info
+--SKIPIF--
+<?php include "skipif.inc"; ?>
+--FILE--
+<?php
+
+try {
+ var_dump(new http\Message("GET HTTP/1.1"));
+} catch (Exception $e) {
+ echo $e, "\n";
+}
+try {
+ var_dump(new http\Message("GET HTTP/1.123"));
+} catch (Exception $e) {
+ echo $e, "\n";
+}
+try {
+ var_dump(new http\Message("GETHTTP/1.1"));
+} catch (Exception $e) {
+ echo $e, "\n";
+}
+var_dump(new http\Message("GET / HTTP/1.1"));
+?>
+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
--- /dev/null
+--TEST--
+invalid HTTP info
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+
+echo "Test\n";
+
+function trap(callable $cb) {
+ try {
+ $cb();
+ } catch (Exception $e) {
+ echo $e,"\n";
+ }
+}
+
+trap(function() {
+ echo new http\Message("HTTP/1.1 99 Apples in my Basket");
+});
+
+trap(function() {
+ echo new http\Message("CONNECT HTTP/1.1");
+});
+
+echo new http\Message("HTTP/1.1");
+echo new http\Message("CONNECT www.example.org:80 HTTP/1.1");
+
+?>
+===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===
+++ /dev/null
---TEST--
-invalid HTTP info
---SKIPIF--
-<?php include "skipif.inc"; ?>
---FILE--
-<?php
-
-try {
- var_dump(new http\Message("GET HTTP/1.1"));
-} catch (Exception $e) {
- echo $e, "\n";
-}
-try {
- var_dump(new http\Message("GET HTTP/1.123"));
-} catch (Exception $e) {
- echo $e, "\n";
-}
-try {
- var_dump(new http\Message("GETHTTP/1.1"));
-} catch (Exception $e) {
- echo $e, "\n";
-}
-var_dump(new http\Message("GET / HTTP/1.1"));
-?>
-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
["Accept-Ranges"]=>
string(5) "bytes"
["Content-Length"]=>
- int(0)
+ string(1) "0"
["Vary"]=>
string(15) "Accept-Encoding"
["Connection"]=>
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
Etag: "2002a-0-48549d615a35c"
Accept-Ranges: bytes
Vary: Accept-Encoding
-Content-Length: 0
Connection: close
Content-Type: text/plain
X-Original-Content-Length: 20
bool(true)
int(200)
string(2) "OK"
-array(11) {
+array(10) {
["Date"]=>
string(29) "Thu, 26 Aug 2010 09:55:09 GMT"
["Server"]=>
string(5) "bytes"
["Vary"]=>
string(15) "Accept-Encoding"
- ["Content-Length"]=>
- int(0)
["Connection"]=>
string(5) "close"
["Content-Type"]=>
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
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
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
["Accept-Ranges"]=>
string(5) "bytes"
["Content-Length"]=>
- int(0)
+ string(1) "0"
["Vary"]=>
string(15) "Accept-Encoding"
["Connection"]=>
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
Etag: "2002a-0-48549d615a35c"
Accept-Ranges: bytes
Vary: Accept-Encoding
-Content-Length: 0
Connection: close
Content-Type: text/plain
X-Original-Content-Length: 20
bool(true)
int(200)
string(2) "OK"
-array(11) {
+array(10) {
["Date"]=>
string(29) "Thu, 26 Aug 2010 09:55:09 GMT"
["Server"]=>
string(5) "bytes"
["Vary"]=>
string(15) "Accept-Encoding"
- ["Content-Length"]=>
- int(0)
["Connection"]=>
string(5) "close"
["Content-Type"]=>
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
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
GET /cgi-bin/chunked.sh HTTP/1.1
Host: localhost
Connection: close
-Content-Length: 0
Done
--- /dev/null
+--TEST--
+message content range
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+echo new http\Message(fopen(__DIR__."/data/message_r_content_range.txt", "r"));
+?>
+===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===
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)) {
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);
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);
}
}
?>
["Host"]=>
string(9) "localhost"
["Content-Length"]=>
- int(3)
+ string(1) "3"
["X-Original-Content-Length"]=>
string(1) "3"
}
}
string(3) "OK
"
-DONE
\ No newline at end of file
+DONE
--- /dev/null
+--TEST--
+phpinfo
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+phpinfo(INFO_MODULES);
+?>
+Done
+--EXPECTF--
+Test
+%a
+HTTP Support => enabled
+Extension Version => 3.%s
+%a
+Done
<?php
include "skipif.inc";
?>
+--XFAIL--
+TBD
--FILE--
<?php
+++ /dev/null
-<?php
-
-foreach (range(8000, 9000) as $port) {
- if (($server = stream_socket_server("tcp://localhost:$port"))) {
- fprintf(STDERR, "%s\n", $port);
- if (($client = stream_socket_accept($server))) {
- /* this might be a proxy connect or a standard request */
- $request = new http\Message($client, false);
-
- if ($request->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
+++ /dev/null
---TEST--
-proxy - send proxy headers for a proxy request
---SKIPIF--
-<?php
-include "skipif.inc";
-skip_client_test();
-?>
---FILE--
-<?php
-
-echo "Test\n";
-
-$spec = array(array("pipe","r"), array("pipe","w"), array("pipe","w"));
-if (($proc = proc_open(PHP_BINARY . " proxy.inc", $spec, $pipes, __DIR__))) {
- $port = trim(fgets($pipes[2]));
- echo "Server on port $port\n";
- $c = new http\Client;
- $r = new http\Client\Request("GET", "http://www.example.com/");
- $r->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===
+++ /dev/null
---TEST--
-proxy - don't send proxy headers for a standard request
---SKIPIF--
-<?php
-include "skipif.inc";
-skip_client_test();
-?>
---FILE--
-<?php
-
-echo "Test\n";
-
-$spec = array(array("pipe","r"), array("pipe","w"), array("pipe","w"));
-if (($proc = proc_open(PHP_BINARY . " proxy.inc", $spec, $pipes, __DIR__))) {
- $port = trim(fgets($pipes[2]));
- echo "Server on port $port\n";
- $c = new http\Client;
- $r = new http\Client\Request("GET", "http://localhost:$port/");
- $r->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===
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
--TEST--
-url parser multibyte/utf-8/topct
+url parser multibyte/locale/topct
--SKIPIF--
<?php
include "skipif.inc";
+if (!defined("http\\Url::PARSE_MBLOC") or
+ !stristr(setlocale(LC_CTYPE, NULL), ".utf")) {
+ die("skip need http\\Url::PARSE_MBLOC support and LC_CTYPE=*.UTF-8");
+}
+
?>
--FILE--
<?php
echo "Test\n";
$urls = array(
- "http://mike:paßwort@sörver.net/for/€/?by=¢#ø"
+ "http://mike:paßwort@𐌀𐌁𐌂.it/for/€/?by=¢#ø"
);
foreach ($urls as $url) {
- var_dump(new http\Url($url, null, http\Url::PARSE_MBUTF8|http\Url::PARSE_TOPCT));
+ var_dump(new http\Url($url, null, http\Url::PARSE_MBLOC|http\Url::PARSE_TOPCT));
}
?>
DONE
["pass"]=>
string(12) "pa%C3%9Fwort"
["host"]=>
- string(11) "sörver.net"
+ string(15) "𐌀𐌁𐌂.it"
["port"]=>
NULL
["path"]=>
--- /dev/null
+--TEST--
+url parser multibyte/utf-8/topct
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$urls = array(
+ "http://mike:paßwort@𐌀𐌁𐌂.it/for/€/?by=¢#ø"
+);
+
+foreach ($urls as $url) {
+ var_dump(new http\Url($url, null, http\Url::PARSE_MBUTF8|http\Url::PARSE_TOPCT));
+}
+?>
+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