Merge branch 'master' into phpng
authorMichael Wallner <mike@php.net>
Wed, 18 Feb 2015 09:33:35 +0000 (10:33 +0100)
committerMichael Wallner <mike@php.net>
Wed, 18 Feb 2015 10:26:54 +0000 (11:26 +0100)
Conflicts:
package.xml
php_http_client_curl.c
php_http_env.c
php_http_env_response.c
php_http_header_parser.c
php_http_message.c
php_http_message_body.c
php_http_message_parser.c
php_http_misc.c
php_http_strlist.c
php_http_url.h
tests/info.phpt

79 files changed:
.gitignore
config9.m4
package.xml
php_http.c
php_http_api.h
php_http_client.c
php_http_client.h
php_http_client_curl.c
php_http_env.c
php_http_env_response.c
php_http_header.c
php_http_header_parser.c
php_http_header_parser.h
php_http_message.c
php_http_message_body.c
php_http_message_parser.c
php_http_message_parser.h
php_http_misc.h
php_http_response_codes.h [new file with mode: 0644]
php_http_strlist.h [deleted file]
php_http_url.h
php_http_utf8.h
tests/client001.phpt
tests/client002.phpt
tests/client003.phpt
tests/client004.phpt
tests/client005.phpt
tests/client006.phpt
tests/client007.phpt
tests/client008.phpt
tests/client009.phpt
tests/client010.phpt
tests/client011.phpt
tests/client012.phpt
tests/client013.phpt
tests/client014.phpt
tests/client015.phpt
tests/client016.phpt
tests/client018.phpt [new file with mode: 0644]
tests/client019.phpt [new file with mode: 0644]
tests/client020.phpt [new file with mode: 0644]
tests/client021.phpt [new file with mode: 0644]
tests/client022.phpt [new file with mode: 0644]
tests/client023.phpt [new file with mode: 0644]
tests/client024.phpt [new file with mode: 0644]
tests/client025.phpt [new file with mode: 0644]
tests/client026.phpt [new file with mode: 0644]
tests/clientresponse001.phpt
tests/clientresponse002.phpt
tests/clientresponse003.phpt
tests/data/message_r_content_range.txt [new file with mode: 0644]
tests/envresponse018.phpt [new file with mode: 0644]
tests/header007.phpt
tests/headerparser001.phpt [new file with mode: 0644]
tests/headerparser002.phpt [new file with mode: 0644]
tests/headerparser003.phpt [new file with mode: 0644]
tests/helper/cookie.inc [new file with mode: 0644]
tests/helper/html/index.html [new file with mode: 0644]
tests/helper/http2.crt [new file with mode: 0644]
tests/helper/http2.key [new file with mode: 0644]
tests/helper/pipeline.inc [new file with mode: 0644]
tests/helper/proxy.inc [new file with mode: 0644]
tests/helper/server.inc [new file with mode: 0644]
tests/info.phpt [deleted file]
tests/info001.phpt [new file with mode: 0644]
tests/info002.phpt [new file with mode: 0644]
tests/info_001.phpt [deleted file]
tests/message001.phpt
tests/message016.phpt [new file with mode: 0644]
tests/messageparser001.phpt
tests/messageparser002.phpt
tests/phpinfo.phpt [new file with mode: 0644]
tests/propertyproxy001.phpt
tests/proxy.inc [deleted file]
tests/proxy001.phpt [deleted file]
tests/proxy002.phpt [deleted file]
tests/skipif.inc
tests/urlparser010.phpt
tests/urlparser011.phpt [new file with mode: 0644]

index 64b23b6c9850843b25b88288a97f0833805da510..93781ff39705687030eaada19d3b577665056d59 100644 (file)
@@ -29,6 +29,7 @@ mkinstalldirs
 modules/
 pecl_http-*.tgz
 *.lo
+*.o
 run-tests.php
 tests/*.diff
 tests/*.exp
@@ -37,3 +38,4 @@ tests/*.out
 tests/*.php
 tests/*.sh
 lcov_data
+*~
index 46f70f9f4d2c501a518b75c6bd5026562a904bb7..8a1011818c9fd9a00c0f8ae9b60abce5604fe743 100644 (file)
@@ -547,7 +547,6 @@ dnl ----
                php_http_options.c \
                php_http_params.c \
                php_http_querystring.c \
-               php_http_strlist.c \
                php_http_url.c \
                php_http_version.c \
        "
@@ -599,8 +598,9 @@ dnl ----
                php_http_options.h \
                php_http_params.h \
                php_http_querystring.h \
-               php_http_strlist.h \
+               php_http_response_codes.h \
                php_http_url.h \
+               php_http_utf8.h \
                php_http_version.h \
        "
        PHP_INSTALL_HEADERS(ext/http, $PHP_HTTP_HEADERS)
index 726d2c2a740c7dda83183c00501e663eece77b28..8fcb5379f78d165131f18849fcf7d440f20e2f35 100644 (file)
@@ -38,11 +38,51 @@ http://devel-m6w6.rhcloud.com/mdref/http
  </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="/">
@@ -110,8 +150,7 @@ http://devel-m6w6.rhcloud.com/mdref/http
    <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"/>
@@ -120,17 +159,31 @@ http://devel-m6w6.rhcloud.com/mdref/http
 
    <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"/>
@@ -147,6 +200,16 @@ http://devel-m6w6.rhcloud.com/mdref/http
      <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"/>
@@ -202,6 +265,8 @@ http://devel-m6w6.rhcloud.com/mdref/http
      <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"/>
@@ -220,8 +285,11 @@ http://devel-m6w6.rhcloud.com/mdref/http
      <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"/>
@@ -237,6 +305,7 @@ http://devel-m6w6.rhcloud.com/mdref/http
      <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"/>
@@ -265,6 +334,7 @@ http://devel-m6w6.rhcloud.com/mdref/http
      <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"/>
@@ -284,6 +354,7 @@ http://devel-m6w6.rhcloud.com/mdref/http
      <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>
index 7a28933b03e37be7f6f99ba6c1690992d7caab35..98df8433b403f1c3b12113204c660ea00aa97339 100644 (file)
@@ -134,6 +134,7 @@ PHP_MINIT_FUNCTION(http)
        || SUCCESS != PHP_MINIT_CALL(http_encoding)
        || SUCCESS != PHP_MINIT_CALL(http_filter)
        || SUCCESS != PHP_MINIT_CALL(http_header)
+       || SUCCESS != PHP_MINIT_CALL(http_header_parser)
        || SUCCESS != PHP_MINIT_CALL(http_message)
        || SUCCESS != PHP_MINIT_CALL(http_message_parser)
        || SUCCESS != PHP_MINIT_CALL(http_message_body)
index 5aa96b7efcdb821bfdd46e9a5451a0c125e73cbd..68fdac88015029a05592dda8732e12d17288a2bb 100644 (file)
@@ -76,7 +76,6 @@
 #include "php_http.h"
 
 #include "php_http_buffer.h"
-#include "php_http_strlist.h"
 #include "php_http_misc.h"
 #include "php_http_options.h"
 
index 22fc6ca0497960ec60a2a8a58c648415c3cbfcaf..467ed2a4197c54eabe769ffbac501caf876ae439 100644 (file)
@@ -814,6 +814,22 @@ static PHP_METHOD(HttpClient, wait)
        }
 }
 
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_configure, 0, 0, 1)
+       ZEND_ARG_ARRAY_INFO(0, settings, 1)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpClient, configure)
+{
+       HashTable *settings = NULL;
+       php_http_client_object_t *obj;
+
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|H!", &settings), invalid_arg, return);
+       obj = PHP_HTTP_OBJ(NULL, getThis());
+
+       php_http_expect(SUCCESS == php_http_client_setopt(obj->client, PHP_HTTP_CLIENT_OPT_CONFIGURATION, settings), unexpected_val, return);
+
+       RETVAL_ZVAL(getThis(), 1, 0);
+}
+
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_enablePipelining, 0, 0, 0)
        ZEND_ARG_INFO(0, enable)
 ZEND_END_ARG_INFO();
@@ -1139,6 +1155,30 @@ static PHP_METHOD(HttpClient, getAvailableDrivers)
        }
 }
 
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_getAvailableOptions, 0, 0, 0)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpClient, getAvailableOptions)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_client_object_t *obj = PHP_HTTP_OBJ(NULL, getThis());
+
+               array_init(return_value);
+               php_http_client_getopt(obj->client, PHP_HTTP_CLIENT_OPT_AVAILABLE_OPTIONS, NULL, &Z_ARRVAL_P(return_value));
+       }
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_getAvailableConfiguration, 0, 0, 0)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpClient, getAvailableConfiguration)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_client_object_t *obj = PHP_HTTP_OBJ(NULL, getThis());
+
+               array_init(return_value);
+               php_http_client_getopt(obj->client, PHP_HTTP_CLIENT_OPT_AVAILABLE_CONFIGURATION, NULL, &Z_ARRVAL_P(return_value));
+       }
+}
+
 static zend_function_entry php_http_client_methods[] = {
        PHP_ME(HttpClient, __construct,          ai_HttpClient_construct,            ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
        PHP_ME(HttpClient, reset,                ai_HttpClient_reset,                ZEND_ACC_PUBLIC)
@@ -1151,8 +1191,9 @@ static zend_function_entry php_http_client_methods[] = {
        PHP_ME(HttpClient, wait,                 ai_HttpClient_wait,                 ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, getResponse,          ai_HttpClient_getResponse,          ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, getHistory,           ai_HttpClient_getHistory,           ZEND_ACC_PUBLIC)
-       PHP_ME(HttpClient, enablePipelining,     ai_HttpClient_enablePipelining,     ZEND_ACC_PUBLIC)
-       PHP_ME(HttpClient, enableEvents,         ai_HttpClient_enableEvents,         ZEND_ACC_PUBLIC)
+       PHP_ME(HttpClient, configure,            ai_HttpClient_configure,            ZEND_ACC_PUBLIC)
+       PHP_ME(HttpClient, enablePipelining,     ai_HttpClient_enablePipelining,     ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
+       PHP_ME(HttpClient, enableEvents,         ai_HttpClient_enableEvents,         ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
        PHP_ME(HttpClient, notify,               ai_HttpClient_notify,               ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, attach,               ai_HttpClient_attach,               ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, detach,               ai_HttpClient_detach,               ZEND_ACC_PUBLIC)
@@ -1168,6 +1209,8 @@ static zend_function_entry php_http_client_methods[] = {
        PHP_ME(HttpClient, addCookies,           ai_HttpClient_addCookies,           ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, getCookies,           ai_HttpClient_getCookies,           ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, getAvailableDrivers,  ai_HttpClient_getAvailableDrivers,  ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+       PHP_ME(HttpClient, getAvailableOptions,  ai_HttpClient_getAvailableOptions,  ZEND_ACC_PUBLIC)
+       PHP_ME(HttpClient, getAvailableConfiguration, ai_HttpClient_getAvailableConfiguration, ZEND_ACC_PUBLIC)
        EMPTY_FUNCTION_ENTRY
 };
 
index 41671b9ff813a0c061615636ebc32615d297a138..792581a4da017906ffa05b5e550069b39a0b2157 100644 (file)
 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 {
index c50779916ff3fc40cf609c03e9ee13d48ffe46b7..df832ecd9a8515421a2019d7910e433285f56d3d 100644 (file)
@@ -13,7 +13,7 @@
 #include "php_http_api.h"
 #include "php_http_client.h"
 
-#if 1||PHP_HTTP_HAVE_CURL
+#if PHP_HTTP_HAVE_CURL
 
 #if PHP_HTTP_HAVE_EVENT
 #      if !PHP_HTTP_HAVE_EVENT2 && /* just be really sure */ !(LIBEVENT_VERSION_NUMBER >= 0x02000000)
@@ -833,7 +833,7 @@ static void php_http_curlm_timer_callback(CURLM *multi, long timeout_ms, void *t
 
 /* curl options */
 
-static php_http_options_t php_http_curle_options;
+static php_http_options_t php_http_curle_options, php_http_curlm_options;
 
 #define PHP_HTTP_CURLE_OPTION_CHECK_STRLEN             0x0001
 #define PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR            0x0002
@@ -962,6 +962,9 @@ static ZEND_RESULT_CODE php_http_curle_option_set_compress(php_http_option_t *op
        php_http_client_curl_handler_t *curl = userdata;
        CURL *ch = curl->handle;
 
+#if !PHP_HTTP_CURL_VERSION(7,21,6)
+#      define CURLOPT_ACCEPT_ENCODING CURLOPT_ENCODING
+#endif
        if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_ACCEPT_ENCODING, Z_TYPE_P(val) == IS_TRUE ? "" : NULL)) {
                return FAILURE;
        }
@@ -1509,8 +1512,9 @@ static zval *php_http_curle_get_option(php_http_option_t *opt, HashTable *option
                zval zopt;
 
                ZVAL_DUP(&zopt, option);
-               convert_to_explicit_type(option, opt->type);
+               convert_to_explicit_type(&zopt, opt->type);
                zend_hash_update(&curl->options.cache, opt->name, &zopt);
+               return zend_hash_find(&curl->options.cache, opt->name);
        }
        return option;
 }
@@ -1599,6 +1603,187 @@ static ZEND_RESULT_CODE php_http_curle_set_option(php_http_option_t *opt, zval *
        return rv;
 }
 
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+static ZEND_RESULT_CODE php_http_curlm_option_set_pipelining_bl(php_http_option_t *opt, zval *value, void *userdata)
+{
+       php_http_client_t *client = userdata;
+       php_http_client_curl_t *curl = client->ctx;
+       CURLM *ch = curl->handle;
+       HashTable tmp_ht;
+       char **bl = NULL;
+       TSRMLS_FETCH_FROM_CTX(client->ts);
+
+       /* array of char *, ending with a NULL */
+       if (value && Z_TYPE_P(value) != IS_NULL) {
+               zval *entry;
+               HashTable *ht = HASH_OF(value);
+               int c = zend_hash_num_elements(ht);
+               char **ptr = ecalloc(c + 1, sizeof(char *));
+
+               bl = ptr;
+
+               zend_hash_init(&tmp_ht, c, NULL, ZVAL_PTR_DTOR, 0);
+               array_join(ht, &tmp_ht, 0, ARRAY_JOIN_STRINGIFY);
+
+               ZEND_HASH_FOREACH_VAL(&tmp_ht, entry)
+               {
+                       *ptr++ = Z_STRVAL_P(entry);
+               }
+               ZEND_HASH_FOREACH_END();
+       }
+
+       if (CURLM_OK != curl_multi_setopt(ch, opt->option, bl)) {
+               if (bl) {
+                       efree(bl);
+                       zend_hash_destroy(&tmp_ht);
+               }
+               return FAILURE;
+       }
+
+       if (bl) {
+               efree(bl);
+               zend_hash_destroy(&tmp_ht);
+       }
+       return SUCCESS;
+}
+#endif
+
+#if PHP_HTTP_HAVE_EVENT
+static inline ZEND_RESULT_CODE php_http_curlm_use_eventloop(php_http_client_t *h, zend_bool enable)
+{
+       php_http_client_curl_t *curl = h->ctx;
+
+       if ((curl->useevents = enable)) {
+               if (!curl->evbase) {
+                       curl->evbase = event_base_new();
+               }
+               if (!curl->timeout) {
+                       curl->timeout = ecalloc(1, sizeof(struct event));
+               }
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback);
+       } else {
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
+       }
+
+       return SUCCESS;
+}
+
+static ZEND_RESULT_CODE php_http_curlm_option_set_use_eventloop(php_http_option_t *opt, zval *value, void *userdata)
+{
+       php_http_client_t *client = userdata;
+
+       return php_http_curlm_use_eventloop(client, value && Z_TYPE_P(value) == IS_TRUE);
+}
+#endif
+
+static void php_http_curlm_options_init(php_http_options_t *registry TSRMLS_DC)
+{
+       php_http_option_t *opt;
+
+       /* set size of connection cache */
+       if ((opt = php_http_option_register(registry, ZEND_STRL("maxconnects"), CURLMOPT_MAXCONNECTS, IS_LONG))) {
+               /* -1 == default, 0 == unlimited */
+               ZVAL_LONG(&opt->defval, -1);
+       }
+       /* set max number of connections to a single host */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("max_host_connections"), CURLMOPT_MAX_HOST_CONNECTIONS, IS_LONG);
+#endif
+       /* maximum number of requests in a pipeline */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("max_pipeline_length"), CURLMOPT_MAX_PIPELINE_LENGTH, IS_LONG))) {
+               ZVAL_LONG(&opt->defval, 5);
+       }
+#endif
+       /* max simultaneously open connections */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("max_total_connections"), CURLMOPT_MAX_TOTAL_CONNECTIONS, IS_LONG);
+#endif
+       /* enable/disable HTTP pipelining */
+       php_http_option_register(registry, ZEND_STRL("pipelining"), CURLMOPT_PIPELINING, _IS_BOOL);
+       /* chunk length threshold for pipelining */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("chunk_length_penalty_size"), CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, IS_LONG);
+#endif
+       /* size threshold for pipelining penalty */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("content_length_penalty_size"), CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, IS_LONG);
+#endif
+       /* pipelining server blacklist */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_server_bl"), CURLMOPT_PIPELINING_SERVER_BL, IS_ARRAY))) {
+               opt->setter = php_http_curlm_option_set_pipelining_bl;
+       }
+#endif
+       /* pipelining host blacklist */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_site_bl"), CURLMOPT_PIPELINING_SITE_BL, IS_ARRAY))) {
+               opt->setter = php_http_curlm_option_set_pipelining_bl;
+       }
+#endif
+       /* events */
+#if PHP_HTTP_HAVE_EVENT
+       if ((opt = php_http_option_register(registry, ZEND_STRL("use_eventloop"), 0, _IS_BOOL))) {
+               opt->setter = php_http_curlm_option_set_use_eventloop;
+       }
+#endif
+}
+
+static ZEND_RESULT_CODE php_http_curlm_set_option(php_http_option_t *opt, zval *val, void *userdata)
+{
+       php_http_client_t *client = userdata;
+       php_http_client_curl_t *curl = client->ctx;
+       CURLM *ch = curl->handle;
+       zval *orig = val;
+       CURLMcode rc = CURLM_UNKNOWN_OPTION;
+       ZEND_RESULT_CODE rv = SUCCESS;
+
+       if (!val) {
+               val = &opt->defval;
+       } else if (opt->type && Z_TYPE_P(val) != opt->type && !(Z_TYPE_P(val) == IS_NULL && opt->type == IS_ARRAY)) {
+               zval zopt;
+
+               ZVAL_DUP(&zopt, val);
+               convert_to_explicit_type(&zopt, opt->type);
+
+               val = &zopt;
+       }
+
+       if (opt->setter) {
+               rv = opt->setter(opt, val, client);
+       } else {
+               switch (opt->type) {
+               case _IS_BOOL:
+                       if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, (long) zend_is_true(val)))) {
+                               rv = FAILURE;
+                       }
+                       break;
+               case IS_LONG:
+                       if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, Z_LVAL_P(val)))) {
+                               rv = FAILURE;
+                       }
+                       break;
+               default:
+                       rv = FAILURE;
+                       break;
+               }
+       }
+
+       if (val && val != orig && val != &opt->defval) {
+               zval_ptr_dtor(val);
+       }
+
+       if (rv != SUCCESS) {
+               php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Could not set option %s (%s)", opt->name->val, curl_easy_strerror(rc));
+       }
+       return rv;
+}
 
 /* client ops */
 
@@ -1912,24 +2097,40 @@ static void queue_dtor(php_http_client_enqueue_t *e)
        php_http_client_curl_handler_dtor(handler);
 }
 
-static php_resource_factory_t *create_rf(php_http_url_t *url)
+static php_resource_factory_t *create_rf(php_http_client_t *h, php_http_client_enqueue_t *enqueue)
 {
-       php_persistent_handle_factory_t *pf;
+       php_persistent_handle_factory_t *pf = NULL;
        php_resource_factory_t *rf = NULL;
-       zend_string *id;
-       char *id_str = NULL;
-       size_t id_len;
+       php_http_url_t *url = enqueue->request->http.info.request.url;
 
        if (!url || (!url->host && !url->path)) {
                php_error_docref(NULL, E_WARNING, "Cannot request empty URL");
                return NULL;
        }
 
-       id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), url->port ? url->port : 80);
-       id = php_http_cs2zs(id_str, id_len);
+       /* only if the client itself is setup for persistence */
+       if (h->rf->dtor == (void (*)(void*)) php_persistent_handle_abandon) {
+               zend_string *id;
+               char *id_str = NULL;
+               size_t id_len;
+               int port = url->port ? url->port : 80;
+               zval *zport;
+
+               id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), url->port ? url->port : 80);
+               id = php_http_cs2zs(id_str, id_len);
+
+               if ((zport = zend_hash_str_find(enqueue->options, ZEND_STRL("port")))) {
+                       zend_long lport = zval_get_long(zport);
+
+                       if (lport > 0) {
+                               port = lport;
+                       }
+               }
 
-       pf = php_persistent_handle_concede(NULL, PHP_HTTP_G->client.curl.driver.request_name, id, NULL, NULL);
-       zend_string_release(id);
+               id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), port);
+               pf = php_persistent_handle_concede(NULL, PHP_HTTP_G->client.curl.driver.request_name, id, NULL, NULL);
+               zend_string_release(id);
+       }
 
        if (pf) {
                rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon);
@@ -1948,7 +2149,7 @@ static ZEND_RESULT_CODE php_http_client_curl_enqueue(php_http_client_t *h, php_h
        php_http_client_progress_state_t *progress;
        php_resource_factory_t *rf;
 
-       rf = create_rf(enqueue->request->http.info.request.url);
+       rf = create_rf(h, enqueue);
        if (!rf) {
                return FAILURE;
        }
@@ -2133,6 +2334,10 @@ static ZEND_RESULT_CODE php_http_client_curl_setopt(php_http_client_t *h, php_ht
        php_http_client_curl_t *curl = h->ctx;
 
        switch (opt) {
+               case PHP_HTTP_CLIENT_OPT_CONFIGURATION:
+                       return php_http_options_apply(&php_http_curlm_options, (HashTable *) arg,  h);
+                       break;
+
                case PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING:
                        if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) {
                                return FAILURE;
@@ -2141,23 +2346,7 @@ static ZEND_RESULT_CODE php_http_client_curl_setopt(php_http_client_t *h, php_ht
 
                case PHP_HTTP_CLIENT_OPT_USE_EVENTS:
 #if PHP_HTTP_HAVE_EVENT
-                       if ((curl->useevents = *((zend_bool *) arg))) {
-                               if (!curl->evbase) {
-                                       curl->evbase = event_base_new();
-                               }
-                               if (!curl->timeout) {
-                                       curl->timeout = ecalloc(1, sizeof(struct event));
-                               }
-                               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
-                               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback);
-                               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
-                               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback);
-                       } else {
-                               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
-                               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
-                               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
-                               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
-                       }
+                       return php_http_curlm_use_eventloop(h, *(zend_bool *) arg);
                        break;
 #endif
 
@@ -2167,9 +2356,40 @@ static ZEND_RESULT_CODE php_http_client_curl_setopt(php_http_client_t *h, php_ht
        return SUCCESS;
 }
 
+static int apply_available_options(zval *pDest, int num_args, va_list args, zend_hash_key *hash_key)
+{
+       php_http_option_t *opt = Z_PTR_P(pDest);
+       HashTable *ht;
+       zval entry;
+       int c;
+
+       ht = va_arg(args, HashTable*);
+
+       if ((c = zend_hash_num_elements(&opt->suboptions.options))) {
+               array_init_size(&entry, c);
+               zend_hash_apply_with_arguments(&opt->suboptions.options, apply_available_options, 1, Z_ARRVAL(entry));
+       } else {
+               /* catch deliberate NULL options */
+               if (Z_TYPE(opt->defval) == IS_STRING && !Z_STRVAL(opt->defval)) {
+                       ZVAL_NULL(&entry);
+               } else {
+                       ZVAL_ZVAL(&entry, &opt->defval, 1, 0);
+               }
+       }
+
+       if (hash_key->key) {
+               zend_hash_update(ht, hash_key->key, &entry);
+       } else {
+               zend_hash_index_update(ht, hash_key->h, &entry);
+       }
+
+       return ZEND_HASH_APPLY_KEEP;
+}
+
 static ZEND_RESULT_CODE php_http_client_curl_getopt(php_http_client_t *h, php_http_client_getopt_opt_t opt, void *arg, void **res)
 {
        php_http_client_enqueue_t *enqueue;
+       TSRMLS_FETCH_FROM_CTX(h->ts);
 
        switch (opt) {
        case PHP_HTTP_CLIENT_OPT_PROGRESS_INFO:
@@ -2190,6 +2410,14 @@ static ZEND_RESULT_CODE php_http_client_curl_getopt(php_http_client_t *h, php_ht
                }
                break;
 
+       case PHP_HTTP_CLIENT_OPT_AVAILABLE_OPTIONS:
+               zend_hash_apply_with_arguments(&php_http_curle_options.options TSRMLS_CC, apply_available_options, 1, *(HashTable **) res);
+               break;
+
+       case PHP_HTTP_CLIENT_OPT_AVAILABLE_CONFIGURATION:
+               zend_hash_apply_with_arguments(&php_http_curlm_options.options TSRMLS_CC, apply_available_options, 1, *(HashTable **) res);
+               break;
+
        default:
                break;
        }
@@ -2243,6 +2471,12 @@ PHP_MINIT_FUNCTION(http_client_curl)
 
                php_http_curle_options_init(options);
        }
+       if ((options = php_http_options_init(&php_http_curlm_options, 1))) {
+               options->getter = php_http_option_get;
+               options->setter = php_http_curlm_set_option;
+
+               php_http_curlm_options_init(options);
+       }
 
        /*
        * HTTP Protocol Version Constants
@@ -2266,7 +2500,7 @@ PHP_MINIT_FUNCTION(http_client_curl)
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv2", CURL_SSLVERSION_SSLv2, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv3", CURL_SSLVERSION_SSLv3, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_ANY", CURL_SSLVERSION_DEFAULT, CONST_CS|CONST_PERSISTENT);
-#if PHP_HTTP_CURL_VERSION(7,21,4)
+#if PHP_HTTP_CURL_VERSION(7,21,4) && defined(PHP_HTTP_CURL_TLSAUTH_SRP)
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "TLSAUTH_SRP", CURL_TLSAUTH_SRP, CONST_CS|CONST_PERSISTENT);
 #endif
 
@@ -2328,6 +2562,7 @@ PHP_MSHUTDOWN_FUNCTION(http_client_curl)
        zend_string_release(PHP_HTTP_G->client.curl.driver.driver_name);
 
        php_http_options_dtor(&php_http_curle_options);
+       php_http_options_dtor(&php_http_curlm_options);
 
        return SUCCESS;
 }
index 311541d7102ee095f480c8108d1a8e3f33452add..0ede201e875a349a850f9b443225119c0d248938 100644 (file)
@@ -566,100 +566,15 @@ ZEND_RESULT_CODE php_http_env_set_response_header_value(long http_code, const ch
        }
 }
 
-static PHP_HTTP_STRLIST(php_http_env_response_status) =
-       PHP_HTTP_STRLIST_ITEM("Continue")
-       PHP_HTTP_STRLIST_ITEM("Switching Protocols")
-       PHP_HTTP_STRLIST_ITEM("Processing")
-       PHP_HTTP_STRLIST_NEXT
-       PHP_HTTP_STRLIST_ITEM("OK")
-       PHP_HTTP_STRLIST_ITEM("Created")
-       PHP_HTTP_STRLIST_ITEM("Accepted")
-       PHP_HTTP_STRLIST_ITEM("Non-Authoritative Information")
-       PHP_HTTP_STRLIST_ITEM("No Content")
-       PHP_HTTP_STRLIST_ITEM("Reset Content")
-       PHP_HTTP_STRLIST_ITEM("Partial Content")
-       PHP_HTTP_STRLIST_ITEM("Multi-Status")
-       PHP_HTTP_STRLIST_ITEM("Already Reported")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("IM Used")
-       PHP_HTTP_STRLIST_NEXT
-       PHP_HTTP_STRLIST_ITEM("Multiple Choices")
-       PHP_HTTP_STRLIST_ITEM("Moved Permanently")
-       PHP_HTTP_STRLIST_ITEM("Found")
-       PHP_HTTP_STRLIST_ITEM("See Other")
-       PHP_HTTP_STRLIST_ITEM("Not Modified")
-       PHP_HTTP_STRLIST_ITEM("Use Proxy")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Temporary Redirect")
-       PHP_HTTP_STRLIST_ITEM("Permanent Redirect")
-       PHP_HTTP_STRLIST_NEXT
-       PHP_HTTP_STRLIST_ITEM("Bad Request")
-       PHP_HTTP_STRLIST_ITEM("Unauthorized")
-       PHP_HTTP_STRLIST_ITEM("Payment Required")
-       PHP_HTTP_STRLIST_ITEM("Forbidden")
-       PHP_HTTP_STRLIST_ITEM("Not Found")
-       PHP_HTTP_STRLIST_ITEM("Method Not Allowed")
-       PHP_HTTP_STRLIST_ITEM("Not Acceptable")
-       PHP_HTTP_STRLIST_ITEM("Proxy Authentication Required")
-       PHP_HTTP_STRLIST_ITEM("Request Timeout")
-       PHP_HTTP_STRLIST_ITEM("Conflict")
-       PHP_HTTP_STRLIST_ITEM("Gone")
-       PHP_HTTP_STRLIST_ITEM("Length Required")
-       PHP_HTTP_STRLIST_ITEM("Precondition Failed")
-       PHP_HTTP_STRLIST_ITEM("Request Entity Too Large")
-       PHP_HTTP_STRLIST_ITEM("Request URI Too Long")
-       PHP_HTTP_STRLIST_ITEM("Unsupported Media Type")
-       PHP_HTTP_STRLIST_ITEM("Requested Range Not Satisfiable")
-       PHP_HTTP_STRLIST_ITEM("Expectation Failed")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Unprocessible Entity")
-       PHP_HTTP_STRLIST_ITEM("Locked")
-       PHP_HTTP_STRLIST_ITEM("Failed Dependency")
-       PHP_HTTP_STRLIST_ITEM("(Reserved)")
-       PHP_HTTP_STRLIST_ITEM("Upgrade Required")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Precondition Required")
-       PHP_HTTP_STRLIST_ITEM("Too Many Requests")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Request Header Fields Too Large")
-       PHP_HTTP_STRLIST_NEXT
-       PHP_HTTP_STRLIST_ITEM("Internal Server Error")
-       PHP_HTTP_STRLIST_ITEM("Not Implemented")
-       PHP_HTTP_STRLIST_ITEM("Bad Gateway")
-       PHP_HTTP_STRLIST_ITEM("Service Unavailable")
-       PHP_HTTP_STRLIST_ITEM("Gateway Timeout")
-       PHP_HTTP_STRLIST_ITEM("HTTP Version Not Supported")
-       PHP_HTTP_STRLIST_ITEM("Variant Also Negotiates")
-       PHP_HTTP_STRLIST_ITEM("Insufficient Storage")
-       PHP_HTTP_STRLIST_ITEM("Loop Detected")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Not Extended")
-       PHP_HTTP_STRLIST_ITEM("Network Authentication Required")
-       PHP_HTTP_STRLIST_STOP
-;
-
 const char *php_http_env_get_response_status_for_code(unsigned code)
 {
-       return php_http_strlist_find(php_http_env_response_status, 100, code);
+       switch (code) {
+#define PHP_HTTP_RESPONSE_CODE(c, s) case c: return s;
+#include "php_http_response_codes.h"
+#undef PHP_HTTP_RESPONSE_CODE
+       default:
+               return NULL;
+       }
 }
 
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getRequestHeader, 0, 0, 0)
@@ -710,32 +625,29 @@ ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpEnv, getResponseStatusForCode)
 {
        zend_long code;
+       const char *status;
 
        if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &code)) {
                return;
        }
-       RETURN_STRING(php_http_env_get_response_status_for_code(code));
+
+       if ((status = php_http_env_get_response_status_for_code(code))) {
+               RETURN_STRING(status);
+       }
 }
 
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseStatusForAllCodes, 0, 0, 0)
 ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpEnv, getResponseStatusForAllCodes)
 {
-       const char *s;
-       unsigned c;
-       php_http_strlist_iterator_t i;
-
        if (SUCCESS != zend_parse_parameters_none()) {
                return;
        }
 
        array_init(return_value);
-       for (   php_http_strlist_iterator_init(&i, php_http_env_response_status, 100);
-                       *(s = php_http_strlist_iterator_this(&i, &c));
-                       php_http_strlist_iterator_next(&i)
-       ) {
-               add_index_string(return_value, c, s);
-       }
+#define PHP_HTTP_RESPONSE_CODE(code, status) add_index_string(return_value, code, status);
+#include "php_http_response_codes.h"
+#undef PHP_HTTP_RESPONSE_CODE
 }
 
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseHeader, 0, 0, 0)
index 7c76c081870ed8fbfe9ec7a6a345b2dbecc2cb4f..5b4682084278d460c6672caafa745177fd289c9a 100644 (file)
@@ -834,6 +834,7 @@ typedef struct php_http_env_response_stream_ctx {
        long status_code;
 
        php_stream *stream;
+       php_stream_filter *chunked_filter;
        php_http_message_t *request;
 
        unsigned started:1;
@@ -844,6 +845,7 @@ typedef struct php_http_env_response_stream_ctx {
 static ZEND_RESULT_CODE php_http_env_response_stream_init(php_http_env_response_t *r, void *init_arg)
 {
        php_http_env_response_stream_ctx_t *ctx;
+       size_t buffer_size = 0x1000;
 
        ctx = ecalloc(1, sizeof(*ctx));
 
@@ -851,6 +853,7 @@ static ZEND_RESULT_CODE php_http_env_response_stream_init(php_http_env_response_
        ++GC_REFCOUNT(ctx->stream->res);
        ZEND_INIT_SYMTABLE(&ctx->header);
        php_http_version_init(&ctx->version, 1, 1);
+       php_stream_set_option(ctx->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buffer_size);
        ctx->status_code = 200;
        ctx->chunked = 1;
        ctx->request = get_request(&r->options);
@@ -868,19 +871,22 @@ static void php_http_env_response_stream_dtor(php_http_env_response_t *r)
 {
        php_http_env_response_stream_ctx_t *ctx = r->ctx;
 
+       if (ctx->chunked_filter) {
+               ctx->chunked_filter = php_stream_filter_remove(ctx->chunked_filter, 1);
+       }
        zend_hash_destroy(&ctx->header);
        zend_list_delete(ctx->stream->res);
        efree(ctx);
        r->ctx = NULL;
 }
-static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header)
+static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header, php_http_buffer_t *buf)
 {
        zval *val;
 
        ZEND_HASH_FOREACH_VAL(header, val)
        {
                if (Z_TYPE_P(val) == IS_ARRAY) {
-                       php_http_env_response_stream_header(ctx, Z_ARRVAL_P(val));
+                       php_http_env_response_stream_header(ctx, Z_ARRVAL_P(val), buf);
                } else {
                        zend_string *zs = zval_get_string(val);
 
@@ -890,8 +896,8 @@ static void php_http_env_response_stream_header(php_http_env_response_stream_ctx
                                        ctx->chunked = 0;
                                }
                        }
-                       php_stream_write(ctx->stream, zs->val, zs->len);
-                       php_stream_write_string(ctx->stream, PHP_HTTP_CRLF);
+                       php_http_buffer_append(buf, zs->val, zs->len);
+                       php_http_buffer_appends(buf, PHP_HTTP_CRLF);
                        zend_string_release(zs);
                }
        }
@@ -899,11 +905,14 @@ static void php_http_env_response_stream_header(php_http_env_response_stream_ctx
 }
 static ZEND_RESULT_CODE php_http_env_response_stream_start(php_http_env_response_stream_ctx_t *ctx)
 {
+       php_http_buffer_t header_buf;
+
        if (ctx->started || ctx->finished) {
                return FAILURE;
        }
 
-       php_stream_printf(ctx->stream, "HTTP/%u.%u %ld %s" PHP_HTTP_CRLF, ctx->version.major, ctx->version.minor, ctx->status_code, php_http_env_get_response_status_for_code(ctx->status_code));
+       php_http_buffer_init(&header_buf);
+       php_http_buffer_appendf(&header_buf, "HTTP/%u.%u %ld %s" PHP_HTTP_CRLF, ctx->version.major, ctx->version.minor, ctx->status_code, php_http_env_get_response_status_for_code(ctx->status_code));
 
        /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */
        if (ctx->version.major == 1 && ctx->version.minor == 0) {
@@ -914,18 +923,27 @@ static ZEND_RESULT_CODE php_http_env_response_stream_start(php_http_env_response
                ctx->chunked = 0;
        }
 
-       php_http_env_response_stream_header(ctx, &ctx->header);
+       php_http_env_response_stream_header(ctx, &ctx->header, &header_buf);
 
        /* enable chunked transfer encoding */
        if (ctx->chunked) {
-               php_stream_write_string(ctx->stream, "Transfer-Encoding: chunked" PHP_HTTP_CRLF);
+               php_http_buffer_appends(&header_buf, "Transfer-Encoding: chunked" PHP_HTTP_CRLF);
        }
 
-       php_stream_write_string(ctx->stream, PHP_HTTP_CRLF);
+       php_http_buffer_appends(&header_buf, PHP_HTTP_CRLF);
 
-       ctx->started = 1;
+       if (header_buf.used == php_stream_write(ctx->stream, header_buf.data, header_buf.used)) {
+               ctx->started = 1;
+       }
+       php_http_buffer_dtor(&header_buf);
+       php_stream_flush(ctx->stream);
 
-       return SUCCESS;
+       if (ctx->chunked) {
+               ctx->chunked_filter = php_stream_filter_create("http.chunked_encode", NULL, 0);
+               php_stream_filter_append(&ctx->stream->writefilters, ctx->chunked_filter);
+       }
+
+       return ctx->started ? SUCCESS : FAILURE;
 }
 static long php_http_env_response_stream_get_status(php_http_env_response_t *r)
 {
@@ -1039,18 +1057,10 @@ static ZEND_RESULT_CODE php_http_env_response_stream_write(php_http_env_response
                }
        }
 
-       if (stream_ctx->chunked && 0 == php_stream_printf(stream_ctx->stream TSRMLS_CC, "%lx" PHP_HTTP_CRLF, (unsigned long) data_len)) {
-               return FAILURE;
-       }
-
        if (data_len != php_stream_write(stream_ctx->stream, data_str, data_len)) {
                return FAILURE;
        }
 
-       if (stream_ctx->chunked && 2 != php_stream_write_string(stream_ctx->stream, PHP_HTTP_CRLF)) {
-               return FAILURE;
-       }
-
        return SUCCESS;
 }
 static ZEND_RESULT_CODE php_http_env_response_stream_flush(php_http_env_response_t *r)
@@ -1070,22 +1080,24 @@ static ZEND_RESULT_CODE php_http_env_response_stream_flush(php_http_env_response
 }
 static ZEND_RESULT_CODE php_http_env_response_stream_finish(php_http_env_response_t *r)
 {
-       php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
+       php_http_env_response_stream_ctx_t *ctx = r->ctx;
 
-       if (stream_ctx->finished) {
+       if (ctx->finished) {
                return FAILURE;
        }
-       if (!stream_ctx->started) {
-               if (SUCCESS != php_http_env_response_stream_start(stream_ctx)) {
+       if (!ctx->started) {
+               if (SUCCESS != php_http_env_response_stream_start(ctx)) {
                        return FAILURE;
                }
        }
 
-       if (stream_ctx->chunked && 5 != php_stream_write_string(stream_ctx->stream, "0" PHP_HTTP_CRLF PHP_HTTP_CRLF)) {
-               return FAILURE;
+       php_stream_flush(ctx->stream);
+       if (ctx->chunked && ctx->chunked_filter) {
+               php_stream_filter_flush(ctx->chunked_filter, 1);
+               ctx->chunked_filter = php_stream_filter_remove(ctx->chunked_filter, 1 TSRMLS_CC);
        }
 
-       stream_ctx->finished = 1;
+       ctx->finished = 1;
 
        return SUCCESS;
 }
index 4651ef5a5d6cfca1abd695f154f36876a7733ed0..19cb936a7a3efb8a2fae5de5cd845e0a475ebd24 100644 (file)
@@ -33,12 +33,7 @@ ZEND_RESULT_CODE php_http_header_parse(const char *header, size_t length, HashTa
        php_http_header_parser_dtor(&ctx);
        php_http_buffer_dtor(&buf);
 
-       if (rs == PHP_HTTP_HEADER_PARSER_STATE_FAILURE) {
-               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse headers");
-               return FAILURE;
-       }
-       
-       return SUCCESS;
+       return rs == PHP_HTTP_HEADER_PARSER_STATE_FAILURE ? FAILURE : SUCCESS;
 }
 
 void php_http_header_to_callback(HashTable *headers, zend_bool crlf, php_http_pass_format_callback_t cb, void *cb_arg TSRMLS_DC)
index e14b44333b1efe44b23b6f05fe2ba65f14bd3d11..cf30c84c48bc5ba0be2415a9a5572224adcfa940 100644 (file)
 
 #include "php_http_api.h"
 
+#ifndef DBG_PARSER
+#      define DBG_PARSER 0
+#endif
+
 typedef struct php_http_header_parser_state_spec {
        php_http_header_parser_state_t state;
        unsigned need_data:1;
@@ -21,7 +25,7 @@ static const php_http_header_parser_state_spec_t php_http_header_parser_states[]
                {PHP_HTTP_HEADER_PARSER_STATE_START,            1},
                {PHP_HTTP_HEADER_PARSER_STATE_KEY,                      1},
                {PHP_HTTP_HEADER_PARSER_STATE_VALUE,            1},
-               {PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX,         1},
+               {PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX,         0},
                {PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE,      0},
                {PHP_HTTP_HEADER_PARSER_STATE_DONE,                     0}
 };
@@ -90,19 +94,35 @@ void php_http_header_parser_free(php_http_header_parser_t **parser)
        }
 }
 
+/* NOTE: 'str' has to be null terminated */
+static void php_http_header_parser_error(size_t valid_len, char *str, size_t len, const char *eol_str )
+{
+       zend_string *escaped_str = zend_string_init(str, len, 0);
+
+       escaped_str = php_addcslashes(escaped_str, 1, ZEND_STRL("\x0..\x1F\x7F..\xFF"));
+
+       if (valid_len != len && (!eol_str || (str+valid_len) != eol_str)) {
+               php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected character '\\%03o' at pos %zu of '%s'", str[valid_len], valid_len, escaped_str->val);
+       } else if (eol_str) {
+               php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected end of line at pos %zu of '%s'", eol_str - str, escaped_str->val);
+       } else {
+               php_error_docref(NULL, E_WARNING, "Failed to parse headers: unexpected end of input at pos %zu of '%s'", len, escaped_str->val);
+       }
+
+       efree(escaped_str);
+}
+
 php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg)
 {
        while (buffer->used || !php_http_header_parser_states[php_http_header_parser_state_is(parser)].need_data) {
-#if 0
+#if DBG_PARSER
                const char *state[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
-               int num_headers = headers ? zend_hash_num_elements(headers) : 0;
-               fprintf(stderr, "#HP: (%d) %s (avail:%zu, num:%d)\n", php_http_header_parser_state_is(parser),
-                               php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)],
-                                               buffer->used, num_headers);
+               fprintf(stderr, "#HP: %s (avail:%zu, num:%d cleanup:%u)\n", php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)], buffer->used, headers?zend_hash_num_elements(headers):0, flags);
                _dpf(0, buffer->data, buffer->used);
 #endif
                switch (php_http_header_parser_state_pop(parser)) {
                        case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers");
                                return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
 
                        case PHP_HTTP_HEADER_PARSER_STATE_START: {
@@ -135,13 +155,28 @@ php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_pars
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
                                } else if ((colon = memchr(buffer->data, ':', buffer->used)) && (!eol_str || eol_str > colon)) {
                                        /* header: string */
-                                       parser->_key.str = estrndup(buffer->data, parser->_key.len = colon - buffer->data);
+                                       size_t valid_len;
+
+                                       parser->_key.len = colon - buffer->data;
+                                       parser->_key.str = estrndup(buffer->data, parser->_key.len);
+
+                                       valid_len = strspn(parser->_key.str, PHP_HTTP_HEADER_NAME_CHARS);
+                                       if (valid_len != parser->_key.len) {
+                                               php_http_header_parser_error(valid_len, parser->_key.str, parser->_key.len, eol_str TSRMLS_CC);
+                                               PTR_SET(parser->_key.str, NULL);
+                                               return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+                                       }
                                        while (PHP_HTTP_IS_CTYPE(space, *++colon) && *colon != '\n' && *colon != '\r');
                                        php_http_buffer_cut(buffer, 0, colon - buffer->data);
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
-                               } else {
-                                       /* neither reqeust/response line nor header: string */
+                               } else if (eol_str || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) {
+                                       /* neither reqeust/response line nor 'header:' string, or injected new line or NUL etc. */
+                                       php_http_buffer_fix(buffer);
+                                       php_http_header_parser_error(strspn(buffer->data, PHP_HTTP_HEADER_NAME_CHARS), buffer->data, buffer->used, eol_str TSRMLS_CC);
                                        return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+                               } else {
+                                       /* keep feeding */
+                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY);
                                }
                                break;
                        }
@@ -180,40 +215,43 @@ php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_pars
 
                                if ((eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) {
                                        SET_ADD_VAL(eol_str - buffer->data, eol_len);
-
-                                       if (buffer->used) {
-                                               if (*buffer->data != '\t' && *buffer->data != ' ') {
-                                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
-                                                       break;
-                                               } else {
-                                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
-                                                       break;
-                                               }
-                                       }
-                               }
-
-                               if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) {
+                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
+                               } else if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) {
                                        if (buffer->used) {
                                                SET_ADD_VAL(buffer->used, 0);
                                        }
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
                                } else {
-                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
+                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
                                }
                                break;
                        }
 
                        case PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX:
-                               if (*buffer->data == ' ' || *buffer->data == '\t') {
+                               if (buffer->used && (*buffer->data == ' ' || *buffer->data == '\t')) {
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
-                               } else {
+                               } else if (buffer->used || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) {
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
+                               } else {
+                                       /* keep feeding */
+                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
                                }
                                break;
 
                        case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE:
                                if (parser->_key.str && parser->_val.str) {
                                        zval tmp, *exist;
+                                       size_t valid_len = strlen(parser->_val.str);
+
+                                       /* check for truncation */
+                                       if (valid_len != parser->_val.len) {
+                                               php_http_header_parser_error(valid_len, parser->_val.str, parser->_val.len, NULL TSRMLS_CC);
+
+                                               PTR_SET(parser->_key.str, NULL);
+                                               PTR_SET(parser->_val.str, NULL);
+
+                                               return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+                                       }
 
                                        if (!headers && callback_func) {
                                                callback_func(callback_arg, &headers, NULL);
@@ -244,6 +282,193 @@ php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_pars
        return php_http_header_parser_state_is(parser);
 }
 
+php_http_header_parser_state_t php_http_header_parser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buf, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg)
+{
+       php_http_message_parser_state_t state = PHP_HTTP_MESSAGE_PARSER_STATE_START;
+       TSRMLS_FETCH_FROM_CTX(parser->ts);
+
+       if (!buf->data) {
+               php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
+       }
+       while (1) {
+               size_t justread = 0;
+#if DBG_PARSER
+               const char *states[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
+               fprintf(stderr, "#SHP: %s (f:%u)\n", states[state], flags);
+#endif
+               /* resize if needed */
+               if (buf->free < 0x1000) {
+                       php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
+               }
+               switch (state) {
+               case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
+               case PHP_HTTP_HEADER_PARSER_STATE_DONE:
+                       return state;
+
+               default:
+                       /* read line */
+                       php_stream_get_line(s, buf->data + buf->used, buf->free, &justread);
+                       /* if we fail reading a whole line, try a single char */
+                       if (!justread) {
+                               int c = php_stream_getc(s);
+
+                               if (c != EOF) {
+                                       char s[1] = {c};
+                                       justread = php_http_buffer_append(buf, s, 1);
+                               }
+                       }
+                       php_http_buffer_account(buf, justread);
+               }
+
+               if (justread) {
+                       state = php_http_header_parser_parse(parser, buf, flags, headers, callback_func, callback_arg);
+               } else if (php_stream_eof(s)) {
+                       return php_http_header_parser_parse(parser, buf, flags | PHP_HTTP_HEADER_PARSER_CLEANUP, headers, callback_func, callback_arg);
+               } else  {
+                       return state;
+               }
+       }
+
+       return PHP_HTTP_HEADER_PARSER_STATE_DONE;
+}
+
+zend_class_entry *php_http_header_parser_class_entry;
+static zend_object_handlers php_http_header_parser_object_handlers;
+
+zend_object *php_http_header_parser_object_new(zend_class_entry *ce)
+{
+       return &php_http_header_parser_object_new_ex(ce, NULL)->zo;
+}
+
+php_http_header_parser_object_t *php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser)
+{
+       php_http_header_parser_object_t *o;
+
+       o = ecalloc(1, sizeof(php_http_header_parser_object_t) + zend_object_properties_size(ce));
+       zend_object_std_init(&o->zo, ce);
+       object_properties_init(&o->zo, ce);
+
+       if (parser) {
+               o->parser = parser;
+       } else {
+               o->parser = php_http_header_parser_init(NULL);
+       }
+       o->buffer = php_http_buffer_new();
+
+       o->zo.handlers = &php_http_header_parser_object_handlers;
+
+       return o;
+}
+
+void php_http_header_parser_object_free(zend_object *object)
+{
+       php_http_header_parser_object_t *o = PHP_HTTP_OBJ(object, NULL);
+
+       if (o->parser) {
+               php_http_header_parser_free(&o->parser);
+       }
+       if (o->buffer) {
+               php_http_buffer_free(&o->buffer);
+       }
+       zend_object_std_dtor(object);
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_getState, 0, 0, 0)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpHeaderParser, getState)
+{
+       php_http_header_parser_object_t *parser_obj = PHP_HTTP_OBJ(NULL, getThis());
+
+       zend_parse_parameters_none();
+       /* always return the real state */
+       RETVAL_LONG(php_http_header_parser_state_is(parser_obj->parser));
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_parse, 0, 0, 3)
+       ZEND_ARG_INFO(0, data)
+       ZEND_ARG_INFO(0, flags)
+       ZEND_ARG_ARRAY_INFO(1, headers, 1)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpHeaderParser, parse)
+{
+       php_http_header_parser_object_t *parser_obj;
+       zval *zmsg;
+       char *data_str;
+       size_t data_len;
+       zend_long flags;
+
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "slz", &data_str, &data_len, &flags, &zmsg), invalid_arg, return);
+
+       ZVAL_DEREF(zmsg);
+       if (Z_TYPE_P(zmsg) != IS_ARRAY) {
+               zval_dtor(zmsg);
+               array_init(zmsg);
+       }
+       parser_obj = PHP_HTTP_OBJ(NULL, getThis());
+       php_http_buffer_append(parser_obj->buffer, data_str, data_len);
+       RETVAL_LONG(php_http_header_parser_parse(parser_obj->parser, parser_obj->buffer, flags, Z_ARRVAL_P(zmsg), NULL, NULL));
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_stream, 0, 0, 3)
+       ZEND_ARG_INFO(0, stream)
+       ZEND_ARG_INFO(0, flags)
+       ZEND_ARG_ARRAY_INFO(1, headers, 1)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpHeaderParser, stream)
+{
+       php_http_header_parser_object_t *parser_obj;
+       zend_error_handling zeh;
+       zval *zmsg, *zstream;
+       php_stream *s;
+       zend_long flags;
+
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "rlz", &zstream, &flags, &zmsg), invalid_arg, return);
+
+       zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh);
+       php_stream_from_zval(s, zstream);
+       zend_restore_error_handling(&zeh);
+
+       ZVAL_DEREF(zmsg);
+       if (Z_TYPE_P(zmsg) != IS_ARRAY) {
+               zval_dtor(zmsg);
+               array_init(zmsg);
+       }
+       parser_obj = PHP_HTTP_OBJ(NULL, getThis());
+       RETVAL_LONG(php_http_header_parser_parse_stream(parser_obj->parser, parser_obj->buffer, s, flags, Z_ARRVAL_P(zmsg), NULL, NULL));
+}
+
+static zend_function_entry php_http_header_parser_methods[] = {
+               PHP_ME(HttpHeaderParser, getState, ai_HttpHeaderParser_getState, ZEND_ACC_PUBLIC)
+               PHP_ME(HttpHeaderParser, parse, ai_HttpHeaderParser_parse, ZEND_ACC_PUBLIC)
+               PHP_ME(HttpHeaderParser, stream, ai_HttpHeaderParser_stream, ZEND_ACC_PUBLIC)
+               {NULL, NULL, NULL}
+};
+
+PHP_MINIT_FUNCTION(http_header_parser)
+{
+       zend_class_entry ce;
+
+       INIT_NS_CLASS_ENTRY(ce, "http\\Header", "Parser", php_http_header_parser_methods);
+       php_http_header_parser_class_entry = zend_register_internal_class(&ce);
+       memcpy(&php_http_header_parser_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_header_parser_class_entry->create_object = php_http_header_parser_object_new;
+       php_http_header_parser_object_handlers.offset = XtOffsetOf(php_http_header_parser_object_t, zo);
+       php_http_header_parser_object_handlers.clone_obj = NULL;
+       php_http_header_parser_object_handlers.free_obj = php_http_header_parser_object_free;
+
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_HEADER_PARSER_CLEANUP);
+
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_HEADER_PARSER_STATE_START);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_KEY"), PHP_HTTP_HEADER_PARSER_STATE_KEY);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE"), PHP_HTTP_HEADER_PARSER_STATE_VALUE);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE_EX"), PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_HEADER_PARSER_STATE_DONE);
+
+       return SUCCESS;
+}
+
 /*
  * Local variables:
  * tab-width: 4
index 5b4993a459f070ed33fe27f2c40f8081f23c5e51..33707bd6fb5709ec5643fe950567e9747447bca5 100644 (file)
@@ -47,6 +47,21 @@ PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_pop(php
 PHP_HTTP_API void php_http_header_parser_dtor(php_http_header_parser_t *parser);
 PHP_HTTP_API void php_http_header_parser_free(php_http_header_parser_t **parser);
 PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg);
+PHP_HTTP_API php_http_header_parser_state_t php_http_headerparser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buffer, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg);
+
+typedef struct php_http_header_parser_object {
+       php_http_buffer_t *buffer;
+       php_http_header_parser_t *parser;
+       zend_object zo;
+} php_http_header_parser_object_t;
+
+PHP_HTTP_API zend_class_entry *php_http_header_parser_class_entry;
+
+PHP_MINIT_FUNCTION(http_header_parser);
+
+zend_object *php_http_header_parser_object_new(zend_class_entry *ce);
+php_http_header_parser_object_t *php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser);
+void php_http_header_parser_object_free(zend_object *object);
 
 #endif /* PHP_HTTP_HEADER_PARSER_H */
 
index 6ecf6e737831eb818d75beda189f431271ea6c57..3d3822cfeeb8326e455e5aae29d8d1afa93e8b38 100644 (file)
@@ -275,6 +275,8 @@ void php_http_message_update_headers(php_http_message_t *msg)
 
        if (php_http_message_body_stream(msg->body)->readfilters.head) {
                /* if a read stream filter is attached to the body the caller must also care for the headers */
+       } else if (php_http_message_header(msg, ZEND_STRL("Content-Range"))) {
+               /* don't mess around with a Content-Range message */
        } else if ((size = php_http_message_body_size(msg->body))) {
                ZVAL_LONG(&h, size);
                zend_hash_str_update(&msg->hdrs, "Content-Length", lenof("Content-Length"), &h);
@@ -299,6 +301,7 @@ void php_http_message_update_headers(php_http_message_t *msg)
                }
        } else if ((cl = php_http_message_header_string(msg, ZEND_STRL("Content-Length")))) {
                if (!zend_string_equals_literal(cl, "0")) {
+                       /* body->size == 0, so get rid of old Content-Length */
                        zend_hash_str_del(&msg->hdrs, ZEND_STRL("Content-Length"));
                }
                zend_string_release(cl);
index 1c540c77389dba84e58b1040128ae040579cfec8..4a3174a62a785c649d563ad7281874435d97e88f 100644 (file)
@@ -113,7 +113,7 @@ const char *php_http_message_body_boundary(php_http_message_body_t *body)
        if (!body->boundary) {
                union { double dbl; int num[2]; } data;
 
-               data.dbl = php_combined_lcg(TSRMLS_C);
+               data.dbl = php_combined_lcg();
                spprintf(&body->boundary, 0, "%x.%x", data.num[0], data.num[1]);
        }
        return body->boundary;
@@ -121,24 +121,28 @@ const char *php_http_message_body_boundary(php_http_message_body_t *body)
 
 char *php_http_message_body_etag(php_http_message_body_t *body)
 {
-       const php_stream_statbuf *ssb = php_http_message_body_stat(body);
+       php_http_etag_t *etag;
+       php_stream *s = php_http_message_body_stream(body);
 
        /* real file or temp buffer ? */
-       if (ssb->sb.st_mtime) {
-               char *etag;
+       if (s->ops != &php_stream_temp_ops && s->ops != &php_stream_memory_ops) {
+               php_stream_stat(php_http_message_body_stream(body), &body->ssb);
 
-               spprintf(&etag, 0, "%lx-%lx-%lx", ssb->sb.st_ino, ssb->sb.st_mtime, ssb->sb.st_size);
-               return etag;
-       } else {
-               php_http_etag_t *etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode);
+               if (body->ssb.sb.st_mtime) {
+                       char *etag;
 
-               if (etag) {
-                       php_http_message_body_to_callback(body, (php_http_pass_callback_t) php_http_etag_update, etag, 0, 0);
-                       return php_http_etag_finish(etag);
-               } else {
-                       return NULL;
+                       spprintf(&etag, 0, "%lx-%lx-%lx", body->ssb.sb.st_ino, body->ssb.sb.st_mtime, body->ssb.sb.st_size);
+                       return etag;
                }
        }
+
+       /* content based */
+       if ((etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode))) {
+               php_http_message_body_to_callback(body, (php_http_pass_callback_t) php_http_etag_update, etag, 0, 0);
+               return php_http_etag_finish(etag);
+       }
+
+       return NULL;
 }
 
 zend_string *php_http_message_body_to_string(php_http_message_body_t *body, off_t offset, size_t forlen)
@@ -212,7 +216,7 @@ size_t php_http_message_body_append(php_http_message_body_t *body, const char *b
        written = php_stream_write(s, buf, len);
 
        if (written != len) {
-               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to append %zu bytes to body; wrote %zu", len, written);
+               php_error_docref(NULL, E_WARNING, "Failed to append %zu bytes to body; wrote %zu", len, written);
        }
 
        return len;
@@ -584,7 +588,7 @@ void php_http_message_body_object_free(zend_object *object)
        php_http_message_body_object_t *obj = PHP_HTTP_OBJ(object, NULL);
 
        php_http_message_body_free(&obj->body);
-       zend_object_std_dtor(object TSRMLS_CC);
+       zend_object_std_dtor(object);
 }
 
 #define PHP_HTTP_MESSAGE_BODY_OBJECT_INIT(obj) \
@@ -813,7 +817,7 @@ PHP_METHOD(HttpMessageBody, stat)
        char *field_str = NULL;
        size_t field_len = 0;
 
-       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &field_str, &field_len)) {
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "|s", &field_str, &field_len)) {
                php_http_message_body_object_t *obj = PHP_HTTP_OBJ(NULL, getThis());
                const php_stream_statbuf *sb;
 
index e40e7b3e79c1edb282da9dd16406b8ddc7e65920..7bf9a164f5a14cad90d42617cfb114fc7455ab80 100644 (file)
@@ -23,19 +23,20 @@ typedef struct php_http_message_parser_state_spec {
 
 static const php_http_message_parser_state_spec_t php_http_message_parser_states[] = {
                {PHP_HTTP_MESSAGE_PARSER_STATE_START,                   1},
-               {PHP_HTTP_MESSAGE_PARSER_STATE_HEADER,                  1},
+               {PHP_HTTP_MESSAGE_PARSER_STATE_HEADER,                  0},
                {PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE,             0},
                {PHP_HTTP_MESSAGE_PARSER_STATE_BODY,                    0},
                {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB,               1},
                {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH,             1},
                {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED,    1},
                {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE,               0},
+               {PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL,               0},
                {PHP_HTTP_MESSAGE_PARSER_STATE_DONE,                    0}
 };
 
 #if DBG_PARSER
 const char *php_http_message_parser_state_name(php_http_message_parser_state_t state) {
-       const char *states[] = {"START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "DONE"};
+       const char *states[] = {"START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "UPDATE_CL", "DONE"};
        
        if (state < 0 || state > (sizeof(states)/sizeof(char*))-1) {
                return "FAILURE";
@@ -120,18 +121,30 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me
        if (!buf->data) {
                php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
        }
-
-       while (!php_stream_eof(s)) {
+       while (1) {
                size_t justread = 0;
 #if DBG_PARSER
                fprintf(stderr, "#SP: %s (f:%u)\n", php_http_message_parser_state_name(state), flags);
 #endif
+               /* resize if needed */
+               if (buf->free < 0x1000) {
+                       php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
+               }
                switch (state) {
                        case PHP_HTTP_MESSAGE_PARSER_STATE_START:
                        case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER:
                        case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE:
                                /* read line */
                                php_stream_get_line(s, buf->data + buf->used, buf->free, &justread);
+                               /* if we fail reading a whole line, try a single char */
+                               if (!justread) {
+                                       int c = php_stream_getc(s);
+
+                                       if (c != EOF) {
+                                               char s[1] = {c};
+                                               justread = php_http_buffer_append(buf, s, 1);
+                                       }
+                               }
                                php_http_buffer_account(buf, justread);
                                break;
 
@@ -166,6 +179,7 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me
 
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL:
                                /* should not occur */
                                abort();
                                break;
@@ -177,7 +191,9 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me
 
                if (justread) {
                        state = php_http_message_parser_parse(parser, buf, flags, message);
-               } else  {
+               } else if (php_stream_eof(s)) {
+                       return php_http_message_parser_parse(parser, buf, flags | PHP_HTTP_MESSAGE_PARSER_CLEANUP, message);
+               } else {
                        return state;
                }
        }
@@ -237,9 +253,10 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                break;
 
                                        default:
-                                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER);
-                                               if (buffer->used) {
-                                                       return PHP_HTTP_MESSAGE_PARSER_STATE_HEADER;
+                                               if (buffer->used || !(flags & PHP_HTTP_MESSAGE_PARSER_CLEANUP)) {
+                                                       return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER);
+                                               } else {
+                                                       php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE);
                                                }
                                }
                                break;
@@ -252,6 +269,11 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                zend_long content_length = -1;
                                zend_string *content_range = NULL;
 
+                               /* Content-Range has higher precedence than Content-Length,
+                                * and content-length denotes the original length of the entity,
+                                * so let's *NOT* remove CR/CL, because that would fundamentally
+                                * change the meaning of the whole message
+                                */
                                if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Transfer-Encoding")))) {
                                        zend_string *zs = zval_get_string(h_ptr);
 
@@ -262,25 +284,20 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                        zend_hash_str_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", lenof("X-Original-Transfer-Encoding"), h_ptr);
                                        zend_hash_str_del(&(*message)->hdrs, "Transfer-Encoding", lenof("Transfer-Encoding"));
 
-                               }
-                               if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Length")))) {
+                                       /* reset */
+                                       ZVAL_LONG(&h, 0);
+                                       zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &h);
+                               } else if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Length")))) {
                                        content_length = zval_get_long(h_ptr);
-
                                        Z_TRY_ADDREF_P(h_ptr);
                                        zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Length", lenof("X-Original-Content-Length"), h_ptr);
                                }
-                               if ((h_ptr = php_http_message_header(*message, ZEND_STRL("Content-Range")))) {
-                                       content_range = zval_get_string(h_ptr);
 
-                                       Z_TRY_ADDREF_P(h_ptr);
-                                       zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Range", lenof("X-Original-Content-Range"), h_ptr);
-                                       zend_hash_str_del(&(*message)->hdrs, "Content-Range", lenof("Content-Range"));
+                               if ((content_range = php_http_message_header_string(*message, ZEND_STRL("Content-Range")))) {
+                                       ZVAL_STR_COPY(&h, content_range);
+                                       zend_hash_str_update(&(*message)->hdrs, "Content-Range", lenof("Content-Range"), &h);
                                }
 
-                               /* default */
-                               ZVAL_LONG(&h, 0);
-                               zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &h);
-
                                /* so, if curl sees a 3xx code, a Location header and a Connection:close header
                                 * it decides not to read the response body.
                                 */
@@ -290,16 +307,22 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                &&      (h_loc = php_http_message_header(*message, ZEND_STRL("Location")))
                                &&      (h_con = php_http_message_header(*message, ZEND_STRL("Connection")))
                                ) {
-                                       if (php_http_match(Z_STRVAL_P(h_con), "close", PHP_HTTP_MATCH_WORD)) {
+                                       zend_string *con = zval_get_string(h_con);
+
+                                       if (php_http_match(con->val, "close", PHP_HTTP_MATCH_WORD)) {
+                                               zend_string_release(con);
                                                php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
                                                break;
                                        }
+                                       zend_string_release(con);
                                }
 
                                if ((h_ce = php_http_message_header(*message, ZEND_STRL("Content-Encoding")))) {
-                                       if (php_http_match(Z_STRVAL_P(h_ce), "gzip", PHP_HTTP_MATCH_WORD)
-                                       ||      php_http_match(Z_STRVAL_P(h_ce), "x-gzip", PHP_HTTP_MATCH_WORD)
-                                       ||      php_http_match(Z_STRVAL_P(h_ce), "deflate", PHP_HTTP_MATCH_WORD)
+                                       zend_string *ce = zval_get_string(h_ce);
+
+                                       if (php_http_match(ce->val, "gzip", PHP_HTTP_MATCH_WORD)
+                                       ||      php_http_match(ce->val, "x-gzip", PHP_HTTP_MATCH_WORD)
+                                       ||      php_http_match(ce->val, "deflate", PHP_HTTP_MATCH_WORD)
                                        ) {
                                                if (parser->inflate) {
                                                        php_http_encoding_stream_reset(&parser->inflate);
@@ -310,6 +333,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                zend_hash_str_update(&(*message)->hdrs, "X-Original-Content-Encoding", lenof("X-Original-Content-Encoding"), h_ce);
                                                zend_hash_str_del(&(*message)->hdrs, "Content-Encoding", lenof("Content-Encoding"));
                                        }
+                                       zend_string_release(ce);
                                }
 
                                if ((flags & PHP_HTTP_MESSAGE_PARSER_DUMB_BODIES)) {
@@ -321,12 +345,6 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                break;
                                        }
 
-                                       if (content_length >= 0) {
-                                               parser->body_length = content_length;
-                                               php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
-                                               break;
-                                       }
-
                                        if (content_range) {
                                                ulong total = 0, start = 0, end = 0;
 
@@ -346,7 +364,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                                        total = strtoul(total_at + 1, NULL, 10);
                                                                }
 
-                                                               if (end >= start && (!total || end < total)) {
+                                                               if (end >= start && (!total || end <= total)) {
                                                                        parser->body_length = end + 1 - start;
                                                                        php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
                                                                        zend_string_release(content_range);
@@ -358,6 +376,11 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                zend_string_release(content_range);
                                        }
 
+                                       if (content_length >= 0) {
+                                               parser->body_length = content_length;
+                                               php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
+                                               break;
+                                       }
 
                                        if ((*message)->type == PHP_HTTP_REQUEST) {
                                                php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
@@ -371,8 +394,6 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
                        {
                                if (len) {
-                                       zval zcl;
-
                                        if (parser->inflate) {
                                                char *dec_str = NULL;
                                                size_t dec_len;
@@ -389,10 +410,6 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                        }
 
                                        php_stream_write(php_http_message_body_stream((*message)->body), str, len);
-
-                                       /* keep track */
-                                       ZVAL_LONG(&zcl, php_http_message_body_size((*message)->body));
-                                       zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &zcl);
                                }
 
                                if (cut) {
@@ -465,7 +482,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                        {
                                php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
 
-                               if (parser->dechunk) {
+                               if (parser->dechunk && parser->dechunk->ctx) {
                                        char *dec_str = NULL;
                                        size_t dec_len;
 
@@ -478,14 +495,24 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                str = dec_str;
                                                len = dec_len;
                                                cut = 0;
-                                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
+                                               php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
                                        }
                                }
 
                                break;
                        }
 
-                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: {
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL:
+                       {
+                               zval zcl;
+
+                               ZVAL_LONG(&zcl, php_http_message_body_size((*message)->body));
+                               zend_hash_str_update(&(*message)->hdrs, "Content-Length", lenof("Content-Length"), &zcl);
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE:
+                       {
                                char *ptr = buffer->data;
 
                                while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
@@ -648,6 +675,7 @@ PHP_MINIT_FUNCTION(http_message_parser)
        zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_LENGTH"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
        zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_CHUNKED"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED);
        zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE);
+       zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_UPDATE_CL"), PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL);
        zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
 
        return SUCCESS;
index 71d91f066c964010013f0b671ef57a04042ac83d..fa6385845bc7a8906f974430bf378ba1c216a996 100644 (file)
@@ -27,6 +27,7 @@ typedef enum php_http_message_parser_state {
        PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH,
        PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED,
        PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE,
+       PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL,
        PHP_HTTP_MESSAGE_PARSER_STATE_DONE
 } php_http_message_parser_state_t;
 
index d9b9e4ec5fb75df794cfbbf3bcda77c8d2a20d66..421c9912cf57082283554c2c4ca759c82a098c4e 100644 (file)
@@ -27,6 +27,9 @@
 /* send buffer size */
 #define PHP_HTTP_SENDBUF_SIZE 40960
 
+/* allowed characters of header field names */
+#define PHP_HTTP_HEADER_NAME_CHARS "!#$%&'*+-.^_`|~1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
+
 /* SLEEP */
 
 #define PHP_HTTP_DIFFSEC (0.001)
diff --git a/php_http_response_codes.h b/php_http_response_codes.h
new file mode 100644 (file)
index 0000000..82157f9
--- /dev/null
@@ -0,0 +1,83 @@
+/*
+    +--------------------------------------------------------------------+
+    | PECL :: http                                                       |
+    +--------------------------------------------------------------------+
+    | Redistribution and use in source and binary forms, with or without |
+    | modification, are permitted provided that the conditions mentioned |
+    | in the accompanying LICENSE file are met.                          |
+    +--------------------------------------------------------------------+
+    | Copyright (c) 2004-2015, Michael Wallner <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
+ */
diff --git a/php_http_strlist.h b/php_http_strlist.h
deleted file mode 100644 (file)
index 2685423..0000000
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
-    +--------------------------------------------------------------------+
-    | PECL :: http                                                       |
-    +--------------------------------------------------------------------+
-    | Redistribution and use in source and binary forms, with or without |
-    | modification, are permitted provided that the conditions mentioned |
-    | in the accompanying LICENSE file are met.                          |
-    +--------------------------------------------------------------------+
-    | Copyright (c) 2004-2014, Michael Wallner <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
- */
-
index b7402e93cff861a025be126e68b3f72d211291f5..6ae0ac38af2b081d9967b1876b544e25fac0146c 100644 (file)
@@ -79,21 +79,6 @@ static inline void php_http_url_argsep(const char **str, size_t *len)
        }
 }
 
-static inline php_url *php_http_url_to_php_url(php_http_url_t *url)
-{
-       php_url *purl = ecalloc(1, sizeof(*purl));
-
-       if (url->scheme)   purl->scheme   = estrdup(url->scheme);
-       if (url->pass)     purl->pass     = estrdup(url->pass);
-       if (url->user)     purl->user     = estrdup(url->user);
-       if (url->host)     purl->host     = estrdup(url->host);
-       if (url->path)     purl->path     = estrdup(url->path);
-       if (url->query)    purl->query    = estrdup(url->query);
-       if (url->fragment) purl->fragment = estrdup(url->fragment);
-
-       return purl;
-}
-
 static inline zend_bool php_http_url_is_empty(const php_http_url_t *url) {
        return !(url->scheme || url->pass || url->user || url->host || url->port ||     url->path || url->query || url->fragment);
 }
index c7bcb49428c53a79234593390c3705d9ca791af5..2503f0644e605db64aae577b6523e641b3e54098 100644 (file)
@@ -555,7 +555,7 @@ static inline size_t utf8towc(unsigned *wc, const unsigned char *uc, size_t len)
 {
        unsigned char ub = utf8_mblen[*uc];
 
-       if (!ub || ub > len || ub > 3) {
+       if (!ub || ub > len || ub > 4) {
                return 0;
        }
 
@@ -595,9 +595,9 @@ static inline size_t utf8towc(unsigned *wc, const unsigned char *uc, size_t len)
 
 static inline zend_bool isualpha(unsigned ch)
 {
-       unsigned i, j;
+       unsigned i = 0, j;
 
-       for (i = 0; i < sizeof(utf8_ranges)/sizeof(utf8_range_t); ++i) {
+       PHP_HTTP_DUFF(sizeof(utf8_ranges)/sizeof(utf8_range_t),
                if (utf8_ranges[i].start == ch) {
                        return 1;
                } else if (utf8_ranges[i].start <= ch && utf8_ranges[i].end >= ch) {
@@ -611,7 +611,8 @@ static inline zend_bool isualpha(unsigned ch)
                        }
                        return 0;
                }
-       }
+               ++i;
+       );
        return 0;
 }
 
index 8071afa411ddbcc90389b4a610091dcdd2f6338c..dea89a84aa2788ae9b0b4c950b0c1ea67e6ef388 100644 (file)
@@ -3,6 +3,7 @@ client drivers
 --SKIPIF--
 <?php
 include "skipif.inc";
+skip_client_test();
 ?>
 --FILE--
 <?php
index 89d4d658b8a58ee2d5cfcf94cf72ca4b8c319bbe..aeb2d8ac7041e888475178b190db8175823f63b5 100644 (file)
@@ -3,10 +3,13 @@ client observer
 --SKIPIF--
 <?php
 include "skipif.inc";
-skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php
+
+include "helper/server.inc";
+
 echo "Test\n";
 
 class Observer implements SplObserver
@@ -19,15 +22,14 @@ 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();
+       }
+});
 
 ?>
 
index 62d0bca64be7eee3e5522918605ecdac601797ca..a6b1a4c7cfb4fef4d971196e6fe70bd6a99e1f6e 100644 (file)
@@ -3,26 +3,31 @@ client once & wait
 --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--
index 1342efaadd61601e07e08675015b532032f94b06..1b3bb5ecdca988b214624150f7b061d6e625f914 100644 (file)
@@ -3,31 +3,36 @@ client reset
 --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--
index 4576f16d0868785d328dd3cb8fac30dd8ca6557d..b73b5bb01dd01e2648c0b74b32449ad7c49ee751 100644 (file)
@@ -3,22 +3,27 @@ client response callback
 --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
index 7b3986e583a2703f38a6153099d4091aab9cd5f0..1621cc71b0ff5d146947f41d40c1f18fef9d5cdb 100644 (file)
@@ -3,10 +3,13 @@ client response callback + dequeue
 --SKIPIF--
 <?php
 include "skipif.inc";
-skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php
+
+include "helper/server.inc";
+
 echo "Test\n";
 
 function response($response) {
@@ -19,20 +22,22 @@ 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
index 74cdbcd9d06f677797338ab422a324bbd1679510..f0100a6af290cc9f42ee6ba834f046e264cef2dc 100644 (file)
@@ -3,10 +3,13 @@ client response callback + requeue
 --SKIPIF--
 <?php
 include "skipif.inc";
-skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php
+
+include "helper/server.inc";
+
 echo "Test\n";
 
 function response($response) {
@@ -16,15 +19,17 @@ 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
index 31584d30c548e36372ef2695ec01c9607a90f6a8..f8b8774eb8edc1c7511b8c7961438470fd9d5a88 100644 (file)
@@ -3,29 +3,35 @@ client features
 --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
index e1553d65449240ec83a1d70875b4023c4881af5c..9b5579dc71994a80f94a6d1c9ec903c1ad99fc1f 100644 (file)
@@ -4,6 +4,7 @@ client static cookies
 <?php
 include "skipif.inc";
 skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php
index 055d5855c916284eb5b146eaf05be80c38023d95..86f9c8c94da026c364e2fe557c64901c9ddbc551 100644 (file)
@@ -4,6 +4,7 @@ client upload
 <?php
 include "skipif.inc";
 skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php
index 284edb25d10dac448cbd7230c328510373b02e06..59097420c72f5b35d63469a9d3fa588f4ed231af 100644 (file)
@@ -4,6 +4,7 @@ client history
 <?php 
 include "skipif.inc";
 skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php 
index e4c188c7ffd44cd8bac36c5567e859ad5ea2e4a3..306f26eb9d3101c52e7538b1b44d220e4b85374d 100644 (file)
@@ -4,10 +4,10 @@ client ssl
 <?php 
 include "skipif.inc";
 skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php 
-
 echo "Test\n";
 
 $client = new http\Client;
@@ -26,7 +26,7 @@ $client->send();
 
 $ti = (array) $client->getTransferInfo($req);
 var_dump(array_key_exists("ssl_engines", $ti));
-var_dump(0 < count($ti["ssl_engines"]));
+var_dump(0 < count($ti["ssl_engines"] || $ti["tls_session"]["backend"] != "openssl"));
 ?>
 Done
 --EXPECTF--
index 00bae4e0de5c7f7419b42c46c1240d5a1d33d9e4..fdf6c969c37587884ebc472214e21c3598335a50 100644 (file)
@@ -3,11 +3,13 @@ client observers
 --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 {
@@ -33,33 +35,36 @@ class CallbackObserver implements SplObserver {
        }
 }
 
-$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
index d0ff4716b276786bfbd7c0a7ca80c1ddf597ac7c..a4504c1291833c70f38d5645758b137b0cc224f5 100644 (file)
@@ -3,6 +3,7 @@ reset content length when resetting body
 --SKIPIF--
 <?php 
 include "skipif.inc";
+skip_client_test();
 ?>
 --FILE--
 <?php 
index 60d31323efc350230b7dc341ea2f866eab520470..70b5f43bc603c8a417bc08eeeac0d5c5787ba674 100644 (file)
@@ -3,37 +3,35 @@ http client event base
 --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--
index f50d9bb8cda5d0236e18936fff8eabeed87098f2..58f97c1ff0332cf8a1e7cace580e33499063e5df 100644 (file)
@@ -3,34 +3,32 @@ client once & wait with events
 --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--
diff --git a/tests/client018.phpt b/tests/client018.phpt
new file mode 100644 (file)
index 0000000..7acb911
--- /dev/null
@@ -0,0 +1,56 @@
+--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===
diff --git a/tests/client019.phpt b/tests/client019.phpt
new file mode 100644 (file)
index 0000000..e60e5aa
--- /dev/null
@@ -0,0 +1,45 @@
+--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===
diff --git a/tests/client020.phpt b/tests/client020.phpt
new file mode 100644 (file)
index 0000000..7ea5d60
--- /dev/null
@@ -0,0 +1,41 @@
+--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===
diff --git a/tests/client021.phpt b/tests/client021.phpt
new file mode 100644 (file)
index 0000000..41a220a
--- /dev/null
@@ -0,0 +1,105 @@
+--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===
diff --git a/tests/client022.phpt b/tests/client022.phpt
new file mode 100644 (file)
index 0000000..255de08
--- /dev/null
@@ -0,0 +1,45 @@
+--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===
diff --git a/tests/client023.phpt b/tests/client023.phpt
new file mode 100644 (file)
index 0000000..b09c2e2
--- /dev/null
@@ -0,0 +1,39 @@
+--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===
diff --git a/tests/client024.phpt b/tests/client024.phpt
new file mode 100644 (file)
index 0000000..27a68b4
--- /dev/null
@@ -0,0 +1,25 @@
+--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===
diff --git a/tests/client025.phpt b/tests/client025.phpt
new file mode 100644 (file)
index 0000000..18c5095
--- /dev/null
@@ -0,0 +1,41 @@
+--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===
diff --git a/tests/client026.phpt b/tests/client026.phpt
new file mode 100644 (file)
index 0000000..d89e98e
--- /dev/null
@@ -0,0 +1,44 @@
+--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===
index 270512b358ab926c94fd2edda42d17390aa6048a..705ee2e3e173ef83c477ba260a67ed173280a851 100644 (file)
@@ -4,6 +4,7 @@ client response cookie
 <?php
 include "skipif.inc";
 skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php
index 2379cc7e6ddf3e7717e373a1b365b9253dc79630..8c57355b46b016cbfc64fca948b44c83add6f6a0 100644 (file)
@@ -4,6 +4,7 @@ client response cookies
 <?php
 include "skipif.inc";
 skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php
index 8ab936d059b94b94b4302fe77faeada7c90676f4..60ba07b2265c6409d53add0c15c117fba9ebbc32 100644 (file)
@@ -3,20 +3,25 @@ client response transfer info
 --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--
diff --git a/tests/data/message_r_content_range.txt b/tests/data/message_r_content_range.txt
new file mode 100644 (file)
index 0000000..d42dbff
--- /dev/null
@@ -0,0 +1,9 @@
+PUT / HTTP/1.1
+User-Agent: PECL_HTTP/2.3.0dev PHP/5.6.6-dev libcurl/7.41.0-DEV
+Host: localhost:8000
+Accept: */*
+Expect: 100-continue
+Content-Length: 3
+Content-Range: bytes 1-2/3
+
+23
\ No newline at end of file
diff --git a/tests/envresponse018.phpt b/tests/envresponse018.phpt
new file mode 100644 (file)
index 0000000..bed8526
--- /dev/null
@@ -0,0 +1,33 @@
+--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===
index aa91901fb7f7769193281fef084909f473a3f046..7fb4dd1141a7dc01174d842903677bb13200b796 100644 (file)
@@ -17,5 +17,5 @@ Done
 --EXPECTF--
 Test
 
-Warning: http\Header::parse(): Could not parse headers in %s on line %d
+Warning: http\Header::parse(): Failed to parse headers: unexpected end of line at pos 4 of 'wass\nup' in %s on line %d
 Done
diff --git a/tests/headerparser001.phpt b/tests/headerparser001.phpt
new file mode 100644 (file)
index 0000000..0a1eb37
--- /dev/null
@@ -0,0 +1,61 @@
+--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===
diff --git a/tests/headerparser002.phpt b/tests/headerparser002.phpt
new file mode 100644 (file)
index 0000000..c5a02f1
--- /dev/null
@@ -0,0 +1,59 @@
+--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
diff --git a/tests/headerparser003.phpt b/tests/headerparser003.phpt
new file mode 100644 (file)
index 0000000..e1954e7
--- /dev/null
@@ -0,0 +1,54 @@
+--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
diff --git a/tests/helper/cookie.inc b/tests/helper/cookie.inc
new file mode 100644 (file)
index 0000000..d33666b
--- /dev/null
@@ -0,0 +1,11 @@
+<?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);
+});
diff --git a/tests/helper/html/index.html b/tests/helper/html/index.html
new file mode 100644 (file)
index 0000000..11280ba
--- /dev/null
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+       <head>
+               <meta charset="utf-8">
+               <title>HTTP2</title>
+       </head>
+       <body>
+               Nothing to see here.
+       </body>
+</html>
diff --git a/tests/helper/http2.crt b/tests/helper/http2.crt
new file mode 100644 (file)
index 0000000..c297162
--- /dev/null
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDNzCCAh+gAwIBAgIJAKOw1awbt7aIMA0GCSqGSIb3DQEBCwUAMDIxCzAJBgNV
+BAYTAkFUMQ8wDQYDVQQKDAZQSFAgUUExEjAQBgNVBAMMCWxvY2FsaG9zdDAeFw0x
+NTAyMTIxMjQ2NTRaFw0xNzExMDcxMjQ2NTRaMDIxCzAJBgNVBAYTAkFUMQ8wDQYD
+VQQKDAZQSFAgUUExEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEB
+BQADggEPADCCAQoCggEBAMxa+A6xEKQOYme55nQyu0qpvvGB4c4wGBNa6X6YAEzE
+Hc19Nbix02UZWQgHM1dmBhbVDW3stO42CQmcViIKfAy5R1A9UZjUcn9nQpaxL/sp
+9LrrCANyljISXS40QcBvhCzmcUvDWBRhVTGjV+QTaYmmaM+8hJJJM6W7ByIP22Zv
+jHoAnzTx9sjs+OMyWmjYT9pWv6aE+u5iSC8bn3B0GgtfPwoPqDF8ePxIMBpmtbk7
+JqXFzHxTVywkypnsF34XK94QjpvRcFGSg/Dc1TopJG2Wfq8hxwtoKerSlL5tyKy0
+ZrltxCiLkfVZixwNZEqkg7jPSUvLS299mBrwy+ikmr8CAwEAAaNQME4wHQYDVR0O
+BBYEFDiHynoXXjMChfYhc1k7TNtU8ey0MB8GA1UdIwQYMBaAFDiHynoXXjMChfYh
+c1k7TNtU8ey0MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAGD9GERC
+uJv+oHfMwkNkDV5ZB4F+SQPzXVxDx+rJm1aGJs9PcwPNiV5GweXbvgcnvRAL4h8h
+uv3MLQPgVOq0iqp1QPFCoUXxbYYjYzi9FVbR/154sg0uWEHElU2e3fSjcinNRfXD
+12232k6HNwSeeZUFQqn2fuk+cae5BsYT8GfsyMq5EfPtG2d8IG+Ji4eEOJeKu4gl
+Y33yQnHhw6QKbx8vWaDpZK8qtpapCtLrTtw/nRhX5YV6kMGK+htQUK1etV2O0VQQ
+OtDrhOWWaDxQULsHIvCMgTTnZqNQD+Xz4MBm3PGEGi/QUsrEaSQYppb8xxQKZU4X
+MyTh7rO5TdNSXmo=
+-----END CERTIFICATE-----
diff --git a/tests/helper/http2.key b/tests/helper/http2.key
new file mode 100644 (file)
index 0000000..bcf4f3f
--- /dev/null
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEpQIBAAKCAQEAzFr4DrEQpA5iZ7nmdDK7Sqm+8YHhzjAYE1rpfpgATMQdzX01
+uLHTZRlZCAczV2YGFtUNbey07jYJCZxWIgp8DLlHUD1RmNRyf2dClrEv+yn0uusI
+A3KWMhJdLjRBwG+ELOZxS8NYFGFVMaNX5BNpiaZoz7yEkkkzpbsHIg/bZm+MegCf
+NPH2yOz44zJaaNhP2la/poT67mJILxufcHQaC18/Cg+oMXx4/EgwGma1uTsmpcXM
+fFNXLCTKmewXfhcr3hCOm9FwUZKD8NzVOikkbZZ+ryHHC2gp6tKUvm3IrLRmuW3E
+KIuR9VmLHA1kSqSDuM9JS8tLb32YGvDL6KSavwIDAQABAoIBAQCzUdAB9FYJ36Vy
+J6qVpD69EZ7ABZzDdWhq84eY0oDQ2/ba7lhJraE2QbviU481zgzh1CponyFVNo1P
+paPfUxvvflWZj3Ueiq2+JjpESU81MmfR7ZOmktJBNeQWOzzHRBPT4pLgTJXprE85
+s3/YX0BozWGDiIU8aIetkiR8OzXm97+BrJWiPFl733dGnHvfpHAZu/PwKZc2+8ft
+CnQw4GHRhTBWCVNj29HLwm+CA66zQqiAXItgijWMKzs+9ciPn+aCsCnZDNF+o1zs
+4pWt60CtIJQtD3r3rSRy7nBaCKcTrr/JU3FvwqKdunuUBUsuYeSaMBokW67kpVzS
+dO9L9p6BAoGBAP+pvcAd8qfC1WIrn9vka3aK25ulbYSCae3YaWmABc93INJPBMvO
+GrcUuaLKiQC1oou+E64vGyJ9MeEFwxh2zbvU75a9ezeKAajgaq92ciMX2QqREh0N
+IntNOc4w7eB6BK8NpaDXNvTtxNWMvlYyhVN0M7FVQRmYJfCJdnTZAkzvAoGBAMyf
+6rvWuc/wmIcAtBVe+jIeQ69EJJ/Idcjk0JUm6lFECAAraPpsCglha+wTHWWRQZ9u
+pPqBnb7QPjevU+3bZHnMvGoEzd5F4Rw74J+p5EZeMUJpuKmon7Ekzemcb0DV+qX9
+NorB781D2Z0MG9SCptKyKpN5TPHTjGz4BB3mLC8xAoGAdq99/ynn9ClmleRameI4
+YRelS2RIqzM/qcLFbMyZ5e4PtpIoT9SmYkekxgXwA/xOMUFUMZB8sE4eUbAzGbBN
+Yd1APGJKSUYv7w3/eOUrp07y2wzts77dOxBmvWnJhGQguINFWJ2QTbPzpI9p7OoX
+Kt7PAIvrZM5VDo1CCIyVnNECgYEAgLmpZXlzcwicK3GZ2EfjhVvcoIlxsMLetf6b
+6PiON4lgrxqf88m7lqMezWhI+fgjHDTyvFSF89/1A/rcBaoazzSo4tka2VWEg8p3
+SHoMDOh8fJcdgD2AGGRa1TeAFX2HLJzajvfp72tbnpxbdZircah7eEK60PaQRIzR
+qi1+ZkECgYEAi7GkO7Ey98DppLnt7567veQoEj0u8ixTlCyJ4V278nHR5+6eAZP5
+PfdZVC3MtKGLnxrrPTVUy5wU0hR6Gk9EVDmrAF8TgJdnZFlBK7X23eWZ0q4qO/eO
+3xIi+UrNwLag8BjYOr32nP/i+F+TLikgRIFR4oiZjk867+ofkTXmNFA=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/helper/pipeline.inc b/tests/helper/pipeline.inc
new file mode 100644 (file)
index 0000000..815b463
--- /dev/null
@@ -0,0 +1,25 @@
+<?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);
+       }
+});
diff --git a/tests/helper/proxy.inc b/tests/helper/proxy.inc
new file mode 100644 (file)
index 0000000..80a0073
--- /dev/null
@@ -0,0 +1,23 @@
+<?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);
+});
diff --git a/tests/helper/server.inc b/tests/helper/server.inc
new file mode 100644 (file)
index 0000000..0605adc
--- /dev/null
@@ -0,0 +1,94 @@
+<?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
diff --git a/tests/info.phpt b/tests/info.phpt
deleted file mode 100644 (file)
index c1f58d9..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
---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
diff --git a/tests/info001.phpt b/tests/info001.phpt
new file mode 100644 (file)
index 0000000..8209669
--- /dev/null
@@ -0,0 +1,60 @@
+--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
diff --git a/tests/info002.phpt b/tests/info002.phpt
new file mode 100644 (file)
index 0000000..72690e4
--- /dev/null
@@ -0,0 +1,49 @@
+--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===
diff --git a/tests/info_001.phpt b/tests/info_001.phpt
deleted file mode 100644 (file)
index 370d70e..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
---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
index 230fd6b8a6deeb858867f9dbd2a1fa36977c4914..0214dfa4654578898901e7cf9d6fa31af4b3e50e 100644 (file)
@@ -184,7 +184,7 @@ array(10) {
   ["Accept-Ranges"]=>
   string(5) "bytes"
   ["Content-Length"]=>
-  int(0)
+  string(1) "0"
   ["Vary"]=>
   string(15) "Accept-Encoding"
   ["Connection"]=>
@@ -197,7 +197,6 @@ array(10) {
 GET /default/empty.txt HTTP/1.1
 Host: localhost
 Connection: close
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 09:55:09 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -205,7 +204,6 @@ Last-Modified: Wed, 28 Apr 2010 10:54:37 GMT
 Etag: "2002a-0-48549d615a35c"
 Accept-Ranges: bytes
 Vary: Accept-Encoding
-Content-Length: 0
 Connection: close
 Content-Type: text/plain
 X-Original-Content-Length: 20
@@ -214,7 +212,7 @@ string(3) "1.1"
 bool(true)
 int(200)
 string(2) "OK"
-array(11) {
+array(10) {
   ["Date"]=>
   string(29) "Thu, 26 Aug 2010 09:55:09 GMT"
   ["Server"]=>
@@ -227,8 +225,6 @@ array(11) {
   string(5) "bytes"
   ["Vary"]=>
   string(15) "Accept-Encoding"
-  ["Content-Length"]=>
-  int(0)
   ["Connection"]=>
   string(5) "close"
   ["Content-Type"]=>
@@ -242,7 +238,6 @@ GET /default/empty.txt HTTP/1.1
 Host: localhost
 Accept-Encoding: gzip
 Connection: close
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 11:41:02 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -277,7 +272,6 @@ array(8) {
 GET /default/empty.php HTTP/1.1
 Connection: close
 Host: localhost
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 12:51:28 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -311,7 +305,6 @@ array(7) {
 GET /cgi-bin/chunked.sh HTTP/1.1
 Host: localhost
 Connection: close
-Content-Length: 0
 ---
 HTTP/1.1 200 OK
 Date: Wed, 25 Aug 2010 12:11:44 GMT
@@ -340,7 +333,7 @@ array(10) {
   ["Accept-Ranges"]=>
   string(5) "bytes"
   ["Content-Length"]=>
-  int(0)
+  string(1) "0"
   ["Vary"]=>
   string(15) "Accept-Encoding"
   ["Connection"]=>
@@ -353,7 +346,6 @@ array(10) {
 GET /default/empty.txt HTTP/1.1
 Host: localhost
 Connection: close
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 09:55:09 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -361,7 +353,6 @@ Last-Modified: Wed, 28 Apr 2010 10:54:37 GMT
 Etag: "2002a-0-48549d615a35c"
 Accept-Ranges: bytes
 Vary: Accept-Encoding
-Content-Length: 0
 Connection: close
 Content-Type: text/plain
 X-Original-Content-Length: 20
@@ -370,7 +361,7 @@ string(3) "1.1"
 bool(true)
 int(200)
 string(2) "OK"
-array(11) {
+array(10) {
   ["Date"]=>
   string(29) "Thu, 26 Aug 2010 09:55:09 GMT"
   ["Server"]=>
@@ -383,8 +374,6 @@ array(11) {
   string(5) "bytes"
   ["Vary"]=>
   string(15) "Accept-Encoding"
-  ["Content-Length"]=>
-  int(0)
   ["Connection"]=>
   string(5) "close"
   ["Content-Type"]=>
@@ -398,7 +387,6 @@ GET /default/empty.txt HTTP/1.1
 Host: localhost
 Accept-Encoding: gzip
 Connection: close
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 11:41:02 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -433,7 +421,6 @@ array(8) {
 GET /default/empty.php HTTP/1.1
 Connection: close
 Host: localhost
-Content-Length: 0
 HTTP/1.1 200 OK
 Date: Thu, 26 Aug 2010 12:51:28 GMT
 Server: Apache/2.2.16 (Unix) mod_ssl/2.2.16 OpenSSL/1.0.0a mod_fastcgi/2.4.6
@@ -467,5 +454,4 @@ array(7) {
 GET /cgi-bin/chunked.sh HTTP/1.1
 Host: localhost
 Connection: close
-Content-Length: 0
 Done
diff --git a/tests/message016.phpt b/tests/message016.phpt
new file mode 100644 (file)
index 0000000..c29d8d6
--- /dev/null
@@ -0,0 +1,24 @@
+--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===
index f4ec2d90967a01e42350817d1a93f8cf776f63ce..d2d22a50aafe2c2d1eaa857f8eb59878c7bb3d93 100644 (file)
@@ -12,6 +12,7 @@ echo "Test\n";
 use http\Message\Parser;
 
 foreach (glob(__DIR__."/data/message_*.txt") as $file) {
+       $string = "";
        $parser = new Parser;
        $fd = fopen($file, "r") or die("Could not open $file");
        while (!feof($fd)) {
@@ -23,6 +24,11 @@ foreach (glob(__DIR__."/data/message_*.txt") as $file) {
                        throw new Exception(($e = error_get_last()) ? $e["message"] : "Could not parse $file");
                }
        }
+       
+       if (!$string) {
+               $s = ["START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "UPDATE_CL", "DONE"];
+               printf("Unexpected state: %s (%s)\n", $s[$parser->getState()], $file);
+       }
 
        $parser = new Parser;
        rewind($fd);
@@ -38,12 +44,14 @@ foreach (glob(__DIR__."/data/message_*.txt") as $file) {
        if ($string !== (string) $message) {
                $a = explode("\n", $string);
                $b = explode("\n", (string) $message);
-               while ((null !== ($aa = array_shift($a))) || (null !== ($bb = array_shift($b)))) {
+               do {
+                       $aa = array_shift($a);
+                       $bb = array_shift($b);
                        if ($aa !== $bb) {
                                isset($aa) and printf("-- %s\n", $aa);
                                isset($bb) and printf("++ %s\n", $bb);
                        }
-               }
+               } while ($a || $b);
        }
 }
 ?>
index c70476667c07365f8d0313900549d93549bff8cc..acac6cd82d915ebe5d447f9e572b0bd9e9df4018 100644 (file)
@@ -54,7 +54,7 @@ object(http\Message)#%d (9) {
     ["Host"]=>
     string(9) "localhost"
     ["Content-Length"]=>
-    int(3)
+    string(1) "3"
     ["X-Original-Content-Length"]=>
     string(1) "3"
   }
@@ -63,4 +63,4 @@ object(http\Message)#%d (9) {
 }
 string(3) "OK
 "
-DONE
\ No newline at end of file
+DONE
diff --git a/tests/phpinfo.phpt b/tests/phpinfo.phpt
new file mode 100644 (file)
index 0000000..c1f58d9
--- /dev/null
@@ -0,0 +1,19 @@
+--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
index 552a71318732377d67714b1e62c1f8f9cb327ffd..1001f8e5c3c981eccd6eb4a88a4d34091604fcc1 100644 (file)
@@ -4,6 +4,8 @@ property proxy
 <?php
 include "skipif.inc";
 ?>
+--XFAIL--
+TBD
 --FILE--
 <?php
 
diff --git a/tests/proxy.inc b/tests/proxy.inc
deleted file mode 100644 (file)
index 89d31f4..0000000
+++ /dev/null
@@ -1,26 +0,0 @@
-<?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
diff --git a/tests/proxy001.phpt b/tests/proxy001.phpt
deleted file mode 100644 (file)
index c8a2e63..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
---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===
diff --git a/tests/proxy002.phpt b/tests/proxy002.phpt
deleted file mode 100644 (file)
index 86bee61..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
---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===
index 4b2627a9c4436e0f720f6ccbb3e0503edc746911..cceeaf077e82820daeea9e84423986ac1524177a 100644 (file)
@@ -19,3 +19,12 @@ function skip_client_test($message = "skip need a client driver\n") {
                die($message);
        }
 }
+
+function skip_http2_test($message = "skip need http2 support (nghttpd in PATH)\n") {
+       foreach (explode(":", $_ENV["PATH"]) as $path) {
+               if (is_executable($path . "/nghttpd")) {
+                       return;
+               }
+       }
+       die($message);
+}
\ No newline at end of file
index 71f6943a396b0b42a18ef31f6ec93a878150ef44..ae63ae7b4050e4eba43b60049a565d9736e0342e 100644 (file)
@@ -1,19 +1,24 @@
 --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
@@ -27,7 +32,7 @@ object(http\Url)#%d (8) {
   ["pass"]=>
   string(12) "pa%C3%9Fwort"
   ["host"]=>
-  string(11) "sörver.net"
+  string(15) "𐌀𐌁𐌂.it"
   ["port"]=>
   NULL
   ["path"]=>
diff --git a/tests/urlparser011.phpt b/tests/urlparser011.phpt
new file mode 100644 (file)
index 0000000..5dbd82a
--- /dev/null
@@ -0,0 +1,40 @@
+--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