Merge branch 'master' of git.php.net:/pecl/http/pecl_http
authorRemi Collet <remi@php.net>
Thu, 19 Feb 2015 09:22:19 +0000 (10:22 +0100)
committerRemi Collet <remi@php.net>
Thu, 19 Feb 2015 09:22:19 +0000 (10:22 +0100)
* 'master' of git.php.net:/pecl/http/pecl_http: (84 commits)
  back to dev
  2.3.0RC1
  restore php-5.3 compatibility
  administrativa
  fix build with old libcurl
  remove dead code
  remove strlist api
  hoppala; removed actually living code
  remove dead code
  don't generate stat based etags for temp/mem streams
  test header parser errors and streaming
  test 4-byte sequences
  better error message
  (gcov) tests fixup
  better errors from the headers parser
  let the header parser fail more accurately
  add http\Header\Parser::stream()
  typo
  fix the stream message parser
  ignore disconnect exceptions
  ...

122 files changed:
.gitignore
TODO
config9.m4
gen_curlinfo.php
package.xml
php_http.c
php_http.h
php_http_api.h
php_http_buffer.c
php_http_buffer.h
php_http_client.c
php_http_client.h
php_http_client_curl.c
php_http_client_request.c
php_http_cookie.c
php_http_encoding.c
php_http_env.c
php_http_env_request.c
php_http_env_response.c
php_http_env_response.h
php_http_header.c
php_http_header_parser.c
php_http_header_parser.h
php_http_info.c
php_http_info.h
php_http_message.c
php_http_message_body.c
php_http_message_parser.c
php_http_message_parser.h
php_http_misc.c
php_http_misc.h
php_http_negotiate.c
php_http_negotiate.h
php_http_object.c
php_http_object.h
php_http_params.c
php_http_response_codes.h [new file with mode: 0644]
php_http_strlist.c [deleted file]
php_http_strlist.h [deleted file]
php_http_url.c
php_http_url.h
php_http_utf8.h
php_http_version.c
tests/bug66891.phpt
tests/bug69000.phpt [new file with mode: 0644]
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/client017.phpt [new file with mode: 0644]
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/envrequestcookie001.phpt [new file with mode: 0644]
tests/envresponse004.phpt
tests/envresponse006.phpt
tests/envresponse007.phpt
tests/envresponse008.phpt
tests/envresponse009.phpt
tests/envresponse010.phpt
tests/envresponse014.phpt
tests/envresponse015.phpt
tests/envresponse017.phpt [new file with mode: 0644]
tests/envresponse018.phpt [new file with mode: 0644]
tests/envresponsecookie001.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/message002.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/skipif.inc
tests/url001.phpt
tests/urlparser001.phpt
tests/urlparser002.phpt
tests/urlparser003.phpt
tests/urlparser004.phpt
tests/urlparser005.phpt
tests/urlparser006.phpt
tests/urlparser007.phpt
tests/urlparser008.phpt
tests/urlparser009.phpt
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
+*~
diff --git a/TODO b/TODO
index 3981099147240132e94898d0351ed67405ad4da8..33b759f2d5c26778d012dcb06c727c837ff7116d 100644 (file)
--- a/TODO
+++ b/TODO
@@ -1,4 +1,3 @@
-* let http_info.request.url be a php_url
 * let the message body be a simple query string unless files are added
 * php_http_message_serialize reverses the chain twice; remove that
 * CURLOPT_PROXY_HEADER and CURLOPT_HEADEROPT
\ No newline at end of file
index 3b1f7015554d1683469836e9548efd85b2eb0a08..af3d55fd25531df5b77f6a1f8ecc07cf759f06d8 100644 (file)
@@ -202,6 +202,14 @@ dnl ----
                        if test `echo $CURL_VERSION | $SED -e 's/[[^0-9]]/ /g' | $AWK '{print $1*10000 + $2*100 + $3}'` -lt 71802; then
                                AC_MSG_ERROR([libcurl version greater or equal to 7.18.2 required])
                        fi
+                       
+                       AC_MSG_CHECKING([for HTTP2 support in libcurl])
+                       if $CURL_CONFIG --features | $EGREP -q HTTP2; then
+                               AC_MSG_RESULT([yes])
+                               AC_DEFINE([PHP_HTTP_HAVE_HTTP2], [1], [ ])
+                       else
+                               AC_MSG_RESULT([no])
+                       fi
                
                        dnl
                        dnl compile tests
@@ -210,11 +218,11 @@ dnl ----
                        save_INCLUDES="$INCLUDES"
                        INCLUDES=
                        save_LIBS="$LIBS"
-                       LIBS=
+                       LIBS=-lcurl
                        save_CFLAGS="$CFLAGS"
                        CFLAGS="$CFLAGS `$CURL_CONFIG --cflags`"
                        save_LDFLAGS="$LDFLAGS"
-                       LDFLAGS="$LDFLAGS `$CURL_CONFIG --libs` $ld_runpath_switch$CURL_DIR/$PHP_LIBDIR"
+                       LDFLAGS="$ld_runpath_switch$CURL_DIR/$PHP_LIBDIR"
                
                        AC_MSG_CHECKING([for SSL support in libcurl])
                        CURL_SSL=`$CURL_CONFIG --feature | $EGREP SSL`
@@ -296,6 +304,37 @@ dnl ----
                        ], [
                                AC_MSG_RESULT([no])
                        ])
+                       
+                       AC_MSG_CHECKING([whether CURLOPT_TLSAUTH_TYPE expects CURL_TLSAUTH_SRP or literal "SRP"])
+                       AC_TRY_RUN([
+                               #include <curl/curl.h>
+                               int main(int argc, char *argv[]) {
+                                       CURL *ch = curl_easy_init();
+                                       return curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, CURL_TLSAUTH_SRP);
+                               }
+                       ], [
+                               AC_MSG_RESULT([CURL_TLSAUTH_SRP])
+                               AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_SRP], [CURL_TLSAUTH_SRP], [ ])
+                               AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_DEF], [CURL_TLSAUTH_NONE], [ ])
+                       ], [
+                               AC_TRY_RUN([
+                                       #include <curl/curl.h>
+                                       int main(int argc, char *argv[]) {
+                                               CURL *ch = curl_easy_init();
+                                               return curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, "SRP");
+                                       }
+                               ], [
+                                       AC_MSG_RESULT(["SRP"])
+                                       AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_SRP], ["SRP"], [ ])
+                                       AC_DEFINE([PHP_HTTP_CURL_TLSAUTH_DEF], [""], [ ])
+                               ], [
+                                       AC_MSG_RESULT([neither])
+                               ], [
+                                       AC_MSG_RESULT([neither])
+                               ])                      
+                       ], [
+                               AC_MSG_RESULT([neither])
+                       ])
                
                        INCLUDES="$save_INCLUDES"
                        LIBS="$save_LIBS"
@@ -508,7 +547,6 @@ dnl ----
                php_http_options.c \
                php_http_params.c \
                php_http_querystring.c \
-               php_http_strlist.c \
                php_http_url.c \
                php_http_version.c \
        "
@@ -560,8 +598,9 @@ dnl ----
                php_http_options.h \
                php_http_params.h \
                php_http_querystring.h \
-               php_http_strlist.h \
+               php_http_response_codes.h \
                php_http_url.h \
+               php_http_utf8.h \
                php_http_version.h \
        "
        PHP_INSTALL_HEADERS(ext/http, $PHP_HTTP_HEADERS)
index 0e8dfa1272da5553a3c6267e0c594dee6d991767..076a10d10f86bd34e1a354ea5a34b028bb9dc6ed 100755 (executable)
@@ -41,7 +41,8 @@ $ifdefs = array(
 );
 $exclude = array(
     'PRIVATE', 'LASTSOCKET', 'FTP_ENTRY_PATH', 'CERTINFO', 'TLS_SESSION',
-    'RTSP_SESSION_ID', 'RTSP_CLIENT_CSEQ', 'RTSP_SERVER_CSEQ', 'RTSP_CSEQ_RECV'
+    'RTSP_SESSION_ID', 'RTSP_CLIENT_CSEQ', 'RTSP_SERVER_CSEQ', 'RTSP_CSEQ_RECV',
+       'COOKIELIST'
 );
 
 $translate = array(
index c02cad139fb2e098ac73917d020602004954bc18..187f86e1ae7ff476a3c420b4d4e64d49f40e2d27 100644 (file)
@@ -37,22 +37,58 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/
   <email>mike@php.net</email>
   <active>yes</active>
  </lead>
- <date>2014-08-19</date>
+ <date>2015-02-19</date>
  <version>
-  <release>2.2.0dev</release>
-  <api>2.2.0</api>
+  <release>2.3.0dev</release>
+  <api>2.3.0</api>
  </version>
  <stability>
   <release>beta</release>
-  <api>stable</api>
+  <api>beta</api>
  </stability>
  <license>BSD, revised</license>
  <notes><![CDATA[
-- var_dump(http\Message) no longer automatically creates an empty body
-+ Added http\Message\Parser class
-+ Made http\Client::once() and http\Client::wait() available when using events
-+ Added http\Url::parse() method
-+ Added http\Url::PARSE_MBLOC, http\Url::PARSE_MBUTF8, http\Url::PARSE_TOIDN and http\Url::PARSE_TOPCT constants
++ 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="/">
@@ -120,8 +156,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/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"/>
@@ -130,17 +165,31 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/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"/>
@@ -157,6 +206,16 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/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"/>
@@ -188,6 +247,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/http/
      <file role="test" name="envrequestbody001.phpt"/>
      <file role="test" name="envrequestbody002.phpt"/>
      <file role="test" name="envrequestbody003.phpt"/>
+     <file role="test" name="envrequestcookie001.phpt"/>
      <file role="test" name="envrequestfiles001.phpt"/>
      <file role="test" name="envrequestfiles002.phpt"/>
      <file role="test" name="envrequestform.phpt"/>
@@ -211,9 +271,12 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/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"/>
+     <file role="test" name="envresponsecookie001.phpt"/>
      <file role="test" name="envresponseheader001.phpt"/>
      <file role="test" name="envresponseranges001.phpt"/>
      <file role="test" name="etag001.phpt"/>
@@ -228,8 +291,11 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/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"/>
@@ -245,6 +311,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/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"/>
@@ -273,6 +340,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/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"/>
@@ -292,6 +360,7 @@ v2: http://dev.iworks.at/ext-http/lcov/ext/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 f7a0b8698f03012f2d8460532fcdb37504ea5649..17d9925b5af8d3b01409b613aeb2dca69f76545b 100644 (file)
@@ -141,6 +141,7 @@ PHP_MINIT_FUNCTION(http)
        || SUCCESS != PHP_MINIT_CALL(http_encoding)
        || SUCCESS != PHP_MINIT_CALL(http_filter)
        || SUCCESS != PHP_MINIT_CALL(http_header)
+       || SUCCESS != PHP_MINIT_CALL(http_header_parser)
        || SUCCESS != PHP_MINIT_CALL(http_message)
        || SUCCESS != PHP_MINIT_CALL(http_message_parser)
        || SUCCESS != PHP_MINIT_CALL(http_message_body)
index 98332cb68fb7fdfcb5824ca784d72e5e5e50311a..5a0575d269bb8e799783974f4d961aeeb0c1a65d 100644 (file)
@@ -13,7 +13,7 @@
 #ifndef PHP_EXT_HTTP_H
 #define PHP_EXT_HTTP_H
 
-#define PHP_PECL_HTTP_VERSION "2.2.0dev"
+#define PHP_PECL_HTTP_VERSION "2.3.0dev"
 
 extern zend_module_entry http_module_entry;
 #define phpext_http_ptr &http_module_entry
index 5bddb0c42e670a69fe0bdc6049614b97953354f5..712c8cb1aedc3f53ed52984db9fa6ef888f1083e 100644 (file)
@@ -79,7 +79,6 @@ typedef int STATUS;
 #include "php_http.h"
 
 #include "php_http_buffer.h"
-#include "php_http_strlist.h"
 #include "php_http_misc.h"
 #include "php_http_options.h"
 
index 8ccee0847584b0e4f678596105ccfee8a5915251..e24a4e18baa2e4d7e7a6c8cbdbf7d7c7967fc31b 100644 (file)
@@ -45,7 +45,7 @@ PHP_HTTP_BUFFER_API size_t php_http_buffer_resize_ex(php_http_buffer_t *buf, siz
 {
        char *ptr = NULL;
 #if 0
-       fprintf(stderr, "RESIZE: len=%lu, size=%lu, used=%lu, free=%lu\n", len, buf->size, buf->used, buf->free);
+       fprintf(stderr, "RESIZE: len=%lu, size=%lu, used=%lu, free=%lu, total=%lu\n", len, buf->size, buf->used, buf->free, buf->free+buf->used);
 #endif
        if (buf->free < len) {
                size_t size = override_size ? override_size : buf->size;
@@ -74,10 +74,7 @@ PHP_HTTP_BUFFER_API size_t php_http_buffer_resize_ex(php_http_buffer_t *buf, siz
 
 PHP_HTTP_BUFFER_API char *php_http_buffer_account(php_http_buffer_t *buf, size_t to_account)
 {
-       /* it's probably already too late but check anyway */
-       if (to_account > buf->free) {
-               return NULL;
-       }
+       assert(to_account <= buf->free);
 
        buf->free -= to_account;
        buf->used += to_account;
@@ -229,7 +226,7 @@ PHP_HTTP_BUFFER_API size_t php_http_buffer_chunked_output(php_http_buffer_t **s,
 
        while ((got = php_http_buffer_chunk_buffer(s, data, data_len, &chunk, chunk_len))) {
                if (PHP_HTTP_BUFFER_PASS0 == passout(opaque, chunk, got TSRMLS_CC)) {
-                       STR_SET(chunk, NULL);
+                       PTR_SET(chunk, NULL);
                        return PHP_HTTP_BUFFER_PASS0;
                }
                ++passed;
@@ -240,9 +237,9 @@ PHP_HTTP_BUFFER_API size_t php_http_buffer_chunked_output(php_http_buffer_t **s,
                }
                data = NULL;
                data_len = 0;
-               STR_SET(chunk, NULL);
+               PTR_SET(chunk, NULL);
        }
-       STR_FREE(chunk);
+       PTR_FREE(chunk);
        return passed;
 }
 
index cf9b45863b7af0f2752680be22a9c21b51257bf3..faf8992107675be7b39f744fc0ae069f9326a410 100644 (file)
 #define PHP_HTTP_BUFFER_NOMEM PHP_HTTP_BUFFER_ERROR
 #define PHP_HTTP_BUFFER_PASS0 PHP_HTTP_BUFFER_ERROR
 
-#ifndef STR_FREE
-#      define STR_FREE(STR) \
+#ifndef PTR_FREE
+#      define PTR_FREE(PTR) \
        { \
-               if (STR) { \
-                       efree(STR); \
+               if (PTR) { \
+                       efree(PTR); \
                } \
        }
 #endif
-#ifndef STR_SET
-#      define STR_SET(STR, SET) \
+#ifndef PTR_SET
+#      define PTR_SET(PTR, SET) \
        { \
-               STR_FREE(STR); \
-               STR = SET; \
+               PTR_FREE(PTR); \
+               PTR = SET; \
        }
 #endif
 #ifndef TSRMLS_D
index f96164bbba08ad449733b745503149948412de03..4f0de3cb6fc604334470937c0881b4efea3e56a2 100644 (file)
@@ -329,6 +329,8 @@ void php_http_client_object_free(void *object TSRMLS_DC)
        php_http_client_object_t *o = (php_http_client_object_t *) object;
 
        php_http_client_free(&o->client);
+       php_http_object_method_dtor(&o->notify);
+       php_http_object_method_free(&o->update);
        zend_object_std_dtor((zend_object *) o TSRMLS_CC);
        efree(o);
 }
@@ -375,7 +377,7 @@ static void handle_history(zval *zclient, php_http_message_t *request, php_http_
        zval_ptr_dtor(&new_hist);
 }
 
-static STATUS handle_response(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_message_t **request, php_http_message_t **response)
+static STATUS handle_response(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_message_t **response)
 {
        zend_bool dequeue = 0;
        zval zclient;
@@ -395,7 +397,7 @@ static STATUS handle_response(void *arg, php_http_client_t *client, php_http_cli
                php_http_message_set_type(msg, PHP_HTTP_RESPONSE);
 
                if (z_is_true(zend_read_property(php_http_client_class_entry, &zclient, ZEND_STRL("recordHistory"), 0 TSRMLS_CC))) {
-                       handle_history(&zclient, *request, *response TSRMLS_CC);
+                       handle_history(&zclient, e->request, *response TSRMLS_CC);
                }
 
                /* hard detach, redirects etc. are in the history */
@@ -457,14 +459,18 @@ static STATUS handle_response(void *arg, php_http_client_t *client, php_http_cli
 
 static void handle_progress(void *arg, php_http_client_t *client, php_http_client_enqueue_t *e, php_http_client_progress_state_t *progress)
 {
-       zval *zrequest, *zprogress, *retval = NULL, *zclient;
+       zval *zrequest, *zprogress, *zclient, **args[2];
+       php_http_client_object_t *client_obj = arg;
        zend_error_handling zeh;
        TSRMLS_FETCH_FROM_CTX(client->ts);
 
        MAKE_STD_ZVAL(zclient);
-       ZVAL_OBJVAL(zclient, ((php_http_client_object_t *) arg)->zv, 1);
+       ZVAL_OBJVAL(zclient, client_obj->zv, 1);
+
        MAKE_STD_ZVAL(zrequest);
        ZVAL_OBJVAL(zrequest, ((php_http_message_object_t *) e->opaque)->zv, 1);
+       args[0] = &zrequest;
+
        MAKE_STD_ZVAL(zprogress);
        object_init(zprogress);
        add_property_bool(zprogress, "started", progress->started);
@@ -474,15 +480,15 @@ static void handle_progress(void *arg, php_http_client_t *client, php_http_clien
        add_property_double(zprogress, "dlnow", progress->dl.now);
        add_property_double(zprogress, "ultotal", progress->ul.total);
        add_property_double(zprogress, "ulnow", progress->ul.now);
+       args[1] = &zprogress;
+
        zend_replace_error_handling(EH_NORMAL, NULL, &zeh TSRMLS_CC);
-       zend_call_method_with_2_params(&zclient, NULL, NULL, "notify", &retval, zrequest, zprogress);
+       php_http_object_method_call(&client_obj->notify, zclient, NULL, 2, args TSRMLS_CC);
        zend_restore_error_handling(&zeh TSRMLS_CC);
+
        zval_ptr_dtor(&zclient);
        zval_ptr_dtor(&zrequest);
        zval_ptr_dtor(&zprogress);
-       if (retval) {
-               zval_ptr_dtor(&retval);
-       }
 }
 
 static void response_dtor(void *data)
@@ -537,6 +543,8 @@ static PHP_METHOD(HttpClient, __construct)
 
                php_http_expect(obj->client = php_http_client_init(NULL, driver.client_ops, rf, NULL TSRMLS_CC), runtime, return);
 
+               php_http_object_method_init(&obj->notify, getThis(), ZEND_STRL("notify") TSRMLS_CC);
+
                obj->client->callback.response.func = handle_response;
                obj->client->callback.response.arg = obj;
                obj->client->callback.progress.func = handle_progress;
@@ -835,6 +843,22 @@ static PHP_METHOD(HttpClient, wait)
        }
 }
 
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_configure, 0, 0, 1)
+       ZEND_ARG_ARRAY_INFO(0, settings, 1)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpClient, configure)
+{
+       HashTable *settings = NULL;
+       php_http_client_object_t *obj;
+
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|H!", &settings), invalid_arg, return);
+       obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       php_http_expect(SUCCESS == php_http_client_setopt(obj->client, PHP_HTTP_CLIENT_OPT_CONFIGURATION, settings), unexpected_val, return);
+
+       RETVAL_ZVAL(getThis(), 1, 0);
+}
+
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_enablePipelining, 0, 0, 0)
        ZEND_ARG_INFO(0, enable)
 ZEND_END_ARG_INFO();
@@ -869,13 +893,20 @@ static PHP_METHOD(HttpClient, enableEvents)
        RETVAL_ZVAL(getThis(), 1, 0);
 }
 
+struct notify_arg {
+       php_http_object_method_t *cb;
+       zval **args[3];
+       int argc;
+};
+
 static int notify(zend_object_iterator *iter, void *puser TSRMLS_DC)
 {
-       zval **observer = NULL, ***args = puser;
+       zval **observer = NULL;
+       struct notify_arg *arg = puser;
 
        iter->funcs->get_current_data(iter, &observer TSRMLS_CC);
        if (observer) {
-               return php_http_method_call(*observer, ZEND_STRL("update"), args[2]?3:args[1]?2:args[0]?1:0, args, NULL TSRMLS_CC);
+               return php_http_object_method_call(arg->cb, *observer, NULL, arg->argc, arg->args TSRMLS_CC);
        }
        return FAILURE;
 }
@@ -885,10 +916,13 @@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_notify, 0, 0, 0)
 ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpClient, notify)
 {
-       zval *request = NULL, *zprogress = NULL, *observers, **args[3];
+       zval *request = NULL, *zprogress = NULL, *observers;
+       php_http_client_object_t *client_obj;
+       struct notify_arg arg = {NULL};
 
        php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|O!o!", &request, php_http_client_request_class_entry, &zprogress), invalid_arg, return);
 
+       client_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
        observers = zend_read_property(php_http_client_class_entry, getThis(), ZEND_STRL("observers"), 0 TSRMLS_CC);
 
        if (Z_TYPE_P(observers) != IS_OBJECT) {
@@ -896,23 +930,34 @@ static PHP_METHOD(HttpClient, notify)
                return;
        }
 
-       Z_ADDREF_P(getThis());
-       args[0] = &getThis();
-       if (request) {
-               Z_ADDREF_P(request);
-       }
-       args[1] = &request;
-       if (zprogress) {
-               Z_ADDREF_P(zprogress);
-       }
-       args[2] = &zprogress;
-       spl_iterator_apply(observers, notify, args TSRMLS_CC);
-       zval_ptr_dtor(&getThis());
-       if (request) {
-               zval_ptr_dtor(&request);
-       }
-       if (zprogress) {
-               zval_ptr_dtor(&zprogress);
+       if (client_obj->update) {
+               arg.cb = client_obj->update;
+
+               Z_ADDREF_P(getThis());
+               arg.args[0] = &getThis();
+               arg.argc = 1;
+
+               if (request) {
+                       Z_ADDREF_P(request);
+                       arg.args[1] = &request;
+                       arg.argc += 1;
+               }
+
+               if (zprogress) {
+                       Z_ADDREF_P(zprogress);
+                       arg.args[2] = &zprogress;
+                       arg.argc += 1;
+               }
+
+               spl_iterator_apply(observers, notify, &arg TSRMLS_CC);
+
+               zval_ptr_dtor(&getThis());
+               if (request) {
+                       zval_ptr_dtor(&request);
+               }
+               if (zprogress) {
+                       zval_ptr_dtor(&zprogress);
+               }
        }
 
        RETVAL_ZVAL(getThis(), 1, 0);
@@ -924,9 +969,11 @@ ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpClient, attach)
 {
        zval *observers, *observer, *retval = NULL;
+       php_http_client_object_t *client_obj;
 
        php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &observer, spl_ce_SplObserver), invalid_arg, return);
 
+       client_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
        observers = zend_read_property(php_http_client_class_entry, getThis(), ZEND_STRL("observers"), 0 TSRMLS_CC);
 
        if (Z_TYPE_P(observers) != IS_OBJECT) {
@@ -934,6 +981,10 @@ static PHP_METHOD(HttpClient, attach)
                return;
        }
 
+       if (!client_obj->update) {
+               client_obj->update = php_http_object_method_init(NULL, observer, ZEND_STRL("update") TSRMLS_CC);
+       }
+
        zend_call_method_with_1_params(&observers, NULL, NULL, "attach", &retval, observer);
        if (retval) {
                zval_ptr_dtor(&retval);
@@ -1138,6 +1189,30 @@ static PHP_METHOD(HttpClient, getAvailableDrivers) {
        }
 }
 
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_getAvailableOptions, 0, 0, 0)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpClient, getAvailableOptions)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_client_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_init(return_value);
+               php_http_client_getopt(obj->client, PHP_HTTP_CLIENT_OPT_AVAILABLE_OPTIONS, NULL, &Z_ARRVAL_P(return_value));
+       }
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpClient_getAvailableConfiguration, 0, 0, 0)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpClient, getAvailableConfiguration)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_client_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_init(return_value);
+               php_http_client_getopt(obj->client, PHP_HTTP_CLIENT_OPT_AVAILABLE_CONFIGURATION, NULL, &Z_ARRVAL_P(return_value));
+       }
+}
+
 static zend_function_entry php_http_client_methods[] = {
        PHP_ME(HttpClient, __construct,          ai_HttpClient_construct,            ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
        PHP_ME(HttpClient, reset,                ai_HttpClient_reset,                ZEND_ACC_PUBLIC)
@@ -1150,8 +1225,9 @@ static zend_function_entry php_http_client_methods[] = {
        PHP_ME(HttpClient, wait,                 ai_HttpClient_wait,                 ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, getResponse,          ai_HttpClient_getResponse,          ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, getHistory,           ai_HttpClient_getHistory,           ZEND_ACC_PUBLIC)
-       PHP_ME(HttpClient, enablePipelining,     ai_HttpClient_enablePipelining,     ZEND_ACC_PUBLIC)
-       PHP_ME(HttpClient, enableEvents,         ai_HttpClient_enableEvents,         ZEND_ACC_PUBLIC)
+       PHP_ME(HttpClient, configure,            ai_HttpClient_configure,            ZEND_ACC_PUBLIC)
+       PHP_ME(HttpClient, enablePipelining,     ai_HttpClient_enablePipelining,     ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
+       PHP_ME(HttpClient, enableEvents,         ai_HttpClient_enableEvents,         ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
        PHP_ME(HttpClient, notify,               ai_HttpClient_notify,               ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, attach,               ai_HttpClient_attach,               ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, detach,               ai_HttpClient_detach,               ZEND_ACC_PUBLIC)
@@ -1167,6 +1243,8 @@ static zend_function_entry php_http_client_methods[] = {
        PHP_ME(HttpClient, addCookies,           ai_HttpClient_addCookies,           ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, getCookies,           ai_HttpClient_getCookies,           ZEND_ACC_PUBLIC)
        PHP_ME(HttpClient, getAvailableDrivers,  ai_HttpClient_getAvailableDrivers,  ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+       PHP_ME(HttpClient, getAvailableOptions,  ai_HttpClient_getAvailableOptions,  ZEND_ACC_PUBLIC)
+       PHP_ME(HttpClient, getAvailableConfiguration, ai_HttpClient_getAvailableConfiguration, ZEND_ACC_PUBLIC)
        EMPTY_FUNCTION_ENTRY
 };
 
index 6c455166e6233c877862f769a27ece04865608de..4008e874f8ae095e24f842d2b785466e28616de3 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 {
@@ -84,7 +87,7 @@ typedef struct php_http_client_progress_state {
        unsigned finished:1;
 } php_http_client_progress_state_t;
 
-typedef STATUS (*php_http_client_response_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_message_t **request, php_http_message_t **response);
+typedef STATUS (*php_http_client_response_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_message_t **response);
 typedef void (*php_http_client_progress_callback_t)(void *arg, struct php_http_client *client, php_http_client_enqueue_t *e, php_http_client_progress_state_t *state);
 
 typedef struct php_http_client {
@@ -118,6 +121,8 @@ typedef struct php_http_client_object {
        zend_object_value zv;
        php_http_client_t *client;
        long iterator;
+       php_http_object_method_t *update;
+       php_http_object_method_t notify;
 } php_http_client_object_t;
 
 PHP_HTTP_API php_http_client_t *php_http_client_init(php_http_client_t *h, php_http_client_ops_t *ops, php_resource_factory_t *rf, void *init_arg TSRMLS_DC);
index 48bd0deb02789810778ef25840fc73aa1d6dcd8f..44b2c21a9cc819a842e50cabcc23d6dbbb13c558 100644 (file)
@@ -60,24 +60,17 @@ typedef struct php_http_client_curl_handler {
        php_resource_factory_t *rf;
        php_http_client_t *client;
        php_http_client_progress_state_t progress;
-
        php_http_client_enqueue_t queue;
 
        struct {
-               php_http_message_parser_t *parser;
-               php_http_message_t *message;
-               php_http_buffer_t *buffer;
-       } request;
-
-       struct {
-               php_http_message_parser_t *parser;
-               php_http_message_t *message;
-               php_http_buffer_t *buffer;
+               php_http_buffer_t headers;
+               php_http_message_body_t *body;
        } response;
 
        struct {
                HashTable cache;
 
+               struct curl_slist *proxyheaders;
                struct curl_slist *headers;
                struct curl_slist *resolve;
                php_http_buffer_t cookies;
@@ -225,29 +218,23 @@ static int php_http_curle_progress_callback(void *ctx, double dltotal, double dl
        return 0;
 }
 
-static curlioerr php_http_curle_ioctl_callback(CURL *ch, curliocmd cmd, void *ctx)
+static int php_http_curle_seek_callback(void *userdata, curl_off_t offset, int origin)
 {
-       php_http_message_body_t *body = ctx;
+       php_http_message_body_t *body = userdata;
+       TSRMLS_FETCH_FROM_CTX(body->ts);
 
-       if (cmd != CURLIOCMD_RESTARTREAD) {
-               return CURLIOE_UNKNOWNCMD;
+       if (!body) {
+               return 1;
        }
-
-       if (body) {
-               TSRMLS_FETCH_FROM_CTX(body->ts);
-
-               if (SUCCESS == php_stream_rewind(php_http_message_body_stream(body))) {
-                       return CURLIOE_OK;
-               }
+       if (0 == php_stream_seek(php_http_message_body_stream(body), offset, origin)) {
+               return 0;
        }
-
-       return CURLIOE_FAILRESTART;
+       return 2;
 }
 
 static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data, size_t length, void *ctx)
 {
        php_http_client_curl_handler_t *h = ctx;
-       unsigned flags = 0;
 
        /* catch progress */
        switch (type) {
@@ -304,32 +291,6 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data,
                default:
                        break;
        }
-       /* process data */
-       switch (type) {
-               case CURLINFO_HEADER_IN:
-               case CURLINFO_DATA_IN:
-                       php_http_buffer_append(h->response.buffer, data, length);
-
-                       if (h->options.redirects) {
-                               flags |= PHP_HTTP_MESSAGE_PARSER_EMPTY_REDIRECTS;
-                       }
-
-                       if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->response.parser, h->response.buffer, flags, &h->response.message)) {
-                               return -1;
-                       }
-                       break;
-
-               case CURLINFO_HEADER_OUT:
-               case CURLINFO_DATA_OUT:
-                       php_http_buffer_append(h->request.buffer, data, length);
-
-                       if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(h->request.parser, h->request.buffer, flags, &h->request.message)) {
-                               return -1;
-                       }
-                       break;
-               default:
-                       break;
-       }
 
 #if 0
        /* debug */
@@ -339,9 +300,18 @@ static int php_http_curle_raw_callback(CURL *ch, curl_infotype type, char *data,
        return 0;
 }
 
-static int php_http_curle_dummy_callback(char *data, size_t n, size_t l, void *s)
+static int php_http_curle_header_callback(char *data, size_t n, size_t l, void *arg)
+{
+       php_http_client_curl_handler_t *h = arg;
+
+       return php_http_buffer_append(&h->response.headers, data, n * l);
+}
+
+static int php_http_curle_body_callback(char *data, size_t n, size_t l, void *arg)
 {
-       return n*l;
+       php_http_client_curl_handler_t *h = arg;
+
+       return php_http_message_body_append(h->response.body, data, n*l);
 }
 
 static STATUS php_http_curle_get_info(CURL *ch, HashTable *info)
@@ -440,17 +410,6 @@ static STATUS php_http_curle_get_info(CURL *ch, HashTable *info)
                add_assoc_zval_ex(&array, "ssl_engines", sizeof("ssl_engines"), subarray);
                curl_slist_free_all(s);
        }
-       if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_COOKIELIST, &s)) {
-               MAKE_STD_ZVAL(subarray);
-               array_init(subarray);
-               for (p = s; p; p = p->next) {
-                       if (p->data) {
-                               add_next_index_string(subarray, p->data, 1);
-                       }
-               }
-               add_assoc_zval_ex(&array, "cookies", sizeof("cookies"), subarray);
-               curl_slist_free_all(s);
-       }
        if (CURLE_OK == curl_easy_getinfo(ch, CURLINFO_REDIRECT_URL, &c)) {
                add_assoc_string_ex(&array, "redirect_url", sizeof("redirect_url"), c ? c : "", 1);
        }
@@ -617,6 +576,50 @@ static int compare_queue(php_http_client_enqueue_t *e, void *handle)
        return handle == ((php_http_client_curl_handler_t *) e->opaque)->handle;
 }
 
+static php_http_message_t *php_http_curlm_responseparser(php_http_client_curl_handler_t *h TSRMLS_DC)
+{
+       php_http_message_t *response;
+       php_http_header_parser_t parser;
+       zval *zh;
+
+       response = php_http_message_init(NULL, 0, h->response.body TSRMLS_CC);
+       php_http_header_parser_init(&parser TSRMLS_CC);
+       php_http_header_parser_parse(&parser, &h->response.headers, PHP_HTTP_HEADER_PARSER_CLEANUP, &response->hdrs, (php_http_info_callback_t) php_http_message_info_callback, (void *) &response);
+       php_http_header_parser_dtor(&parser);
+
+       /* move body to right message */
+       if (response->body != h->response.body) {
+               php_http_message_t *ptr = response;
+
+               while (ptr->parent) {
+                       ptr = ptr->parent;
+               }
+               response->body = ptr->body;
+               ptr->body = NULL;
+       }
+       php_http_message_body_addref(h->response.body);
+
+       /* let's update the response headers */
+       if ((zh = php_http_message_header(response, ZEND_STRL("Content-Length"), 1))) {
+               zend_hash_update(&response->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), &zh, sizeof(zval *), NULL);
+       }
+       if ((zh = php_http_message_header(response, ZEND_STRL("Transfer-Encoding"), 0))) {
+               zend_hash_update(&response->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &zh, sizeof(zval *), NULL);
+               zend_hash_del(&response->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding"));
+       }
+       if ((zh = php_http_message_header(response, ZEND_STRL("Content-Range"), 0))) {
+               zend_hash_update(&response->hdrs, "X-Original-Content-Range", sizeof("X-Original-Content-Range"), &zh, sizeof(zval *), NULL);
+               zend_hash_del(&response->hdrs, "Content-Range", sizeof("Content-Range"));
+       }
+       if ((zh = php_http_message_header(response, ZEND_STRL("Content-Encoding"), 0))) {
+               zend_hash_update(&response->hdrs, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), &zh, sizeof(zval *), NULL);
+               zend_hash_del(&response->hdrs, "Content-Encoding", sizeof("Content-Encoding"));
+       }
+       php_http_message_update_headers(response);
+
+       return response;
+}
+
 static void php_http_curlm_responsehandler(php_http_client_t *context)
 {
        int remaining = 0;
@@ -635,8 +638,12 @@ static void php_http_curlm_responsehandler(php_http_client_t *context)
 
                        if ((enqueue = php_http_client_enqueued(context, msg->easy_handle, compare_queue))) {
                                php_http_client_curl_handler_t *handler = enqueue->opaque;
+                               php_http_message_t *response = php_http_curlm_responseparser(handler TSRMLS_CC);
 
-                               context->callback.response.func(context->callback.response.arg, context, &handler->queue, &handler->request.message, &handler->response.message);
+                               if (response) {
+                                       context->callback.response.func(context->callback.response.arg, context, &handler->queue, &response);
+                                       php_http_message_free(&response);
+                               }
                        }
                }
        } while (remaining);
@@ -799,7 +806,7 @@ static void php_http_curlm_timer_callback(CURLM *multi, long timeout_ms, void *t
 
 /* curl options */
 
-static php_http_options_t php_http_curle_options;
+static php_http_options_t php_http_curle_options, php_http_curlm_options;
 
 #define PHP_HTTP_CURLE_OPTION_CHECK_STRLEN             0x0001
 #define PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR            0x0002
@@ -925,9 +932,13 @@ static STATUS php_http_curle_option_set_lastmodified(php_http_option_t *opt, zva
 static STATUS php_http_curle_option_set_compress(php_http_option_t *opt, zval *val, void *userdata)
 {
        php_http_client_curl_handler_t *curl = userdata;
+       CURL *ch = curl->handle;
 
-       if (Z_BVAL_P(val)) {
-               curl->options.headers = curl_slist_append(curl->options.headers, "Accept-Encoding: gzip;q=1.0,deflate;q=0.5");
+#if !PHP_HTTP_CURL_VERSION(7,21,6)
+#      define CURLOPT_ACCEPT_ENCODING CURLOPT_ENCODING
+#endif
+       if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_ACCEPT_ENCODING, Z_BVAL_P(val) ? "" : NULL)) {
+               return FAILURE;
        }
        return SUCCESS;
 }
@@ -1073,6 +1084,43 @@ static STATUS php_http_curle_option_set_portrange(php_http_option_t *opt, zval *
        return SUCCESS;
 }
 
+#if PHP_HTTP_CURL_VERSION(7,37,0)
+static STATUS php_http_curle_option_set_proxyheader(php_http_option_t *opt, zval *val, void *userdata)
+{
+       php_http_client_curl_handler_t *curl = userdata;
+       TSRMLS_FETCH_FROM_CTX(curl->client->ts);
+
+       if (val && Z_TYPE_P(val) != IS_NULL) {
+               php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0);
+               zval **header_val, *header_cpy;
+               HashPosition pos;
+               php_http_buffer_t header;
+
+               php_http_buffer_init(&header);
+               FOREACH_KEYVAL(pos, val, header_key, header_val) {
+                       if (header_key.type == HASH_KEY_IS_STRING) {
+                               header_cpy = php_http_ztyp(IS_STRING, *header_val);
+                               php_http_buffer_appendf(&header, "%s: %s", header_key.str, Z_STRVAL_P(header_cpy));
+                               php_http_buffer_fix(&header);
+                               curl->options.proxyheaders = curl_slist_append(curl->options.proxyheaders, header.data);
+                               php_http_buffer_reset(&header);
+
+                               zval_ptr_dtor(&header_cpy);
+                       }
+               }
+               php_http_buffer_dtor(&header);
+       }
+       if (CURLE_OK != curl_easy_setopt(curl->handle, CURLOPT_PROXYHEADER, curl->options.proxyheaders)) {
+               return FAILURE;
+       }
+       if (CURLE_OK != curl_easy_setopt(curl->handle, CURLOPT_HEADEROPT, CURLHEADER_SEPARATE)) {
+               curl_easy_setopt(curl->handle, CURLOPT_PROXYHEADER, NULL);
+               return FAILURE;
+       }
+       return SUCCESS;
+}
+#endif
+
 #if PHP_HTTP_CURL_VERSION(7,21,3)
 static STATUS php_http_curle_option_set_resolve(php_http_option_t *opt, zval *val, void *userdata)
 {
@@ -1103,6 +1151,30 @@ static STATUS php_http_curle_option_set_resolve(php_http_option_t *opt, zval *va
 }
 #endif
 
+#if PHP_HTTP_CURL_VERSION(7,21,4) && defined(PHP_HTTP_CURL_TLSAUTH_SRP)
+static STATUS php_http_curle_option_set_ssl_tlsauthtype(php_http_option_t *opt, zval *val, void *userdata)
+{
+       php_http_client_curl_handler_t *curl = userdata;
+       CURL *ch = curl->handle;
+
+       if (val && Z_LVAL_P(val)) {
+               switch (Z_LVAL_P(val)) {
+               case CURL_TLSAUTH_SRP:
+                       if (CURLE_OK == curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, PHP_HTTP_CURL_TLSAUTH_SRP)) {
+                               return SUCCESS;
+                       }
+                       /* no break */
+               default:
+                       return FAILURE;
+               }
+       }
+       if (CURLE_OK != curl_easy_setopt(ch, CURLOPT_TLSAUTH_TYPE, PHP_HTTP_CURL_TLSAUTH_DEF)) {
+               return FAILURE;
+       }
+       return SUCCESS;
+}
+#endif
+
 static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC)
 {
        php_http_option_t *opt;
@@ -1124,6 +1196,19 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC)
        php_http_option_register(registry, ZEND_STRL("noproxy"), CURLOPT_NOPROXY, IS_STRING);
 #endif
 
+#if PHP_HTTP_CURL_VERSION(7,37,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("proxyheader"), CURLOPT_PROXYHEADER, IS_ARRAY))) {
+               opt->setter = php_http_curle_option_set_proxyheader;
+       }
+#endif
+
+#if PHP_HTTP_CURL_VERSION(7,40,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("unix_socket_path"), CURLOPT_UNIX_SOCKET_PATH, IS_STRING))) {
+               opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
+               opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
+       }
+#endif
+
        /* dns */
        if ((opt = php_http_option_register(registry, ZEND_STRL("dns_cache_timeout"), CURLOPT_DNS_CACHE_TIMEOUT, IS_LONG))) {
                Z_LVAL(opt->defval) = 60;
@@ -1336,6 +1421,9 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC)
                        ZVAL_BOOL(&opt->defval, 1);
                        opt->setter = php_http_curle_option_set_ssl_verifyhost;
                }
+#if PHP_HTTP_CURL_VERSION(7,41,0)
+               php_http_option_register(registry, ZEND_STRL("verifystatus"), CURLOPT_SSL_VERIFYSTATUS, IS_BOOL);
+#endif
                php_http_option_register(registry, ZEND_STRL("cipher_list"), CURLOPT_SSL_CIPHER_LIST, IS_STRING);
                if ((opt = php_http_option_register(registry, ZEND_STRL("cainfo"), CURLOPT_CAINFO, IS_STRING))) {
                        opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
@@ -1378,6 +1466,23 @@ static void php_http_curle_options_init(php_http_options_t *registry TSRMLS_DC)
                if ((opt = php_http_option_register(registry, ZEND_STRL("enable_alpn"), CURLOPT_SSL_ENABLE_ALPN, IS_BOOL))) {
                        ZVAL_BOOL(&opt->defval, 1);
                }
+#endif
+#if PHP_HTTP_CURL_VERSION(7,39,0)
+               if ((opt = php_http_option_register(registry, ZEND_STRL("pinned_publickey"), CURLOPT_PINNEDPUBLICKEY, IS_STRING))) {
+                       opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
+                       opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_BASEDIR;
+               }
+#endif
+#if PHP_HTTP_CURL_VERSION(7,21,4) && defined(PHP_HTTP_CURL_TLSAUTH_SRP)
+               if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthtype"), CURLOPT_TLSAUTH_TYPE, IS_LONG))) {
+                       opt->setter = php_http_curle_option_set_ssl_tlsauthtype;
+               }
+               if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthuser"), CURLOPT_TLSAUTH_USERNAME, IS_STRING))) {
+                       opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
+               }
+               if ((opt = php_http_option_register(registry, ZEND_STRL("tlsauthpass"), CURLOPT_TLSAUTH_PASSWORD, IS_STRING))) {
+                       opt->flags |= PHP_HTTP_CURLE_OPTION_CHECK_STRLEN;
+               }
 #endif
        }
 }
@@ -1475,6 +1580,182 @@ static STATUS php_http_curle_set_option(php_http_option_t *opt, zval *val, void
        return rv;
 }
 
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+static STATUS php_http_curlm_option_set_pipelining_bl(php_http_option_t *opt, zval *value, void *userdata)
+{
+       php_http_client_t *client = userdata;
+       php_http_client_curl_t *curl = client->ctx;
+       CURLM *ch = curl->handle;
+       HashTable tmp_ht;
+       char **bl = NULL;
+       TSRMLS_FETCH_FROM_CTX(client->ts);
+
+       /* array of char *, ending with a NULL */
+       if (value && Z_TYPE_P(value) != IS_NULL) {
+               zval **entry;
+               HashPosition pos;
+               HashTable *ht = HASH_OF(value);
+               int c = zend_hash_num_elements(ht);
+               char **ptr = ecalloc(c + 1, sizeof(char *));
+
+               bl = ptr;
+
+               zend_hash_init(&tmp_ht, c, NULL, ZVAL_PTR_DTOR, 0);
+               array_join(ht, &tmp_ht, 0, ARRAY_JOIN_STRINGIFY);
+
+               FOREACH_HASH_VAL(pos, &tmp_ht, entry) {
+                       *ptr++ = Z_STRVAL_PP(entry);
+               }
+       }
+
+       if (CURLM_OK != curl_multi_setopt(ch, opt->option, bl)) {
+               if (bl) {
+                       efree(bl);
+                       zend_hash_destroy(&tmp_ht);
+               }
+               return FAILURE;
+       }
+
+       if (bl) {
+               efree(bl);
+               zend_hash_destroy(&tmp_ht);
+       }
+       return SUCCESS;
+}
+#endif
+
+#if PHP_HTTP_HAVE_EVENT
+static inline STATUS php_http_curlm_use_eventloop(php_http_client_t *h, zend_bool enable)
+{
+       php_http_client_curl_t *curl = h->ctx;
+
+       if ((curl->useevents = enable)) {
+               if (!curl->evbase) {
+                       curl->evbase = event_base_new();
+               }
+               if (!curl->timeout) {
+                       curl->timeout = ecalloc(1, sizeof(struct event));
+               }
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback);
+       } else {
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
+               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
+       }
+
+       return SUCCESS;
+}
+
+static STATUS php_http_curlm_option_set_use_eventloop(php_http_option_t *opt, zval *value, void *userdata)
+{
+       php_http_client_t *client = userdata;
+
+       return php_http_curlm_use_eventloop(client, value && Z_BVAL_P(value));
+}
+#endif
+
+static void php_http_curlm_options_init(php_http_options_t *registry TSRMLS_DC)
+{
+       php_http_option_t *opt;
+
+       /* set size of connection cache */
+       if ((opt = php_http_option_register(registry, ZEND_STRL("maxconnects"), CURLMOPT_MAXCONNECTS, IS_LONG))) {
+               /* -1 == default, 0 == unlimited */
+               ZVAL_LONG(&opt->defval, -1);
+       }
+       /* set max number of connections to a single host */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("max_host_connections"), CURLMOPT_MAX_HOST_CONNECTIONS, IS_LONG);
+#endif
+       /* maximum number of requests in a pipeline */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("max_pipeline_length"), CURLMOPT_MAX_PIPELINE_LENGTH, IS_LONG))) {
+               ZVAL_LONG(&opt->defval, 5);
+       }
+#endif
+       /* max simultaneously open connections */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("max_total_connections"), CURLMOPT_MAX_TOTAL_CONNECTIONS, IS_LONG);
+#endif
+       /* enable/disable HTTP pipelining */
+       php_http_option_register(registry, ZEND_STRL("pipelining"), CURLMOPT_PIPELINING, IS_BOOL);
+       /* chunk length threshold for pipelining */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("chunk_length_penalty_size"), CURLMOPT_CHUNK_LENGTH_PENALTY_SIZE, IS_LONG);
+#endif
+       /* size threshold for pipelining penalty */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       php_http_option_register(registry, ZEND_STRL("content_length_penalty_size"), CURLMOPT_CONTENT_LENGTH_PENALTY_SIZE, IS_LONG);
+#endif
+       /* pipelining server blacklist */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_server_bl"), CURLMOPT_PIPELINING_SERVER_BL, IS_ARRAY))) {
+               opt->setter = php_http_curlm_option_set_pipelining_bl;
+       }
+#endif
+       /* pipelining host blacklist */
+#if PHP_HTTP_CURL_VERSION(7,30,0)
+       if ((opt = php_http_option_register(registry, ZEND_STRL("pipelining_site_bl"), CURLMOPT_PIPELINING_SITE_BL, IS_ARRAY))) {
+               opt->setter = php_http_curlm_option_set_pipelining_bl;
+       }
+#endif
+       /* events */
+#if PHP_HTTP_HAVE_EVENT
+       if ((opt = php_http_option_register(registry, ZEND_STRL("use_eventloop"), 0, IS_BOOL))) {
+               opt->setter = php_http_curlm_option_set_use_eventloop;
+       }
+#endif
+}
+
+static STATUS php_http_curlm_set_option(php_http_option_t *opt, zval *val, void *userdata)
+{
+       php_http_client_t *client = userdata;
+       php_http_client_curl_t *curl = client->ctx;
+       CURLM *ch = curl->handle;
+       zval *orig = val;
+       CURLMcode rc = CURLM_UNKNOWN_OPTION;
+       STATUS rv = SUCCESS;
+       TSRMLS_FETCH_FROM_CTX(client->ts);
+
+       if (!val) {
+               val = &opt->defval;
+       } else if (opt->type && Z_TYPE_P(val) != opt->type && !(Z_TYPE_P(val) == IS_NULL && opt->type == IS_ARRAY)) {
+               val = php_http_ztyp(opt->type, val);
+       }
+
+       if (opt->setter) {
+               rv = opt->setter(opt, val, client);
+       } else {
+               switch (opt->type) {
+               case IS_BOOL:
+                       if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, (long) Z_BVAL_P(val)))) {
+                               rv = FAILURE;
+                       }
+                       break;
+               case IS_LONG:
+                       if (CURLM_OK != (rc = curl_multi_setopt(ch, opt->option, Z_LVAL_P(val)))) {
+                               rv = FAILURE;
+                       }
+                       break;
+               default:
+                       rv = FAILURE;
+                       break;
+               }
+       }
+
+       if (val && val != orig && val != &opt->defval) {
+               zval_ptr_dtor(&val);
+       }
+
+       if (rv != SUCCESS) {
+               php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Could not set option %s (%s)", opt->name.s, curl_easy_strerror(rc));
+       }
+       return rv;
+}
 
 /* client ops */
 
@@ -1522,6 +1803,10 @@ static STATUS php_http_client_curl_handler_reset(php_http_client_curl_handler_t
                curl_slist_free_all(curl->options.headers);
                curl->options.headers = NULL;
        }
+       if (curl->options.proxyheaders) {
+               curl_slist_free_all(curl->options.proxyheaders);
+               curl->options.proxyheaders = NULL;
+       }
 
        php_http_buffer_reset(&curl->options.cookies);
        php_http_buffer_reset(&curl->options.ranges);
@@ -1544,12 +1829,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt
        handler->rf = rf;
        handler->client = h;
        handler->handle = handle;
-       handler->request.buffer = php_http_buffer_init(NULL);
-       handler->request.parser = php_http_message_parser_init(NULL TSRMLS_CC);
-       handler->request.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC);
-       handler->response.buffer = php_http_buffer_init(NULL);
-       handler->response.parser = php_http_message_parser_init(NULL TSRMLS_CC);
-       handler->response.message = php_http_message_init(NULL, 0, NULL TSRMLS_CC);
+       handler->response.body = php_http_message_body_init(NULL, NULL TSRMLS_CC);
+       php_http_buffer_init(&handler->response.headers);
        php_http_buffer_init(&handler->options.cookies);
        php_http_buffer_init(&handler->options.ranges);
        zend_hash_init(&handler->options.cache, 0, NULL, ZVAL_PTR_DTOR, 0);
@@ -1562,11 +1843,11 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt
        curl_easy_setopt(handle, CURLOPT_AUTOREFERER, 1L);
        curl_easy_setopt(handle, CURLOPT_VERBOSE, 1L);
        curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 0L);
-       curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, NULL);
-       curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_dummy_callback);
+       curl_easy_setopt(handle, CURLOPT_HEADERFUNCTION, php_http_curle_header_callback);
+       curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, php_http_curle_body_callback);
        curl_easy_setopt(handle, CURLOPT_DEBUGFUNCTION, php_http_curle_raw_callback);
        curl_easy_setopt(handle, CURLOPT_READFUNCTION, php_http_curle_read_callback);
-       curl_easy_setopt(handle, CURLOPT_IOCTLFUNCTION, php_http_curle_ioctl_callback);
+       curl_easy_setopt(handle, CURLOPT_SEEKFUNCTION, php_http_curle_seek_callback);
 #if PHP_HTTP_CURL_VERSION(7,32,0)
        curl_easy_setopt(handle, CURLOPT_XFERINFOFUNCTION, php_http_curle_xferinfo_callback);
        curl_easy_setopt(handle, CURLOPT_XFERINFODATA, handler);
@@ -1575,6 +1856,8 @@ static php_http_client_curl_handler_t *php_http_client_curl_handler_init(php_htt
        curl_easy_setopt(handle, CURLOPT_PROGRESSDATA, handler);
 #endif
        curl_easy_setopt(handle, CURLOPT_DEBUGDATA, handler);
+       curl_easy_setopt(handle, CURLOPT_WRITEDATA, handler);
+       curl_easy_setopt(handle, CURLOPT_HEADERDATA, handler);
 
        php_http_client_curl_handler_reset(handler);
 
@@ -1598,7 +1881,7 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_
        if (storage->url) {
                pefree(storage->url, 1);
        }
-       storage->url = pestrdup(PHP_HTTP_INFO(msg).request.url, 1);
+       php_http_url_to_string(PHP_HTTP_INFO(msg).request.url, &storage->url, NULL, 1);
        curl_easy_setopt(curl->handle, CURLOPT_URL, storage->url);
 
        /* request method */
@@ -1630,6 +1913,9 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_
                }
        }
 
+       /* apply options */
+       php_http_options_apply(&php_http_curle_options, enqueue->options, curl);
+
        /* request headers */
        php_http_message_update_headers(msg);
        if (zend_hash_num_elements(&msg->hdrs)) {
@@ -1674,19 +1960,17 @@ static STATUS php_http_client_curl_handler_prepare(php_http_client_curl_handler_
                 * does not allow a request body.
                 */
                php_stream_rewind(php_http_message_body_stream(msg->body));
-               curl_easy_setopt(curl->handle, CURLOPT_IOCTLDATA, msg->body);
+               curl_easy_setopt(curl->handle, CURLOPT_SEEKDATA, msg->body);
                curl_easy_setopt(curl->handle, CURLOPT_READDATA, msg->body);
                curl_easy_setopt(curl->handle, CURLOPT_INFILESIZE, body_size);
                curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, body_size);
        } else {
-               curl_easy_setopt(curl->handle, CURLOPT_IOCTLDATA, NULL);
+               curl_easy_setopt(curl->handle, CURLOPT_SEEKDATA, NULL);
                curl_easy_setopt(curl->handle, CURLOPT_READDATA, NULL);
                curl_easy_setopt(curl->handle, CURLOPT_INFILESIZE, 0L);
                curl_easy_setopt(curl->handle, CURLOPT_POSTFIELDSIZE, 0L);
        }
 
-       php_http_options_apply(&php_http_curle_options, enqueue->options, curl);
-
        return SUCCESS;
 }
 
@@ -1711,12 +1995,8 @@ static void php_http_client_curl_handler_dtor(php_http_client_curl_handler_t *ha
        php_resource_factory_handle_dtor(handler->rf, handler->handle TSRMLS_CC);
        php_resource_factory_free(&handler->rf);
 
-       php_http_message_parser_free(&handler->request.parser);
-       php_http_message_free(&handler->request.message);
-       php_http_buffer_free(&handler->request.buffer);
-       php_http_message_parser_free(&handler->response.parser);
-       php_http_message_free(&handler->response.message);
-       php_http_buffer_free(&handler->response.buffer);
+       php_http_message_body_free(&handler->response.body);
+       php_http_buffer_dtor(&handler->response.headers);
        php_http_buffer_dtor(&handler->options.ranges);
        php_http_buffer_dtor(&handler->options.cookies);
        zend_hash_destroy(&handler->options.cache);
@@ -1784,35 +2064,41 @@ static void queue_dtor(php_http_client_enqueue_t *e)
        php_http_client_curl_handler_dtor(handler);
 }
 
-static php_resource_factory_t *create_rf(const char *url TSRMLS_DC)
+static php_resource_factory_t *create_rf(php_http_client_t *h, php_http_client_enqueue_t *enqueue TSRMLS_DC)
 {
-       php_url *purl;
+       php_persistent_handle_factory_t *pf = NULL;
        php_resource_factory_t *rf = NULL;
+       php_http_url_t *url = enqueue->request->http.info.request.url;
 
-       if (!url || !*url) {
+       if (!url || (!url->host && !url->path)) {
                php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot request empty URL");
                return NULL;
        }
 
-       purl = php_url_parse(url);
-
-       if (!purl) {
-               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse URL '%s'", url);
-               return NULL;
-       } else {
+       /* only if the client itself is setup for persistence */
+       if (h->rf->dtor == (void (*)(void*)) php_persistent_handle_abandon) {
                char *id_str = NULL;
-               size_t id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(purl->host), purl->port ? purl->port : 80);
-               php_persistent_handle_factory_t *pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC);
+               size_t id_len;
+               int port = url->port ? url->port : 80;
+               zval **zport;
+
+               if (SUCCESS == zend_hash_find(enqueue->options, ZEND_STRS("port"), (void *) &zport)) {
+                       zval *zcpy = php_http_ztyp(IS_LONG, *zport);
 
-               if (pf) {
-                       rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon);
+                       if (Z_LVAL_P(zcpy)) {
+                               port = Z_LVAL_P(zcpy);
+                       }
+                       zval_ptr_dtor(&zcpy);
                }
 
-               php_url_free(purl);
+               id_len = spprintf(&id_str, 0, "%s:%d", STR_PTR(url->host), port);
+               pf = php_persistent_handle_concede(NULL, ZEND_STRL("http\\Client\\Curl\\Request"), id_str, id_len, NULL, NULL TSRMLS_CC);
                efree(id_str);
        }
 
-       if (!rf) {
+       if (pf) {
+               rf = php_resource_factory_init(NULL, php_persistent_handle_get_resource_factory_ops(), pf, (void (*)(void*)) php_persistent_handle_abandon);
+       } else {
                rf = php_resource_factory_init(NULL, &php_http_curle_resource_factory_ops, NULL, NULL);
        }
 
@@ -1828,7 +2114,7 @@ static STATUS php_http_client_curl_enqueue(php_http_client_t *h, php_http_client
        php_resource_factory_t *rf;
        TSRMLS_FETCH_FROM_CTX(h->ts);
 
-       rf = create_rf(enqueue->request->http.info.request.url TSRMLS_CC);
+       rf = create_rf(h, enqueue TSRMLS_CC);
        if (!rf) {
                return FAILURE;
        }
@@ -2017,6 +2303,10 @@ static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_
        php_http_client_curl_t *curl = h->ctx;
 
        switch (opt) {
+               case PHP_HTTP_CLIENT_OPT_CONFIGURATION:
+                       return php_http_options_apply(&php_http_curlm_options, (HashTable *) arg,  h);
+                       break;
+
                case PHP_HTTP_CLIENT_OPT_ENABLE_PIPELINING:
                        if (CURLM_OK != curl_multi_setopt(curl->handle, CURLMOPT_PIPELINING, (long) *((zend_bool *) arg))) {
                                return FAILURE;
@@ -2025,23 +2315,7 @@ static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_
 
                case PHP_HTTP_CLIENT_OPT_USE_EVENTS:
 #if PHP_HTTP_HAVE_EVENT
-                       if ((curl->useevents = *((zend_bool *) arg))) {
-                               if (!curl->evbase) {
-                                       curl->evbase = event_base_new();
-                               }
-                               if (!curl->timeout) {
-                                       curl->timeout = ecalloc(1, sizeof(struct event));
-                               }
-                               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, h);
-                               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, php_http_curlm_socket_callback);
-                               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, h);
-                               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, php_http_curlm_timer_callback);
-                       } else {
-                               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETDATA, NULL);
-                               curl_multi_setopt(curl->handle, CURLMOPT_SOCKETFUNCTION, NULL);
-                               curl_multi_setopt(curl->handle, CURLMOPT_TIMERDATA, NULL);
-                               curl_multi_setopt(curl->handle, CURLMOPT_TIMERFUNCTION, NULL);
-                       }
+                       return php_http_curlm_use_eventloop(h, *(zend_bool *) arg);
                        break;
 #endif
 
@@ -2051,9 +2325,43 @@ static STATUS php_http_client_curl_setopt(php_http_client_t *h, php_http_client_
        return SUCCESS;
 }
 
+static int apply_available_options(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
+{
+       php_http_option_t *opt = pDest;
+       HashTable *ht;
+       zval *entry;
+       int c;
+
+       ht = va_arg(args, HashTable*);
+
+       MAKE_STD_ZVAL(entry);
+
+       if ((c = zend_hash_num_elements(&opt->suboptions.options))) {
+               array_init_size(entry, c);
+               zend_hash_apply_with_arguments(&opt->suboptions.options TSRMLS_CC, apply_available_options, 1, Z_ARRVAL_P(entry));
+       } else {
+               /* catch deliberate NULL options */
+               if (Z_TYPE(opt->defval) == IS_STRING && !Z_STRVAL(opt->defval)) {
+                       ZVAL_NULL(entry);
+               } else {
+                       ZVAL_COPY_VALUE(entry, &opt->defval);
+                       zval_copy_ctor(entry);
+               }
+       }
+
+       if (hash_key->nKeyLength) {
+               zend_hash_quick_update(ht, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &entry, sizeof(zval *), NULL);
+       } else {
+               zend_hash_index_update(ht, hash_key->h, (void *) &entry, sizeof(zval *), NULL);
+       }
+
+       return ZEND_HASH_APPLY_KEEP;
+}
+
 static STATUS php_http_client_curl_getopt(php_http_client_t *h, php_http_client_getopt_opt_t opt, void *arg, void **res)
 {
        php_http_client_enqueue_t *enqueue;
+       TSRMLS_FETCH_FROM_CTX(h->ts);
 
        switch (opt) {
        case PHP_HTTP_CLIENT_OPT_PROGRESS_INFO:
@@ -2074,6 +2382,14 @@ static STATUS php_http_client_curl_getopt(php_http_client_t *h, php_http_client_
                }
                break;
 
+       case PHP_HTTP_CLIENT_OPT_AVAILABLE_OPTIONS:
+               zend_hash_apply_with_arguments(&php_http_curle_options.options TSRMLS_CC, apply_available_options, 1, *(HashTable **) res);
+               break;
+
+       case PHP_HTTP_CLIENT_OPT_AVAILABLE_CONFIGURATION:
+               zend_hash_apply_with_arguments(&php_http_curlm_options.options TSRMLS_CC, apply_available_options, 1, *(HashTable **) res);
+               break;
+
        default:
                break;
        }
@@ -2110,8 +2426,8 @@ PHP_MINIT_FUNCTION(http_client_curl)
        };
 
        if (SUCCESS != php_http_client_driver_add(&driver)) {
-                       return FAILURE;
-               }
+               return FAILURE;
+       }
 
        if (SUCCESS != php_persistent_handle_provide(ZEND_STRL("http\\Client\\Curl"), &php_http_curlm_resource_factory_ops, NULL, NULL TSRMLS_CC)) {
                return FAILURE;
@@ -2126,12 +2442,21 @@ PHP_MINIT_FUNCTION(http_client_curl)
 
                php_http_curle_options_init(options TSRMLS_CC);
        }
+       if ((options = php_http_options_init(&php_http_curlm_options, 1))) {
+               options->getter = php_http_option_get;
+               options->setter = php_http_curlm_set_option;
+
+               php_http_curlm_options_init(options TSRMLS_CC);
+       }
 
        /*
        * HTTP Protocol Version Constants
        */
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_0", CURL_HTTP_VERSION_1_0, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_1_1", CURL_HTTP_VERSION_1_1, CONST_CS|CONST_PERSISTENT);
+#if PHP_HTTP_CURL_VERSION(7,33,0)
+       REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_2_0", CURL_HTTP_VERSION_2_0, CONST_CS|CONST_PERSISTENT);
+#endif
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "HTTP_VERSION_ANY", CURL_HTTP_VERSION_NONE, CONST_CS|CONST_PERSISTENT);
 
        /*
@@ -2146,6 +2471,9 @@ PHP_MINIT_FUNCTION(http_client_curl)
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv2", CURL_SSLVERSION_SSLv2, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_SSLv3", CURL_SSLVERSION_SSLv3, CONST_CS|CONST_PERSISTENT);
        REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "SSL_VERSION_ANY", CURL_SSLVERSION_DEFAULT, CONST_CS|CONST_PERSISTENT);
+#if PHP_HTTP_CURL_VERSION(7,21,4) && defined(PHP_HTTP_CURL_TLSAUTH_SRP)
+       REGISTER_NS_LONG_CONSTANT("http\\Client\\Curl", "TLSAUTH_SRP", CURL_TLSAUTH_SRP, CONST_CS|CONST_PERSISTENT);
+#endif
 
        /*
        * DNS IPvX resolving
@@ -2202,6 +2530,7 @@ PHP_MSHUTDOWN_FUNCTION(http_client_curl)
        php_persistent_handle_cleanup(ZEND_STRL("http\\Client\\Curl\\Request"), NULL, 0 TSRMLS_CC);
 
        php_http_options_dtor(&php_http_curle_options);
+       php_http_options_dtor(&php_http_curlm_options);
 
        return SUCCESS;
 }
index ea223be780015137bbbfefea695f215b39cbbb6c..0e40cc5bc930778620321479e570da865b4686ac 100644 (file)
@@ -31,12 +31,12 @@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpClientRequest___construct, 0, 0, 0)
 ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpClientRequest, __construct)
 {
-       char *meth_str = NULL, *url_str = NULL;
-       int meth_len = 0, url_len = 0;
-       zval *zheaders = NULL, *zbody = NULL;
+       char *meth_str = NULL;
+       int meth_len = 0;
+       zval *zheaders = NULL, *zbody = NULL, *zurl = NULL;
        php_http_message_object_t *obj;
 
-       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!s!a!O!", &meth_str, &meth_len, &url_str, &url_len, &zheaders, &zbody, php_http_message_body_class_entry), invalid_arg, return);
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!z!a!O!", &meth_str, &meth_len, &zurl, &zheaders, &zbody, php_http_message_body_class_entry), invalid_arg, return);
 
        obj = zend_object_store_get_object(getThis() TSRMLS_CC);
 
@@ -52,8 +52,8 @@ static PHP_METHOD(HttpClientRequest, __construct)
        if (meth_str && meth_len) {
                PHP_HTTP_INFO(obj->message).request.method = estrndup(meth_str, meth_len);
        }
-       if (url_str && url_len) {
-               PHP_HTTP_INFO(obj->message).request.url = estrndup(url_str, url_len);
+       if (zurl) {
+               PHP_HTTP_INFO(obj->message).request.url = php_http_url_from_zval(zurl, ~0 TSRMLS_CC);
        }
        if (zheaders) {
                array_copy(Z_ARRVAL_P(zheaders), &obj->message->hdrs);
@@ -113,8 +113,9 @@ static PHP_METHOD(HttpClientRequest, setQuery)
 {
        zval *qdata = NULL;
        php_http_message_object_t *obj;
-       php_url *old_url = NULL, new_url = {NULL};
+       php_http_url_t *old_url = NULL, new_url = {NULL};
        char empty[] = "";
+       unsigned flags = PHP_HTTP_URL_REPLACE;
 
        php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!", &qdata), invalid_arg, return);
 
@@ -137,21 +138,20 @@ static PHP_METHOD(HttpClientRequest, setQuery)
                new_url.query = Z_STRVAL(str);
                zval_dtor(&arr);
        } else {
-               new_url.query = &empty[0];
+               flags = PHP_HTTP_URL_STRIP_QUERY;
        }
 
        if (obj->message->http.info.request.url) {
-               old_url = php_url_parse(obj->message->http.info.request.url);
-               efree(obj->message->http.info.request.url);
+               old_url = obj->message->http.info.request.url;
        }
 
-       php_http_url(PHP_HTTP_URL_REPLACE, old_url, &new_url, NULL, &obj->message->http.info.request.url, NULL TSRMLS_CC);
+       obj->message->http.info.request.url = php_http_url_mod(old_url, &new_url, flags TSRMLS_CC);
 
        if (old_url) {
-               php_url_free(old_url);
+               php_http_url_free(&old_url);
        }
        if (new_url.query != &empty[0]) {
-               STR_FREE(new_url.query);
+               PTR_FREE(new_url.query);
        }
 
        RETVAL_ZVAL(getThis(), 1, 0);
@@ -166,16 +166,8 @@ static PHP_METHOD(HttpClientRequest, getQuery)
 
                PHP_HTTP_CLIENT_REQUEST_OBJECT_INIT(obj);
 
-               if (obj->message->http.info.request.url) {
-                       php_url *purl = php_url_parse(obj->message->http.info.request.url);
-
-                       if (purl) {
-                               if (purl->query) {
-                                       RETVAL_STRING(purl->query, 0);
-                                       purl->query = NULL;
-                               }
-                               php_url_free(purl);
-                       }
+               if (obj->message->http.info.request.url && obj->message->http.info.request.url->query) {
+                       RETVAL_STRING(obj->message->http.info.request.url->query, 1);
                }
        }
 }
@@ -187,7 +179,7 @@ static PHP_METHOD(HttpClientRequest, addQuery)
 {
        zval *qdata, arr, str;
        php_http_message_object_t *obj;
-       php_url *old_url = NULL, new_url = {NULL};
+       php_http_url_t *old_url = NULL, new_url = {NULL};
 
        php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &qdata), invalid_arg, return);
 
@@ -208,16 +200,15 @@ static PHP_METHOD(HttpClientRequest, addQuery)
        zval_dtor(&arr);
 
        if (obj->message->http.info.request.url) {
-               old_url = php_url_parse(obj->message->http.info.request.url);
-               efree(obj->message->http.info.request.url);
+               old_url = obj->message->http.info.request.url;
        }
 
-       php_http_url(PHP_HTTP_URL_JOIN_QUERY, old_url, &new_url, NULL, &obj->message->http.info.request.url, NULL TSRMLS_CC);
+       obj->message->http.info.request.url = php_http_url_mod(old_url, &new_url, PHP_HTTP_URL_JOIN_QUERY TSRMLS_CC);
 
        if (old_url) {
-               php_url_free(old_url);
+               php_http_url_free(&old_url);
        }
-       STR_FREE(new_url.query);
+       PTR_FREE(new_url.query);
 
        RETVAL_ZVAL(getThis(), 1, 0);
 }
index 4bd8d80224606f7464a4470250da56d2eb1c07e1..354dfa6ed4f0315aa8dbb0e43fb4062281351316 100644 (file)
@@ -41,8 +41,8 @@ php_http_cookie_list_t *php_http_cookie_list_copy(php_http_cookie_list_t *from,
        array_copy(&from->cookies, &to->cookies);
        array_copy(&from->extras, &to->extras);
 
-       STR_SET(to->path, from->path ? estrdup(from->path) : NULL);
-       STR_SET(to->domain, from->domain ? estrdup(from->domain) : NULL);
+       PTR_SET(to->path, from->path ? estrdup(from->path) : NULL);
+       PTR_SET(to->domain, from->domain ? estrdup(from->domain) : NULL);
        to->expires = from->expires;
        to->max_age = from->max_age;
        to->flags = from->flags;
@@ -56,8 +56,8 @@ void php_http_cookie_list_dtor(php_http_cookie_list_t *list)
                zend_hash_destroy(&list->cookies);
                zend_hash_destroy(&list->extras);
        
-               STR_SET(list->path, NULL);
-               STR_SET(list->domain, NULL);
+               PTR_SET(list->path, NULL);
+               PTR_SET(list->domain, NULL);
        }
 }
 
@@ -125,9 +125,9 @@ static void add_entry(php_http_cookie_list_t *list, char **allowed_extras, long
        }
 
        if _KEY_IS("path") {
-               STR_SET(list->path, estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg)));
+               PTR_SET(list->path, estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg)));
        } else if _KEY_IS("domain") {
-               STR_SET(list->domain, estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg)));
+               PTR_SET(list->domain, estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg)));
        } else if _KEY_IS("expires") {
                char *date = estrndup(Z_STRVAL_P(arg), Z_STRLEN_P(arg));
                list->expires = php_parse_date(date, NULL);
@@ -782,7 +782,7 @@ static PHP_METHOD(HttpCookie, setDomain)
 
        PHP_HTTP_COOKIE_OBJECT_INIT(obj);
 
-       STR_SET(obj->list->domain, domain_str ? estrndup(domain_str, domain_len) : NULL);
+       PTR_SET(obj->list->domain, domain_str ? estrndup(domain_str, domain_len) : NULL);
 
        RETVAL_ZVAL(getThis(), 1, 0);
 }
@@ -821,7 +821,7 @@ static PHP_METHOD(HttpCookie, setPath)
 
        PHP_HTTP_COOKIE_OBJECT_INIT(obj);
 
-       STR_SET(obj->list->path, path_str ? estrndup(path_str, path_len) : NULL);
+       PTR_SET(obj->list->path, path_str ? estrndup(path_str, path_len) : NULL);
 
        RETVAL_ZVAL(getThis(), 1, 0);
 }
index 7f0462c78c1c5537e7ee2438fb74d2999836f368..b7050f6d769e737984bcddee1fd45996c0c3d179 100644 (file)
@@ -180,7 +180,7 @@ STATUS php_http_encoding_deflate(int flags, const char *data, size_t data_len, c
                        (*encoded)[*encoded_len = Z.total_out] = '\0';
                        return SUCCESS;
                } else {
-                       STR_SET(*encoded, NULL);
+                       PTR_SET(*encoded, NULL);
                        *encoded_len = 0;
                }
        }
@@ -513,7 +513,7 @@ static STATUS deflate_update(php_http_encoding_stream_t *s, const char *data, si
                        return SUCCESS;
        }
        
-       STR_SET(*encoded, NULL);
+       PTR_SET(*encoded, NULL);
        *encoded_len = 0;
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to update deflate stream: %s", zError(status));
        return FAILURE;
@@ -709,7 +709,7 @@ static STATUS deflate_flush(php_http_encoding_stream_t *s, char **encoded, size_
                        return SUCCESS;
        }
        
-       STR_SET(*encoded, NULL);
+       PTR_SET(*encoded, NULL);
        *encoded_len = 0;
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to flush deflate stream: %s", zError(status));
        return FAILURE;
@@ -766,7 +766,7 @@ static STATUS deflate_finish(php_http_encoding_stream_t *s, char **encoded, size
                return SUCCESS;
        }
        
-       STR_SET(*encoded, NULL);
+       PTR_SET(*encoded, NULL);
        *encoded_len = 0;
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to finish deflate stream: %s", zError(status));
        return FAILURE;
@@ -805,7 +805,7 @@ static STATUS inflate_finish(php_http_encoding_stream_t *s, char **decoded, size
                return SUCCESS;
        }
        
-       STR_SET(*decoded, NULL);
+       PTR_SET(*decoded, NULL);
        *decoded_len = 0;
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to finish inflate stream: %s", zError(status));
        return FAILURE;
@@ -1071,7 +1071,7 @@ static PHP_METHOD(HttpEncodingStream, finish)
                                                RETURN_EMPTY_STRING();
                                        }
                                } else {
-                                       STR_FREE(encoded_str);
+                                       PTR_FREE(encoded_str);
                                }
                        }
                }
index 2969d1a4fbe5b19bb0e5daf354e022ccabd948e2..6de5ecb90a5ad419b8f098bb16dc31e1287a341c 100644 (file)
@@ -70,7 +70,7 @@ PHP_RINIT_FUNCTION(http_env)
                }
        }
 
-       STR_SET(SG(request_info).content_type_dup, NULL);
+       PTR_SET(SG(request_info).content_type_dup, NULL);
 
        return SUCCESS;
 }
@@ -287,7 +287,7 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_
                return PHP_HTTP_RANGE_NO;
        }
        if (strncmp(range, "bytes=", lenof("bytes="))) {
-               STR_FREE(range);
+               PTR_FREE(range);
                return PHP_HTTP_RANGE_NO;
        }
 
@@ -343,7 +343,7 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_
                                                        switch (end) {
                                                                /* "0-" */
                                                                case -1:
-                                                                       STR_FREE(range);
+                                                                       PTR_FREE(range);
                                                                        return PHP_HTTP_RANGE_NO;
 
                                                                /* "0-0" */
@@ -364,7 +364,7 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_
                                                case -1:
                                                        /* "-", "-0" */
                                                        if (end == -1 || end == -10) {
-                                                               STR_FREE(range);
+                                                               PTR_FREE(range);
                                                                return PHP_HTTP_RANGE_ERR;
                                                        }
                                                        begin = length - end;
@@ -374,13 +374,13 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_
                                                /* "12345-(NNN)" */
                                                default:
                                                        if (length <= (size_t) begin) {
-                                                               STR_FREE(range);
+                                                               PTR_FREE(range);
                                                                return PHP_HTTP_RANGE_ERR;
                                                        }
                                                        switch (end) {
                                                                /* "12345-0" */
                                                                case -10:
-                                                                       STR_FREE(range);
+                                                                       PTR_FREE(range);
                                                                        return PHP_HTTP_RANGE_ERR;
 
                                                                /* "12345-" */
@@ -393,7 +393,7 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_
                                                                        if (length <= (size_t) end) {
                                                                                end = length - 1;
                                                                        } else if (end <  begin) {
-                                                                               STR_FREE(range);
+                                                                               PTR_FREE(range);
                                                                                return PHP_HTTP_RANGE_ERR;
                                                                        }
                                                                        break;
@@ -415,12 +415,12 @@ php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_
                                break;
 
                        default:
-                               STR_FREE(range);
+                               PTR_FREE(range);
                                return PHP_HTTP_RANGE_NO;
                }
        } while (c != 0);
 
-       STR_FREE(range);
+       PTR_FREE(range);
        return PHP_HTTP_RANGE_OK;
 }
 
@@ -573,107 +573,22 @@ STATUS php_http_env_set_response_header_value(long http_code, const char *name_s
                        ret = sapi_header_op(replace ? SAPI_HEADER_REPLACE : SAPI_HEADER_ADD, (void *) &h TSRMLS_CC);
 
                        zval_ptr_dtor(&data);
-                       STR_FREE(h.line);
+                       PTR_FREE(h.line);
 
                        return ret;
                }
        }
 }
 
-static PHP_HTTP_STRLIST(php_http_env_response_status) =
-       PHP_HTTP_STRLIST_ITEM("Continue")
-       PHP_HTTP_STRLIST_ITEM("Switching Protocols")
-       PHP_HTTP_STRLIST_ITEM("Processing")
-       PHP_HTTP_STRLIST_NEXT
-       PHP_HTTP_STRLIST_ITEM("OK")
-       PHP_HTTP_STRLIST_ITEM("Created")
-       PHP_HTTP_STRLIST_ITEM("Accepted")
-       PHP_HTTP_STRLIST_ITEM("Non-Authoritative Information")
-       PHP_HTTP_STRLIST_ITEM("No Content")
-       PHP_HTTP_STRLIST_ITEM("Reset Content")
-       PHP_HTTP_STRLIST_ITEM("Partial Content")
-       PHP_HTTP_STRLIST_ITEM("Multi-Status")
-       PHP_HTTP_STRLIST_ITEM("Already Reported")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("IM Used")
-       PHP_HTTP_STRLIST_NEXT
-       PHP_HTTP_STRLIST_ITEM("Multiple Choices")
-       PHP_HTTP_STRLIST_ITEM("Moved Permanently")
-       PHP_HTTP_STRLIST_ITEM("Found")
-       PHP_HTTP_STRLIST_ITEM("See Other")
-       PHP_HTTP_STRLIST_ITEM("Not Modified")
-       PHP_HTTP_STRLIST_ITEM("Use Proxy")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Temporary Redirect")
-       PHP_HTTP_STRLIST_ITEM("Permanent Redirect")
-       PHP_HTTP_STRLIST_NEXT
-       PHP_HTTP_STRLIST_ITEM("Bad Request")
-       PHP_HTTP_STRLIST_ITEM("Unauthorized")
-       PHP_HTTP_STRLIST_ITEM("Payment Required")
-       PHP_HTTP_STRLIST_ITEM("Forbidden")
-       PHP_HTTP_STRLIST_ITEM("Not Found")
-       PHP_HTTP_STRLIST_ITEM("Method Not Allowed")
-       PHP_HTTP_STRLIST_ITEM("Not Acceptable")
-       PHP_HTTP_STRLIST_ITEM("Proxy Authentication Required")
-       PHP_HTTP_STRLIST_ITEM("Request Timeout")
-       PHP_HTTP_STRLIST_ITEM("Conflict")
-       PHP_HTTP_STRLIST_ITEM("Gone")
-       PHP_HTTP_STRLIST_ITEM("Length Required")
-       PHP_HTTP_STRLIST_ITEM("Precondition Failed")
-       PHP_HTTP_STRLIST_ITEM("Request Entity Too Large")
-       PHP_HTTP_STRLIST_ITEM("Request URI Too Long")
-       PHP_HTTP_STRLIST_ITEM("Unsupported Media Type")
-       PHP_HTTP_STRLIST_ITEM("Requested Range Not Satisfiable")
-       PHP_HTTP_STRLIST_ITEM("Expectation Failed")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Unprocessible Entity")
-       PHP_HTTP_STRLIST_ITEM("Locked")
-       PHP_HTTP_STRLIST_ITEM("Failed Dependency")
-       PHP_HTTP_STRLIST_ITEM("(Reserved)")
-       PHP_HTTP_STRLIST_ITEM("Upgrade Required")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Precondition Required")
-       PHP_HTTP_STRLIST_ITEM("Too Many Requests")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Request Header Fields Too Large")
-       PHP_HTTP_STRLIST_NEXT
-       PHP_HTTP_STRLIST_ITEM("Internal Server Error")
-       PHP_HTTP_STRLIST_ITEM("Not Implemented")
-       PHP_HTTP_STRLIST_ITEM("Bad Gateway")
-       PHP_HTTP_STRLIST_ITEM("Service Unavailable")
-       PHP_HTTP_STRLIST_ITEM("Gateway Timeout")
-       PHP_HTTP_STRLIST_ITEM("HTTP Version Not Supported")
-       PHP_HTTP_STRLIST_ITEM("Variant Also Negotiates")
-       PHP_HTTP_STRLIST_ITEM("Insufficient Storage")
-       PHP_HTTP_STRLIST_ITEM("Loop Detected")
-       PHP_HTTP_STRLIST_ITEM("(Unused)")
-       PHP_HTTP_STRLIST_ITEM("Not Extended")
-       PHP_HTTP_STRLIST_ITEM("Network Authentication Required")
-       PHP_HTTP_STRLIST_STOP
-;
-
 const char *php_http_env_get_response_status_for_code(unsigned code)
 {
-       return php_http_strlist_find(php_http_env_response_status, 100, code);
+       switch (code) {
+#define PHP_HTTP_RESPONSE_CODE(c, s) case c: return s;
+#include "php_http_response_codes.h"
+#undef PHP_HTTP_RESPONSE_CODE
+       default:
+               return NULL;
+       }
 }
 
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getRequestHeader, 0, 0, 0)
@@ -724,32 +639,29 @@ ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpEnv, getResponseStatusForCode)
 {
        long code;
+       const char *status;
 
        if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &code)) {
                return;
        }
-       RETURN_STRING(php_http_env_get_response_status_for_code(code), 1);
+
+       if ((status = php_http_env_get_response_status_for_code(code))) {
+               RETURN_STRING(status, 1);
+       }
 }
 
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseStatusForAllCodes, 0, 0, 0)
 ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpEnv, getResponseStatusForAllCodes)
 {
-       const char *s;
-       unsigned c;
-       php_http_strlist_iterator_t i;
-
        if (SUCCESS != zend_parse_parameters_none()) {
                return;
        }
 
        array_init(return_value);
-       for (   php_http_strlist_iterator_init(&i, php_http_env_response_status, 100);
-                       *(s = php_http_strlist_iterator_this(&i, &c));
-                       php_http_strlist_iterator_next(&i)
-       ) {
-               add_index_string(return_value, c, s, 1);
-       }
+#define PHP_HTTP_RESPONSE_CODE(code, status) add_index_string(return_value, code, status, 1);
+#include "php_http_response_codes.h"
+#undef PHP_HTTP_RESPONSE_CODE
 }
 
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnv_getResponseHeader, 0, 0, 0)
@@ -977,7 +889,7 @@ static SAPI_POST_HANDLER_FUNC(php_http_json_post_handler)
                }
        }
 #if PHP_VERSION_ID >= 50600
-       STR_FREE(json_str);
+       PTR_FREE(json_str);
 #endif
 }
 
index 6aff0c00fe0d106fba418841e061efc02bb7778d..ac0a5e4d23ebe31400f180b4c36512b764736c72 100644 (file)
@@ -149,6 +149,16 @@ static PHP_METHOD(HttpEnvRequest, __construct)
        zend_update_property(php_http_env_request_class_entry, getThis(), ZEND_STRL("form"), zqs TSRMLS_CC);
        zval_ptr_dtor(&zqs);
 
+       zsg = php_http_env_get_superglobal(ZEND_STRL("_COOKIE") TSRMLS_CC);
+       MAKE_STD_ZVAL(zqs);
+       object_init_ex(zqs, php_http_querystring_class_entry);
+       php_http_expect(SUCCESS == php_http_querystring_ctor(zqs, zsg TSRMLS_CC), unexpected_val,
+                       zval_ptr_dtor(&zqs);
+                       return;
+       );
+       zend_update_property(php_http_env_request_class_entry, getThis(), ZEND_STRL("cookie"), zqs TSRMLS_CC);
+       zval_ptr_dtor(&zqs);
+
        MAKE_STD_ZVAL(zqs);
        array_init(zqs);
        if ((zsg = php_http_env_get_superglobal(ZEND_STRL("_FILES") TSRMLS_CC))) {
@@ -212,6 +222,22 @@ static PHP_METHOD(HttpEnvRequest, getQuery)
        }
 }
 
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnvRequest_getCookie, 0, 0, 0)
+       ZEND_ARG_INFO(0, name)
+       ZEND_ARG_INFO(0, type)
+       ZEND_ARG_INFO(0, defval)
+       ZEND_ARG_INFO(0, delete)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpEnvRequest, getCookie)
+{
+       if (ZEND_NUM_ARGS()) {
+               call_querystring_get("cookie");
+       } else {
+               zval *zcookie = zend_read_property(php_http_env_request_class_entry, getThis(), ZEND_STRL("cookie"), 0 TSRMLS_CC);
+               RETURN_ZVAL(zcookie, 1, 0);
+       }
+}
+
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnvRequest_getFiles, 0, 0, 0)
 ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpEnvRequest, getFiles)
@@ -226,6 +252,7 @@ static zend_function_entry php_http_env_request_methods[] = {
        PHP_ME(HttpEnvRequest, __construct,  ai_HttpEnvRequest___construct,  ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
        PHP_ME(HttpEnvRequest, getForm,      ai_HttpEnvRequest_getForm,      ZEND_ACC_PUBLIC)
        PHP_ME(HttpEnvRequest, getQuery,     ai_HttpEnvRequest_getQuery,     ZEND_ACC_PUBLIC)
+       PHP_ME(HttpEnvRequest, getCookie,    ai_HttpEnvRequest_getCookie,    ZEND_ACC_PUBLIC)
        PHP_ME(HttpEnvRequest, getFiles,     ai_HttpEnvRequest_getFiles,     ZEND_ACC_PUBLIC)
        EMPTY_FUNCTION_ENTRY
 };
@@ -241,6 +268,7 @@ PHP_MINIT_FUNCTION(http_env_request)
 
        zend_declare_property_null(php_http_env_request_class_entry, ZEND_STRL("query"), ZEND_ACC_PROTECTED TSRMLS_CC);
        zend_declare_property_null(php_http_env_request_class_entry, ZEND_STRL("form"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       zend_declare_property_null(php_http_env_request_class_entry, ZEND_STRL("cookie"), ZEND_ACC_PROTECTED TSRMLS_CC);
        zend_declare_property_null(php_http_env_request_class_entry, ZEND_STRL("files"), ZEND_ACC_PROTECTED TSRMLS_CC);
 
        return SUCCESS;
index 13a35c6af7054803de8760f6cc688ee2745ab954..2f2f161a676655bab5578d412dea6bc4206aaffb 100644 (file)
@@ -26,6 +26,7 @@ static void set_option(zval *options, const char *name_str, size_t name_len, int
                                case IS_STRING:
                                        zend_update_property_stringl(Z_OBJCE_P(options), options, name_str, name_len, value_ptr, value_len TSRMLS_CC);
                                        break;
+                               case IS_ARRAY:
                                case IS_OBJECT:
                                        zend_update_property(Z_OBJCE_P(options), options, name_str, name_len, value_ptr TSRMLS_CC);
                                        break;
@@ -47,6 +48,7 @@ static void set_option(zval *options, const char *name_str, size_t name_len, int
                                        char *value = estrndup(value_ptr, value_len);
                                        add_assoc_stringl_ex(options, name_str, name_len + 1, value, value_len, 0);
                                        break;
+                               case IS_ARRAY:
                                case IS_OBJECT:
                                        Z_ADDREF_P(value_ptr);
                                        add_assoc_zval_ex(options, name_str, name_len + 1, value_ptr);
@@ -108,6 +110,36 @@ static php_http_message_t *get_request(zval *options TSRMLS_DC)
 
        return request;
 }
+static void set_cookie(zval *options, zval *zcookie_new TSRMLS_DC)
+{
+       HashPosition pos;
+       zval *zcookies_set;
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       php_http_cookie_object_t *obj = zend_object_store_get_object(zcookie_new TSRMLS_CC);
+
+       zcookies_set = get_option(options, ZEND_STRL("cookies") TSRMLS_CC);
+       if (!zcookies_set || Z_TYPE_P(zcookies_set) != IS_ARRAY) {
+               if (zcookies_set) {
+                       zval_ptr_dtor(&zcookies_set);
+               }
+               MAKE_STD_ZVAL(zcookies_set);
+               array_init_size(zcookies_set, zend_hash_num_elements(&obj->list->cookies));
+       } else {
+               SEPARATE_ZVAL(&zcookies_set);
+       }
+
+       FOREACH_HASH_KEY(pos, &obj->list->cookies, key) {
+               Z_ADDREF_P(zcookie_new);
+               if (key.type == HASH_KEY_IS_STRING) {
+                       add_assoc_zval_ex(zcookies_set, key.str, key.len, zcookie_new);
+               } else {
+                       add_index_zval(zcookies_set, key.num, zcookie_new);
+               }
+       }
+
+       set_option(options, ZEND_STRL("cookies"), IS_ARRAY, zcookies_set, 0 TSRMLS_CC);
+       zval_ptr_dtor(&zcookies_set);
+}
 
 php_http_cache_status_t php_http_env_is_response_cached_by_etag(zval *options, const char *header_str, size_t header_len, php_http_message_t *request TSRMLS_DC)
 {
@@ -147,7 +179,7 @@ php_http_cache_status_t php_http_env_is_response_cached_by_etag(zval *options, c
                efree(etag);
        }
 
-       STR_FREE(header);
+       PTR_FREE(header);
        return ret;
 }
 
@@ -190,7 +222,7 @@ php_http_cache_status_t php_http_env_is_response_cached_by_last_modified(zval *o
                }
        }
 
-       STR_FREE(header);
+       PTR_FREE(header);
        return ret;
 }
 
@@ -254,7 +286,7 @@ static STATUS php_http_env_response_send_data(php_http_env_response_t *r, const
                        return SUCCESS;
                }
                chunks_sent = php_http_buffer_chunked_output(&r->buffer, enc_str, enc_len, buf ? chunk : 0, output, r TSRMLS_CC);
-               STR_FREE(enc_str);
+               PTR_FREE(enc_str);
        } else {
                chunks_sent = php_http_buffer_chunked_output(&r->buffer, buf, len, buf ? chunk : 0, output, r TSRMLS_CC);
        }
@@ -303,8 +335,8 @@ void php_http_env_response_dtor(php_http_env_response_t *r)
        }
        php_http_buffer_free(&r->buffer);
        zval_ptr_dtor(&r->options);
-       STR_FREE(r->content.type);
-       STR_FREE(r->content.encoding);
+       PTR_FREE(r->content.type);
+       PTR_FREE(r->content.encoding);
        if (r->content.encoder) {
                php_http_encoding_stream_free(&r->content.encoder);
        }
@@ -370,6 +402,33 @@ static STATUS php_http_env_response_send_head(php_http_env_response_t *r, php_ht
                return ret;
        }
 
+       if ((zoption = get_option(options, ZEND_STRL("cookies") TSRMLS_CC))) {
+               if (Z_TYPE_P(zoption) == IS_ARRAY) {
+                       HashPosition pos;
+                       zval **zcookie;
+
+                       FOREACH_VAL(pos, zoption, zcookie) {
+                               if (Z_TYPE_PP(zcookie) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(zcookie), php_http_cookie_class_entry TSRMLS_CC)) {
+                                       php_http_cookie_object_t *obj = zend_object_store_get_object(*zcookie TSRMLS_CC);
+                                       char *str;
+                                       size_t len;
+
+                                       php_http_cookie_list_to_string(obj->list, &str, &len);
+                                       if (SUCCESS != (ret = r->ops->add_header(r, "Set-Cookie: %s", str))) {
+                                               efree(str);
+                                               break;
+                                       }
+                                       efree(str);
+                               }
+                       }
+               }
+               zval_ptr_dtor(&zoption);
+       }
+
+       if (ret != SUCCESS) {
+               return ret;
+       }
+
        if ((zoption = get_option(options, ZEND_STRL("contentType") TSRMLS_CC))) {
                zval *zoption_copy = php_http_ztyp(IS_STRING, zoption);
 
@@ -824,26 +883,38 @@ typedef struct php_http_env_response_stream_ctx {
        long status_code;
 
        php_stream *stream;
+       php_stream_filter *chunked_filter;
+       php_http_message_t *request;
 
        unsigned started:1;
        unsigned finished:1;
+       unsigned chunked:1;
 } php_http_env_response_stream_ctx_t;
 
 static STATUS php_http_env_response_stream_init(php_http_env_response_t *r, void *init_arg)
 {
        php_http_env_response_stream_ctx_t *ctx;
+       size_t buffer_size = 0x1000;
        TSRMLS_FETCH_FROM_CTX(r->ts);
 
        ctx = ecalloc(1, sizeof(*ctx));
 
        ctx->stream = init_arg;
-       if (SUCCESS != zend_list_addref(ctx->stream->rsrc_id)) {
+       if (!ctx->stream || SUCCESS != zend_list_addref(ctx->stream->rsrc_id)) {
                efree(ctx);
                return FAILURE;
        }
+       php_stream_set_option(ctx->stream, PHP_STREAM_OPTION_WRITE_BUFFER, PHP_STREAM_BUFFER_FULL, &buffer_size);
        zend_hash_init(&ctx->header, 0, NULL, ZVAL_PTR_DTOR, 0);
        php_http_version_init(&ctx->version, 1, 1 TSRMLS_CC);
        ctx->status_code = 200;
+       ctx->chunked = 1;
+       ctx->request = get_request(r->options TSRMLS_CC);
+
+       /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */
+       if (ctx->request && ctx->request->http.version.major == 1 && ctx->request->http.version.minor == 0) {
+               ctx->version.minor = 0;
+       }
 
        r->ctx = ctx;
 
@@ -854,36 +925,77 @@ static void php_http_env_response_stream_dtor(php_http_env_response_t *r)
        php_http_env_response_stream_ctx_t *ctx = r->ctx;
        TSRMLS_FETCH_FROM_CTX(r->ts);
 
+       if (ctx->chunked_filter) {
+               php_stream_filter_free(ctx->chunked_filter TSRMLS_CC);
+       }
        zend_hash_destroy(&ctx->header);
        zend_list_delete(ctx->stream->rsrc_id);
        efree(ctx);
        r->ctx = NULL;
 }
-static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header TSRMLS_DC)
+static void php_http_env_response_stream_header(php_http_env_response_stream_ctx_t *ctx, HashTable *header, php_http_buffer_t *buf TSRMLS_DC)
 {
        HashPosition pos;
        zval **val;
 
-       FOREACH_HASH_VAL(pos, &ctx->header, val) {
+       FOREACH_HASH_VAL(pos, header, val) {
                if (Z_TYPE_PP(val) == IS_ARRAY) {
-                       php_http_env_response_stream_header(ctx, Z_ARRVAL_PP(val) TSRMLS_CC);
+                       php_http_env_response_stream_header(ctx, Z_ARRVAL_PP(val), buf TSRMLS_CC);
                } else {
-                       php_stream_write(ctx->stream, Z_STRVAL_PP(val), Z_STRLEN_PP(val));
-                       php_stream_write_string(ctx->stream, PHP_HTTP_CRLF);
+                       zval *tmp = php_http_ztyp(IS_STRING, *val);
+
+                       if (ctx->chunked) {
+                               /* disable chunked transfer encoding if we've got an explicit content-length */
+                               if (!strncasecmp(Z_STRVAL_P(tmp), "Content-Length:", lenof("Content-Length:"))) {
+                                       ctx->chunked = 0;
+                               }
+                       }
+                       php_http_buffer_append(buf, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
+                       php_http_buffer_appends(buf, PHP_HTTP_CRLF);
+                       zval_ptr_dtor(&tmp);
                }
        }
 }
 static STATUS php_http_env_response_stream_start(php_http_env_response_stream_ctx_t *ctx TSRMLS_DC)
 {
+       php_http_buffer_t header_buf;
+
        if (ctx->started || ctx->finished) {
                return FAILURE;
        }
 
-       php_stream_printf(ctx->stream TSRMLS_CC, "HTTP/%u.%u %ld %s" PHP_HTTP_CRLF, ctx->version.major, ctx->version.minor, ctx->status_code, php_http_env_get_response_status_for_code(ctx->status_code));
-       php_http_env_response_stream_header(ctx, &ctx->header TSRMLS_CC);
-       php_stream_write_string(ctx->stream, PHP_HTTP_CRLF);
-       ctx->started = 1;
-       return SUCCESS;
+       php_http_buffer_init(&header_buf);
+       php_http_buffer_appendf(&header_buf, "HTTP/%u.%u %ld %s" PHP_HTTP_CRLF, ctx->version.major, ctx->version.minor, ctx->status_code, php_http_env_get_response_status_for_code(ctx->status_code));
+
+       /* there are some limitations regarding TE:chunked, see https://tools.ietf.org/html/rfc7230#section-3.3.1 */
+       if (ctx->version.major == 1 && ctx->version.minor == 0) {
+               ctx->chunked = 0;
+       } else if (ctx->status_code == 204 || ctx->status_code/100 == 1) {
+               ctx->chunked = 0;
+       } else if (ctx->request && ctx->status_code/100 == 2 && !strcasecmp(ctx->request->http.info.request.method, "CONNECT")) {
+               ctx->chunked = 0;
+       }
+
+       php_http_env_response_stream_header(ctx, &ctx->header, &header_buf TSRMLS_CC);
+
+       /* enable chunked transfer encoding */
+       if (ctx->chunked) {
+               php_http_buffer_appends(&header_buf, "Transfer-Encoding: chunked" PHP_HTTP_CRLF);
+       }
+       php_http_buffer_appends(&header_buf, PHP_HTTP_CRLF);
+
+       if (header_buf.used == php_stream_write(ctx->stream, header_buf.data, header_buf.used)) {
+               ctx->started = 1;
+       }
+       php_http_buffer_dtor(&header_buf);
+       php_stream_flush(ctx->stream);
+
+       if (ctx->chunked) {
+               ctx->chunked_filter = php_stream_filter_create("http.chunked_encode", NULL, 0 TSRMLS_CC);
+               php_stream_filter_append(&ctx->stream->writefilters, ctx->chunked_filter);
+       }
+
+       return ctx->started ? SUCCESS : FAILURE;
 }
 static long php_http_env_response_stream_get_status(php_http_env_response_t *r)
 {
@@ -1023,19 +1135,25 @@ static STATUS php_http_env_response_stream_flush(php_http_env_response_t *r)
 }
 static STATUS php_http_env_response_stream_finish(php_http_env_response_t *r)
 {
-       php_http_env_response_stream_ctx_t *stream_ctx = r->ctx;
+       php_http_env_response_stream_ctx_t *ctx = r->ctx;
        TSRMLS_FETCH_FROM_CTX(r->ts);
 
-       if (stream_ctx->finished) {
+       if (ctx->finished) {
                return FAILURE;
        }
-       if (!stream_ctx->started) {
-               if (SUCCESS != php_http_env_response_stream_start(stream_ctx TSRMLS_CC)) {
+       if (!ctx->started) {
+               if (SUCCESS != php_http_env_response_stream_start(ctx TSRMLS_CC)) {
                        return FAILURE;
                }
        }
 
-       stream_ctx->finished = 1;
+       php_stream_flush(ctx->stream);
+       if (ctx->chunked && ctx->chunked_filter) {
+               php_stream_filter_flush(ctx->chunked_filter, 1);
+               ctx->chunked_filter = php_stream_filter_remove(ctx->chunked_filter, 1 TSRMLS_CC);
+       }
+
+       ctx->finished = 1;
 
        return SUCCESS;
 }
@@ -1249,6 +1367,46 @@ static PHP_METHOD(HttpEnvResponse, setThrottleRate)
        RETVAL_ZVAL(getThis(), 1, 0);
 }
 
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnvResponse_setCookie, 0, 0, 1)
+       ZEND_ARG_INFO(0, cookie)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpEnvResponse, setCookie)
+{
+       zval *zcookie_new;
+       zend_error_handling zeh;
+       php_http_cookie_list_t *list = NULL;
+
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zcookie_new), invalid_arg, return);
+
+       zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh TSRMLS_CC);
+       switch (Z_TYPE_P(zcookie_new)) {
+       case IS_OBJECT:
+               if (instanceof_function(Z_OBJCE_P(zcookie_new), php_http_cookie_class_entry TSRMLS_CC)) {
+                       Z_ADDREF_P(zcookie_new);
+                       break;
+               }
+               /* no break */
+       case IS_ARRAY:
+               list = php_http_cookie_list_from_struct(NULL, zcookie_new TSRMLS_CC);
+               MAKE_STD_ZVAL(zcookie_new);
+               ZVAL_OBJVAL(zcookie_new, php_http_cookie_object_new_ex(php_http_cookie_class_entry, list, NULL TSRMLS_CC), 0);
+               break;
+
+       default:
+               zcookie_new = php_http_ztyp(IS_STRING, zcookie_new);
+               list = php_http_cookie_list_parse(NULL, Z_STRVAL_P(zcookie_new), Z_STRLEN_P(zcookie_new), 0, NULL TSRMLS_CC);
+               zval_ptr_dtor(&zcookie_new);
+               MAKE_STD_ZVAL(zcookie_new);
+               ZVAL_OBJVAL(zcookie_new, php_http_cookie_object_new_ex(php_http_cookie_class_entry, list, NULL TSRMLS_CC), 0);
+       }
+       zend_restore_error_handling(&zeh TSRMLS_CC);
+
+       set_cookie(getThis(), zcookie_new TSRMLS_CC);
+       zval_ptr_dtor(&zcookie_new);
+
+       RETVAL_ZVAL(getThis(), 1, 0);
+}
+
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpEnvResponse_send, 0, 0, 0)
        ZEND_ARG_INFO(0, stream)
 ZEND_END_ARG_INFO();
@@ -1265,6 +1423,7 @@ static PHP_METHOD(HttpEnvResponse, send)
 #else
                php_end_ob_buffers(1 TSRMLS_CC);
 #endif
+
                if (zstream) {
                        php_http_env_response_t *r;
 
@@ -1293,6 +1452,7 @@ static zend_function_entry php_http_env_response_methods[] = {
        PHP_ME(HttpEnvResponse, __construct,             ai_HttpEnvResponse___construct,             ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
        PHP_ME(HttpEnvResponse, __invoke,                ai_HttpEnvResponse___invoke,                ZEND_ACC_PUBLIC)
        PHP_ME(HttpEnvResponse, setEnvRequest,           ai_HttpEnvResponse_setEnvRequest,           ZEND_ACC_PUBLIC)
+       PHP_ME(HttpEnvResponse, setCookie,               ai_HttpEnvResponse_setCookie,               ZEND_ACC_PUBLIC)
        PHP_ME(HttpEnvResponse, setContentType,          ai_HttpEnvResponse_setContentType,          ZEND_ACC_PUBLIC)
        PHP_ME(HttpEnvResponse, setContentDisposition,   ai_HttpEnvResponse_setContentDisposition,   ZEND_ACC_PUBLIC)
        PHP_ME(HttpEnvResponse, setContentEncoding,      ai_HttpEnvResponse_setContentEncoding,      ZEND_ACC_PUBLIC)
@@ -1323,6 +1483,7 @@ PHP_MINIT_FUNCTION(http_env_response)
        zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CACHE_MISS"), PHP_HTTP_CACHE_MISS TSRMLS_CC);
 
        zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("request"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("cookies"), ZEND_ACC_PROTECTED TSRMLS_CC);
        zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentType"), ZEND_ACC_PROTECTED TSRMLS_CC);
        zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentDisposition"), ZEND_ACC_PROTECTED TSRMLS_CC);
        zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("contentEncoding"), ZEND_ACC_PROTECTED TSRMLS_CC);
index 3a702b23273c144412e131f21ff290ed4184ef90..b67293050cbf34af925988f50a53b6673cda5c80 100644 (file)
@@ -36,6 +36,7 @@ struct php_http_env_response {
        void *ctx;
        php_http_env_response_ops_t *ops;
 
+       php_http_cookie_list_t *cookies;
        php_http_buffer_t *buffer;
        zval *options;
 
index 92a2de4e49a96c31d076a0e1e77d61534f006d85..5a9ecd3bf136c8bfde5f21f931c45032760fae17 100644 (file)
@@ -33,12 +33,7 @@ STATUS php_http_header_parse(const char *header, size_t length, HashTable *heade
        php_http_header_parser_dtor(&ctx);
        php_http_buffer_dtor(&buf);
 
-       if (rs == PHP_HTTP_HEADER_PARSER_STATE_FAILURE) {
-               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Could not parse headers");
-               return FAILURE;
-       }
-       
-       return SUCCESS;
+       return rs == PHP_HTTP_HEADER_PARSER_STATE_FAILURE ? FAILURE : SUCCESS;
 }
 
 void php_http_header_to_callback(HashTable *headers, zend_bool crlf, php_http_pass_format_callback_t cb, void *cb_arg TSRMLS_DC)
index 2526835f90cf929cd096515ccdb050305112e8b6..cea2dbfa67e5a5e09e535327241cfe8f81032fce 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}
 };
@@ -79,8 +83,8 @@ void php_http_header_parser_dtor(php_http_header_parser_t *parser)
 {
        zend_ptr_stack_destroy(&parser->stack);
        php_http_info_dtor(&parser->info);
-       STR_FREE(parser->_key.str);
-       STR_FREE(parser->_val.str);
+       PTR_FREE(parser->_key.str);
+       PTR_FREE(parser->_val.str);
 }
 
 void php_http_header_parser_free(php_http_header_parser_t **parser)
@@ -92,18 +96,38 @@ void php_http_header_parser_free(php_http_header_parser_t **parser)
        }
 }
 
+/* NOTE: 'str' has to be null terminated */
+static void php_http_header_parser_error(size_t valid_len, char *str, size_t len, const char *eol_str TSRMLS_DC)
+{
+       int escaped_len;
+       char *escaped_str;
+
+       escaped_str = php_addcslashes(str, len, &escaped_len, 0, ZEND_STRL("\x0..\x1F\x7F..\xFF") TSRMLS_CC);
+
+       if (valid_len != len && (!eol_str || (str+valid_len) != eol_str)) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected character '\\%03o' at pos %zu of '%.*s'", str[valid_len], valid_len, escaped_len, escaped_str);
+       } else if (eol_str) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected end of line at pos %zu of '%.*s'", eol_str - str, escaped_len, escaped_str);
+       } else {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers: unexpected end of input at pos %zu of '%.*s'", len, escaped_len, escaped_str);
+       }
+
+       efree(escaped_str);
+}
+
 STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg)
 {
        TSRMLS_FETCH_FROM_CTX(parser->ts);
 
        while (buffer->used || !php_http_header_parser_states[php_http_header_parser_state_is(parser)].need_data) {
-#if 0
-               const char *state[] = {"START", "KEY", "VALUE", "HEADER_DONE", "DONE"};
-               fprintf(stderr, "#HP: %s (avail:%zu, num:%d)\n", php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)], buffer->used, headers?zend_hash_num_elements(headers):0);
+#if DBG_PARSER
+               const char *state[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
+               fprintf(stderr, "#HP: %s (avail:%zu, num:%d cleanup:%u)\n", php_http_header_parser_state_is(parser) < 0 ? "FAILURE" : state[php_http_header_parser_state_is(parser)], buffer->used, headers?zend_hash_num_elements(headers):0, flags);
                _dpf(0, buffer->data, buffer->used);
 #endif
                switch (php_http_header_parser_state_pop(parser)) {
                        case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
+                               php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to parse headers");
                                return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
 
                        case PHP_HTTP_HEADER_PARSER_STATE_START: {
@@ -136,13 +160,28 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
                                } else if ((colon = memchr(buffer->data, ':', buffer->used)) && (!eol_str || eol_str > colon)) {
                                        /* header: string */
-                                       parser->_key.str = estrndup(buffer->data, parser->_key.len = colon - buffer->data);
+                                       size_t valid_len;
+
+                                       parser->_key.len = colon - buffer->data;
+                                       parser->_key.str = estrndup(buffer->data, parser->_key.len);
+
+                                       valid_len = strspn(parser->_key.str, PHP_HTTP_HEADER_NAME_CHARS);
+                                       if (valid_len != parser->_key.len) {
+                                               php_http_header_parser_error(valid_len, parser->_key.str, parser->_key.len, eol_str TSRMLS_CC);
+                                               PTR_SET(parser->_key.str, NULL);
+                                               return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+                                       }
                                        while (PHP_HTTP_IS_CTYPE(space, *++colon) && *colon != '\n' && *colon != '\r');
                                        php_http_buffer_cut(buffer, 0, colon - buffer->data);
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
-                               } else {
-                                       /* neither reqeust/response line nor header: string */
+                               } else if (eol_str || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) {
+                                       /* neither reqeust/response line nor 'header:' string, or injected new line or NUL etc. */
+                                       php_http_buffer_fix(buffer);
+                                       php_http_header_parser_error(strspn(buffer->data, PHP_HTTP_HEADER_NAME_CHARS), buffer->data, buffer->used, eol_str TSRMLS_CC);
                                        return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+                               } else {
+                                       /* keep feeding */
+                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY);
                                }
                                break;
                        }
@@ -181,40 +220,43 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b
 
                                if ((eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) {
                                        SET_ADD_VAL(eol_str - buffer->data, eol_len);
-
-                                       if (buffer->used) {
-                                               if (*buffer->data != '\t' && *buffer->data != ' ') {
-                                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
-                                                       break;
-                                               } else {
-                                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
-                                                       break;
-                                               }
-                                       }
-                               }
-
-                               if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) {
+                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
+                               } else if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) {
                                        if (buffer->used) {
                                                SET_ADD_VAL(buffer->used, 0);
                                        }
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
                                } else {
-                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
+                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
                                }
                                break;
                        }
 
                        case PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX:
-                               if (*buffer->data == ' ' || *buffer->data == '\t') {
+                               if (buffer->used && (*buffer->data == ' ' || *buffer->data == '\t')) {
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
-                               } else {
+                               } else if (buffer->used || (flags & PHP_HTTP_HEADER_PARSER_CLEANUP)) {
                                        php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
+                               } else {
+                                       /* keep feeding */
+                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX);
                                }
                                break;
 
                        case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE:
                                if (parser->_key.str && parser->_val.str) {
                                        zval array, **exist;
+                                       size_t valid_len = strlen(parser->_val.str);
+
+                                       /* check for truncation */
+                                       if (valid_len != parser->_val.len) {
+                                               php_http_header_parser_error(valid_len, parser->_val.str, parser->_val.len, NULL TSRMLS_CC);
+
+                                               PTR_SET(parser->_key.str, NULL);
+                                               PTR_SET(parser->_val.str, NULL);
+
+                                               return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+                                       }
 
                                        if (!headers && callback_func) {
                                                callback_func(callback_arg, &headers, NULL TSRMLS_CC);
@@ -231,8 +273,8 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b
                                        parser->_val.str = NULL;
                                }
 
-                               STR_SET(parser->_key.str, NULL);
-                               STR_SET(parser->_val.str, NULL);
+                               PTR_SET(parser->_key.str, NULL);
+                               PTR_SET(parser->_val.str, NULL);
 
                                php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY);
                                break;
@@ -245,6 +287,195 @@ STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_b
        return php_http_header_parser_state_is(parser);
 }
 
+php_http_header_parser_state_t php_http_header_parser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buf, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg)
+{
+       php_http_message_parser_state_t state = PHP_HTTP_MESSAGE_PARSER_STATE_START;
+       TSRMLS_FETCH_FROM_CTX(parser->ts);
+
+       if (!buf->data) {
+               php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
+       }
+       while (1) {
+               size_t justread = 0;
+#if DBG_PARSER
+               const char *states[] = {"START", "KEY", "VALUE", "VALUE_EX", "HEADER_DONE", "DONE"};
+               fprintf(stderr, "#SHP: %s (f:%u)\n", states[state], flags);
+#endif
+               /* resize if needed */
+               if (buf->free < 0x1000) {
+                       php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
+               }
+               switch (state) {
+               case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
+               case PHP_HTTP_HEADER_PARSER_STATE_DONE:
+                       return state;
+
+               default:
+                       /* read line */
+                       php_stream_get_line(s, buf->data + buf->used, buf->free, &justread);
+                       /* if we fail reading a whole line, try a single char */
+                       if (!justread) {
+                               int c = php_stream_getc(s);
+
+                               if (c != EOF) {
+                                       char s[1] = {c};
+                                       justread = php_http_buffer_append(buf, s, 1);
+                               }
+                       }
+                       php_http_buffer_account(buf, justread);
+               }
+
+               if (justread) {
+                       state = php_http_header_parser_parse(parser, buf, flags, headers, callback_func, callback_arg);
+               } else if (php_stream_eof(s)) {
+                       return php_http_header_parser_parse(parser, buf, flags | PHP_HTTP_HEADER_PARSER_CLEANUP, headers, callback_func, callback_arg);
+               } else  {
+                       return state;
+               }
+       }
+
+       return PHP_HTTP_HEADER_PARSER_STATE_DONE;
+}
+
+zend_class_entry *php_http_header_parser_class_entry;
+static zend_object_handlers php_http_header_parser_object_handlers;
+
+zend_object_value php_http_header_parser_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_header_parser_object_new_ex(ce, NULL, NULL TSRMLS_CC);
+}
+
+zend_object_value php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser, php_http_header_parser_object_t **ptr TSRMLS_DC)
+{
+       php_http_header_parser_object_t *o;
+
+       o = ecalloc(1, sizeof(php_http_header_parser_object_t));
+       zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       if (ptr) {
+               *ptr = o;
+       }
+
+       if (parser) {
+               o->parser = parser;
+       } else {
+               o->parser = php_http_header_parser_init(NULL TSRMLS_CC);
+       }
+       o->buffer = php_http_buffer_new();
+
+       o->zv.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_header_parser_object_free, NULL TSRMLS_CC);
+       o->zv.handlers = &php_http_header_parser_object_handlers;
+
+       return o->zv;
+}
+
+void php_http_header_parser_object_free(void *object TSRMLS_DC)
+{
+       php_http_header_parser_object_t *o = (php_http_header_parser_object_t *) object;
+
+       if (o->parser) {
+               php_http_header_parser_free(&o->parser);
+       }
+       if (o->buffer) {
+               php_http_buffer_free(&o->buffer);
+       }
+       zend_object_std_dtor((zend_object *) o TSRMLS_CC);
+       efree(o);
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_getState, 0, 0, 0)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpHeaderParser, getState)
+{
+       php_http_header_parser_object_t *parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       zend_parse_parameters_none();
+       /* always return the real state */
+       RETVAL_LONG(php_http_header_parser_state_is(parser_obj->parser));
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_parse, 0, 0, 3)
+       ZEND_ARG_INFO(0, data)
+       ZEND_ARG_INFO(0, flags)
+       ZEND_ARG_ARRAY_INFO(1, headers, 1)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpHeaderParser, parse)
+{
+       php_http_header_parser_object_t *parser_obj;
+       zval *zmsg;
+       char *data_str;
+       int data_len;
+       long flags;
+
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "slz", &data_str, &data_len, &flags, &zmsg), invalid_arg, return);
+
+       if (Z_TYPE_P(zmsg) != IS_ARRAY) {
+               zval_dtor(zmsg);
+               array_init(zmsg);
+       }
+       parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+       php_http_buffer_append(parser_obj->buffer, data_str, data_len);
+       RETVAL_LONG(php_http_header_parser_parse(parser_obj->parser, parser_obj->buffer, flags, Z_ARRVAL_P(zmsg), NULL, NULL));
+}
+
+ZEND_BEGIN_ARG_INFO_EX(ai_HttpHeaderParser_stream, 0, 0, 3)
+       ZEND_ARG_INFO(0, stream)
+       ZEND_ARG_INFO(0, flags)
+       ZEND_ARG_ARRAY_INFO(1, headers, 1)
+ZEND_END_ARG_INFO();
+static PHP_METHOD(HttpHeaderParser, stream)
+{
+       php_http_header_parser_object_t *parser_obj;
+       zend_error_handling zeh;
+       zval *zmsg, *zstream;
+       php_stream *s;
+       long flags;
+
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rlz", &zstream, &flags, &zmsg), invalid_arg, return);
+
+       zend_replace_error_handling(EH_THROW, php_http_exception_unexpected_val_class_entry, &zeh TSRMLS_CC);
+       php_stream_from_zval(s, &zstream);
+       zend_restore_error_handling(&zeh TSRMLS_CC);
+
+       if (Z_TYPE_P(zmsg) != IS_ARRAY) {
+               zval_dtor(zmsg);
+               array_init(zmsg);
+       }
+       parser_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+       RETVAL_LONG(php_http_header_parser_parse_stream(parser_obj->parser, parser_obj->buffer, s, flags, Z_ARRVAL_P(zmsg), NULL, NULL));
+}
+
+static zend_function_entry php_http_header_parser_methods[] = {
+               PHP_ME(HttpHeaderParser, getState, ai_HttpHeaderParser_getState, ZEND_ACC_PUBLIC)
+               PHP_ME(HttpHeaderParser, parse, ai_HttpHeaderParser_parse, ZEND_ACC_PUBLIC)
+               PHP_ME(HttpHeaderParser, stream, ai_HttpHeaderParser_stream, ZEND_ACC_PUBLIC)
+               {NULL, NULL, NULL}
+};
+
+PHP_MINIT_FUNCTION(http_header_parser)
+{
+       zend_class_entry ce;
+
+       INIT_NS_CLASS_ENTRY(ce, "http\\Header", "Parser", php_http_header_parser_methods);
+       php_http_header_parser_class_entry = zend_register_internal_class(&ce TSRMLS_CC);
+       memcpy(&php_http_header_parser_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_header_parser_class_entry->create_object = php_http_header_parser_object_new;
+       php_http_header_parser_object_handlers.clone_obj = NULL;
+
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("CLEANUP"), PHP_HTTP_HEADER_PARSER_CLEANUP TSRMLS_CC);
+
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_FAILURE"), PHP_HTTP_HEADER_PARSER_STATE_FAILURE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_START"), PHP_HTTP_HEADER_PARSER_STATE_START TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_KEY"), PHP_HTTP_HEADER_PARSER_STATE_KEY TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE"), PHP_HTTP_HEADER_PARSER_STATE_VALUE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_VALUE_EX"), PHP_HTTP_HEADER_PARSER_STATE_VALUE_EX TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_HEADER_DONE"), PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_header_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_HEADER_PARSER_STATE_DONE TSRMLS_CC);
+
+       return SUCCESS;
+}
+
 /*
  * Local variables:
  * tab-width: 4
index 4c60f6ef634b4b262eb9228855588816b19419d2..ed9ecaf1af5b3f9db7769cc51f54da99da6bcbea 100644 (file)
@@ -50,6 +50,22 @@ PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_pop(php
 PHP_HTTP_API void php_http_header_parser_dtor(php_http_header_parser_t *parser);
 PHP_HTTP_API void php_http_header_parser_free(php_http_header_parser_t **parser);
 PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer_t *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg);
+PHP_HTTP_API php_http_header_parser_state_t php_http_headerparser_parse_stream(php_http_header_parser_t *parser, php_http_buffer_t *buffer, php_stream *s, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg);
+
+typedef struct php_http_header_parser_object {
+       zend_object zo;
+       zend_object_value zv;
+       php_http_buffer_t *buffer;
+       php_http_header_parser_t *parser;
+} php_http_header_parser_object_t;
+
+PHP_HTTP_API zend_class_entry *php_http_header_parser_class_entry;
+
+PHP_MINIT_FUNCTION(http_header_parser);
+
+zend_object_value php_http_header_parser_object_new(zend_class_entry *ce TSRMLS_DC);
+zend_object_value php_http_header_parser_object_new_ex(zend_class_entry *ce, php_http_header_parser_t *parser, php_http_header_parser_object_t **ptr TSRMLS_DC);
+void php_http_header_parser_object_free(void *object TSRMLS_DC);
 
 #endif /* PHP_HTTP_HEADER_PARSER_H */
 
index 10ea0a3c0ad7a114bb58ec3a78de6563115633d4..905091979a70ac08159ac451bb0c561e92181e18 100644 (file)
@@ -27,12 +27,12 @@ void php_http_info_dtor(php_http_info_t *i)
 {
        switch (i->type) {
                case PHP_HTTP_REQUEST:
-                       STR_SET(PHP_HTTP_INFO(i).request.method, NULL);
-                       STR_SET(PHP_HTTP_INFO(i).request.url, NULL);
+                       PTR_SET(PHP_HTTP_INFO(i).request.method, NULL);
+                       PTR_SET(PHP_HTTP_INFO(i).request.url, NULL);
                        break;
                
                case PHP_HTTP_RESPONSE:
-                       STR_SET(PHP_HTTP_INFO(i).response.status, NULL);
+                       PTR_SET(PHP_HTTP_INFO(i).response.status, NULL);
                        break;
                
                default:
@@ -65,15 +65,15 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head
        }
        
        /* there must be HTTP/1.x in the line */
-       if (!(http = php_http_locate_str(pre_header, end - pre_header, "HTTP/1.", lenof("HTTP/1.")))) {
+       if (!(http = php_http_locate_str(pre_header, end - pre_header, "HTTP/", lenof("HTTP/")))) {
                return NULL;
        }
        
        info = php_http_info_init(info TSRMLS_CC);
 
-       /* and nothing than SPACE or NUL after HTTP/1.x */
+       /* and nothing than SPACE or NUL after HTTP/X.x */
        if (!php_http_version_parse(&info->http.version, http TSRMLS_CC)
-       ||      (http[lenof("HTTP/1.1")] && (!PHP_HTTP_IS_CTYPE(space, http[lenof("HTTP/1.1")])))) {
+       ||      (http[lenof("HTTP/X.x")] && (!PHP_HTTP_IS_CTYPE(space, http[lenof("HTTP/X.x")])))) {
                if (free_info) {
                        php_http_info_free(&info);
                }
@@ -90,13 +90,22 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head
 
        /* is response */
        if (pre_header == http) {
-               char *status = NULL;
-               const char *code = http + sizeof("HTTP/1.1");
+               const char *status = NULL, *code = http + sizeof("HTTP/X.x");
                
                info->type = PHP_HTTP_RESPONSE;
                while (' ' == *code) ++code;
                if (code && end > code) {
-                       PHP_HTTP_INFO(info).response.code = strtol(code, &status, 10);
+                       /* rfc7230#3.1.2 The status-code element is a 3-digit integer code */
+                       PHP_HTTP_INFO(info).response.code = 100*(*code++ - '0');
+                       PHP_HTTP_INFO(info).response.code += 10*(*code++ - '0');
+                       PHP_HTTP_INFO(info).response.code +=     *code++ - '0';
+                       if (PHP_HTTP_INFO(info).response.code < 100 || PHP_HTTP_INFO(info).response.code > 599) {
+                               if (free_info) {
+                                       php_http_info_free(&info);
+                               }
+                               return NULL;
+                       }
+                       status = code;
                } else {
                        PHP_HTTP_INFO(info).response.code = 0;
                }
@@ -111,18 +120,27 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head
        }
        
        /* is request */
-       else if (*(http - 1) == ' ' && (!http[lenof("HTTP/1.x")] || http[lenof("HTTP/1.x")] == '\r' || http[lenof("HTTP/1.x")] == '\n')) {
+       else if (*(http - 1) == ' ' && (!http[lenof("HTTP/X.x")] || http[lenof("HTTP/X.x")] == '\r' || http[lenof("HTTP/X.x")] == '\n')) {
                const char *url = strchr(pre_header, ' ');
                
                info->type = PHP_HTTP_REQUEST;
                if (url && http > url) {
-                       PHP_HTTP_INFO(info).request.method = estrndup(pre_header, url - pre_header);
+                       size_t url_len = url - pre_header;
+
+                       PHP_HTTP_INFO(info).request.method = estrndup(pre_header, url_len);
+
                        while (' ' == *url) ++url;
                        while (' ' == *(http-1)) --http;
+
                        if (http > url) {
-                               PHP_HTTP_INFO(info).request.url = estrndup(url, http - url);
+                               /* CONNECT presents an authority only */
+                               if (strcasecmp(PHP_HTTP_INFO(info).request.method, "CONNECT")) {
+                                       PHP_HTTP_INFO(info).request.url = php_http_url_parse(url, http - url, ~0 TSRMLS_CC);
+                               } else {
+                                       PHP_HTTP_INFO(info).request.url = php_http_url_parse_authority(url, http - url, ~0 TSRMLS_CC);
+                               }
                        } else {
-                               STR_SET(PHP_HTTP_INFO(info).request.method, NULL);
+                               PTR_SET(PHP_HTTP_INFO(info).request.method, NULL);
                                return NULL;
                        }
                } else {
@@ -133,7 +151,7 @@ php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_head
                return info;
        }
 
-       /* some darn header containing HTTP/1.x */
+       /* some darn header containing HTTP/X.x */
        else {
                if (free_info) {
                        php_http_info_free(&info);
index afd747cce19bf9236b5f9ec23ca73e7bbdfb38bb..4f02908446b6c0c430eb6bb39269f45967500b00 100644 (file)
 #define PHP_HTTP_INFO_H
 
 #include "php_http_version.h"
+#include "php_http_url.h"
 
-#define PHP_HTTP_INFO_REQUEST_FMT_ARGS(_http_ptr, eol) "%s %s HTTP/%u.%u" eol, \
+#define PHP_HTTP_INFO_REQUEST_FMT_ARGS(_http_ptr, tmp, eol) "%s %s HTTP/%u.%u" eol, \
                                (_http_ptr)->info.request.method?(_http_ptr)->info.request.method:"UNKNOWN", \
-                               (_http_ptr)->info.request.url?(_http_ptr)->info.request.url:"/", \
+                               (_http_ptr)->info.request.method&&!strcasecmp((_http_ptr)->info.request.method,"CONNECT")?( \
+                               (_http_ptr)->info.request.url?php_http_url_authority_to_string((_http_ptr)->info.request.url, &(tmp), NULL):"0"):( \
+                               (_http_ptr)->info.request.url?php_http_url_to_string((_http_ptr)->info.request.url, &(tmp), NULL, 0):"/"), \
                                (_http_ptr)->version.major||(_http_ptr)->version.major?(_http_ptr)->version.major:1, \
                                (_http_ptr)->version.major||(_http_ptr)->version.minor?(_http_ptr)->version.minor:1
 
-#define PHP_HTTP_INFO_RESPONSE_FMT_ARGS(_http_ptr, eol) "HTTP/%u.%u %d%s%s" eol, \
+#define PHP_HTTP_INFO_RESPONSE_FMT_ARGS(_http_ptr, tmp, eol) "HTTP/%u.%u %d%s%s" eol, \
                                (_http_ptr)->version.major||(_http_ptr)->version.major?(_http_ptr)->version.major:1, \
                                (_http_ptr)->version.major||(_http_ptr)->version.minor?(_http_ptr)->version.minor:1, \
                                (_http_ptr)->info.response.code?(_http_ptr)->info.response.code:200, \
@@ -31,7 +34,7 @@
 typedef struct php_http_info_data {
        union {
                /* GET /foo/bar */
-               struct { char *method; char *url; } request;
+               struct { char *method; php_http_url_t *url; } request;
                /* 200 Ok */
                struct { unsigned code; char *status; } response;
        } info;
index c96531748c166ad8c86407e7d4c278650055fcad..b7b500b313bbea8b1308c194e14e1e653cc0b33c 100644 (file)
@@ -69,7 +69,7 @@ php_http_message_t *php_http_message_init_env(php_http_message_t *message, php_h
                                message->http.info.request.method = estrdup(Z_STRVAL_P(sval));
                        }
                        if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_URI"), 1 TSRMLS_CC))) {
-                               message->http.info.request.url = estrdup(Z_STRVAL_P(sval));
+                               message->http.info.request.url = php_http_url_parse(Z_STRVAL_P(sval), Z_STRLEN_P(sval), ~0 TSRMLS_CC);
                        }
                        
                        php_http_env_get_request_headers(&message->hdrs TSRMLS_CC);
@@ -253,12 +253,12 @@ void php_http_message_set_type(php_http_message_t *message, php_http_message_typ
                /* free request info */
                switch (message->type) {
                        case PHP_HTTP_REQUEST:
-                               STR_FREE(message->http.info.request.method);
-                               STR_FREE(message->http.info.request.url);
+                               PTR_FREE(message->http.info.request.method);
+                               PTR_FREE(message->http.info.request.url);
                                break;
                        
                        case PHP_HTTP_RESPONSE:
-                               STR_FREE(message->http.info.response.status);
+                               PTR_FREE(message->http.info.response.status);
                                break;
                        
                        default:
@@ -276,13 +276,13 @@ void php_http_message_set_info(php_http_message_t *message, php_http_info_t *inf
        message->http.version = info->http.version;
        switch (message->type) {
                case PHP_HTTP_REQUEST:
-                       STR_SET(PHP_HTTP_INFO(message).request.url, PHP_HTTP_INFO(info).request.url ? estrdup(PHP_HTTP_INFO(info).request.url) : NULL);
-                       STR_SET(PHP_HTTP_INFO(message).request.method, PHP_HTTP_INFO(info).request.method ? estrdup(PHP_HTTP_INFO(info).request.method) : NULL);
+                       PTR_SET(PHP_HTTP_INFO(message).request.url, PHP_HTTP_INFO(info).request.url ? php_http_url_copy(PHP_HTTP_INFO(info).request.url, 0) : NULL);
+                       PTR_SET(PHP_HTTP_INFO(message).request.method, PHP_HTTP_INFO(info).request.method ? estrdup(PHP_HTTP_INFO(info).request.method) : NULL);
                        break;
                
                case PHP_HTTP_RESPONSE:
                        PHP_HTTP_INFO(message).response.code = PHP_HTTP_INFO(info).response.code;
-                       STR_SET(PHP_HTTP_INFO(message).response.status, PHP_HTTP_INFO(info).response.status ? estrdup(PHP_HTTP_INFO(info).response.status) : NULL);
+                       PTR_SET(PHP_HTTP_INFO(message).response.status, PHP_HTTP_INFO(info).response.status ? estrdup(PHP_HTTP_INFO(info).response.status) : NULL);
                        break;
                
                default:
@@ -297,6 +297,9 @@ void php_http_message_update_headers(php_http_message_t *msg)
 
        if (php_http_message_body_stream(msg->body)->readfilters.head) {
                /* if a read stream filter is attached to the body the caller must also care for the headers */
+       } else if ((h = php_http_message_header(msg, ZEND_STRL("Content-Range"), 0))) {
+               /* don't mess around with a Content-Range message */
+               zval_ptr_dtor(&h);
        } else if ((size = php_http_message_body_size(msg->body))) {
                MAKE_STD_ZVAL(h);
                ZVAL_LONG(h, size);
@@ -324,6 +327,7 @@ void php_http_message_update_headers(php_http_message_t *msg)
 
                zval_ptr_dtor(&h);
                if (Z_LVAL_P(h_cpy)) {
+                       /* body->size == 0, so get rid of old Content-Length */
                        zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length"));
                }
                zval_ptr_dtor(&h_cpy);
@@ -332,15 +336,18 @@ void php_http_message_update_headers(php_http_message_t *msg)
 
 static void message_headers(php_http_message_t *msg, php_http_buffer_t *str)
 {
+       char *tmp = NULL;
        TSRMLS_FETCH_FROM_CTX(msg->ts);
 
        switch (msg->type) {
                case PHP_HTTP_REQUEST:
-                       php_http_buffer_appendf(str, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&msg->http, PHP_HTTP_CRLF));
+                       php_http_buffer_appendf(str, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&msg->http, tmp, PHP_HTTP_CRLF));
+                       PTR_FREE(tmp);
                        break;
 
                case PHP_HTTP_RESPONSE:
-                       php_http_buffer_appendf(str, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&msg->http, PHP_HTTP_CRLF));
+                       php_http_buffer_appendf(str, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&msg->http, tmp, PHP_HTTP_CRLF));
+                       PTR_FREE(tmp);
                        break;
 
                default:
@@ -496,12 +503,12 @@ void php_http_message_dtor(php_http_message_t *message)
                
                switch (message->type) {
                        case PHP_HTTP_REQUEST:
-                               STR_SET(message->http.info.request.method, NULL);
-                               STR_SET(message->http.info.request.url, NULL);
+                               PTR_SET(message->http.info.request.method, NULL);
+                               PTR_SET(message->http.info.request.url, NULL);
                                break;
                        
                        case PHP_HTTP_RESPONSE:
-                               STR_SET(message->http.info.response.status, NULL);
+                               PTR_SET(message->http.info.response.status, NULL);
                                break;
                        
                        default:
@@ -561,22 +568,23 @@ static void php_http_message_object_prophandler_get_request_method(php_http_mess
 static void php_http_message_object_prophandler_set_request_method(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
        if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message)) {
                zval *cpy = php_http_ztyp(IS_STRING, value);
-               STR_SET(obj->message->http.info.request.method, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)));
+               PTR_SET(obj->message->http.info.request.method, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)));
                zval_ptr_dtor(&cpy);
        }
 }
 static void php_http_message_object_prophandler_get_request_url(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
-       if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message) && obj->message->http.info.request.url) {
-               RETVAL_STRING(obj->message->http.info.request.url, 1);
+       char *url_str;
+       size_t url_len;
+
+       if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message) && obj->message->http.info.request.url && php_http_url_to_string(obj->message->http.info.request.url, &url_str, &url_len, 0)) {
+               RETVAL_STRINGL(url_str, url_len, 0);
        } else {
                RETVAL_NULL();
        }
 }
 static void php_http_message_object_prophandler_set_request_url(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
        if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message)) {
-               zval *cpy = php_http_ztyp(IS_STRING, value);
-               STR_SET(obj->message->http.info.request.url, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)));
-               zval_ptr_dtor(&cpy);
+               PTR_SET(obj->message->http.info.request.url, php_http_url_from_zval(value, ~0 TSRMLS_CC));
        }
 }
 static void php_http_message_object_prophandler_get_response_status(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
@@ -589,7 +597,7 @@ static void php_http_message_object_prophandler_get_response_status(php_http_mes
 static void php_http_message_object_prophandler_set_response_status(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
        if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) {
                zval *cpy = php_http_ztyp(IS_STRING, value);
-               STR_SET(obj->message->http.info.response.status, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)));
+               PTR_SET(obj->message->http.info.response.status, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)));
                zval_ptr_dtor(&cpy);
        }
 }
@@ -604,7 +612,7 @@ static void php_http_message_object_prophandler_set_response_code(php_http_messa
        if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) {
                zval *cpy = php_http_ztyp(IS_LONG, value);
                obj->message->http.info.response.code = Z_LVAL_P(cpy);
-               STR_SET(obj->message->http.info.response.status, estrdup(php_http_env_get_response_status_for_code(obj->message->http.info.response.code)));
+               PTR_SET(obj->message->http.info.response.status, estrdup(php_http_env_get_response_status_for_code(obj->message->http.info.response.code)));
                zval_ptr_dtor(&cpy);
        }
 }
@@ -939,8 +947,8 @@ static HashTable *php_http_message_object_get_props(zval *object TSRMLS_DC)
        php_http_message_object_t *obj = zend_object_store_get_object(object TSRMLS_CC);
        HashTable *props = zend_get_std_object_handlers()->get_properties(object TSRMLS_CC);
        zval array, *parent, *body;
-       char *version;
-       int verlen;
+       char *ver_str, *url_str = NULL;
+       size_t ver_len, url_len = 0;
 
        PHP_HTTP_MESSAGE_OBJECT_INIT(obj);
        INIT_PZVAL_ARRAY(&array, props);
@@ -964,15 +972,21 @@ static HashTable *php_http_message_object_get_props(zval *object TSRMLS_DC)
        } while(0)
 
        ASSOC_PROP(long, "type", obj->message->type);
-       verlen = spprintf(&version, 0, "%u.%u", obj->message->http.version.major, obj->message->http.version.minor);
-       ASSOC_STRINGL_EX("httpVersion", version, verlen, 0);
+       ver_len = spprintf(&ver_str, 0, "%u.%u", obj->message->http.version.major, obj->message->http.version.minor);
+       ASSOC_STRINGL_EX("httpVersion", ver_str, ver_len, 0);
 
        switch (obj->message->type) {
                case PHP_HTTP_REQUEST:
                        ASSOC_PROP(long, "responseCode", 0);
                        ASSOC_STRINGL("responseStatus", "", 0);
                        ASSOC_STRING("requestMethod", STR_PTR(obj->message->http.info.request.method));
-                       ASSOC_STRING("requestUrl", STR_PTR(obj->message->http.info.request.url));
+                       if (obj->message->http.info.request.url) {
+                               php_http_url_to_string(obj->message->http.info.request.url, &url_str, &url_len, 0);
+                               ASSOC_STRINGL_EX("requestUrl", url_str, url_len, 0);
+                       } else {
+                               ASSOC_STRINGL("requestUrl", "", 0);
+                       }
+
                        break;
 
                case PHP_HTTP_RESPONSE:
@@ -1151,17 +1165,19 @@ static PHP_METHOD(HttpMessage, getHeader)
                        if (!header_ce) {
                                RETURN_ZVAL(header, 1, 1);
                        } else if (instanceof_function(header_ce, php_http_header_class_entry TSRMLS_CC)) {
+                               php_http_object_method_t cb;
                                zval *header_name, **argv[2];
 
                                MAKE_STD_ZVAL(header_name);
                                ZVAL_STRINGL(header_name, header_str, header_len, 1);
-                               Z_ADDREF_P(header);
 
                                argv[0] = &header_name;
                                argv[1] = &header;
 
                                object_init_ex(return_value, header_ce);
-                               php_http_method_call(return_value, ZEND_STRL("__construct"), 2, argv, NULL TSRMLS_CC);
+                               php_http_object_method_init(&cb, return_value, ZEND_STRL("__construct") TSRMLS_CC);
+                               php_http_object_method_call(&cb, return_value, NULL, 2, argv TSRMLS_CC);
+                               php_http_object_method_dtor(&cb);
 
                                zval_ptr_dtor(&header_name);
                                zval_ptr_dtor(&header);
@@ -1320,16 +1336,19 @@ ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpMessage, getInfo)
 {
        if (SUCCESS == zend_parse_parameters_none()) {
+               char *tmp = NULL;
                php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
 
                PHP_HTTP_MESSAGE_OBJECT_INIT(obj);
 
                switch (obj->message->type) {
                        case PHP_HTTP_REQUEST:
-                               Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&obj->message->http, ""));
+                               Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&obj->message->http, tmp, ""));
+                               PTR_FREE(tmp);
                                break;
                        case PHP_HTTP_RESPONSE:
-                               Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&obj->message->http, ""));
+                               Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&obj->message->http, tmp, ""));
+                               PTR_FREE(tmp);
                                break;
                        default:
                                RETURN_NULL();
@@ -1449,7 +1468,7 @@ static PHP_METHOD(HttpMessage, setResponseCode)
        }
 
        obj->message->http.info.response.code = code;
-       STR_SET(obj->message->http.info.response.status, estrdup(php_http_env_get_response_status_for_code(code)));
+       PTR_SET(obj->message->http.info.response.status, estrdup(php_http_env_get_response_status_for_code(code)));
 
        RETVAL_ZVAL(getThis(), 1, 0);
 }
@@ -1494,7 +1513,7 @@ static PHP_METHOD(HttpMessage, setResponseStatus)
                php_http_throw(bad_method_call, "http\\Message is not of type response", NULL);
        }
 
-       STR_SET(obj->message->http.info.response.status, estrndup(status, status_len));
+       PTR_SET(obj->message->http.info.response.status, estrndup(status, status_len));
        RETVAL_ZVAL(getThis(), 1, 0);
 }
 
@@ -1545,7 +1564,7 @@ static PHP_METHOD(HttpMessage, setRequestMethod)
                return;
        }
 
-       STR_SET(obj->message->http.info.request.method, estrndup(method, method_len));
+       PTR_SET(obj->message->http.info.request.method, estrndup(method, method_len));
        RETVAL_ZVAL(getThis(), 1, 0);
 }
 
@@ -1564,7 +1583,11 @@ static PHP_METHOD(HttpMessage, getRequestUrl)
                }
 
                if (obj->message->http.info.request.url) {
-                       RETURN_STRING(obj->message->http.info.request.url, 1);
+                       char *url_str;
+                       size_t url_len;
+
+                       php_http_url_to_string(obj->message->http.info.request.url, &url_str, &url_len, 0);
+                       RETURN_STRINGL(url_str, url_len, 0);
                } else {
                        RETURN_EMPTY_STRING();
                }
@@ -1576,11 +1599,12 @@ ZEND_BEGIN_ARG_INFO_EX(ai_HttpMessage_setRequestUrl, 0, 0, 1)
 ZEND_END_ARG_INFO();
 static PHP_METHOD(HttpMessage, setRequestUrl)
 {
-       char *url_str;
-       int url_len;
+       zval *zurl;
+       php_http_url_t *url;
        php_http_message_object_t *obj;
+       zend_error_handling zeh;
 
-       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &url_str, &url_len), invalid_arg, return);
+       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zurl), invalid_arg, return);
 
        obj = zend_object_store_get_object(getThis() TSRMLS_CC);
 
@@ -1591,12 +1615,17 @@ static PHP_METHOD(HttpMessage, setRequestUrl)
                return;
        }
 
-       if (url_len < 1) {
+       zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh TSRMLS_CC);
+       url = php_http_url_from_zval(zurl, ~0 TSRMLS_CC);
+       zend_restore_error_handling(&zeh TSRMLS_CC);
+
+       if (php_http_url_is_empty(url)) {
+               php_http_url_free(&url);
                php_http_throw(invalid_arg, "Cannot set http\\Message's request url to an empty string", NULL);
-               return;
+       } else {
+               PTR_SET(obj->message->http.info.request.url, url);
        }
 
-       STR_SET(obj->message->http.info.request.url, estrndup(url_str, url_len));
        RETVAL_ZVAL(getThis(), 1, 0);
 }
 
@@ -1648,6 +1677,16 @@ static PHP_METHOD(HttpMessage, toString)
        RETURN_EMPTY_STRING();
 }
 
+#ifdef ZTS
+static size_t write_to_stream(void *s, const char *str, size_t len)
+{
+       TSRMLS_FETCH();
+       return php_stream_write(s, str, len);
+}
+#else
+#      define write_to_stream (php_http_pass_callback_t)_php_stream_write
+#endif
+
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpMessage_toStream, 0, 0, 1)
        ZEND_ARG_INFO(0, stream)
 ZEND_END_ARG_INFO();
@@ -1662,7 +1701,7 @@ static PHP_METHOD(HttpMessage, toStream)
                PHP_HTTP_MESSAGE_OBJECT_INIT(obj);
 
                php_stream_from_zval(s, &zstream);
-               php_http_message_to_callback(obj->message, (php_http_pass_callback_t) _php_stream_write, s);
+               php_http_message_to_callback(obj->message, write_to_stream, s);
        }
 }
 
@@ -1831,7 +1870,7 @@ static PHP_METHOD(HttpMessage, splitMultipartBody)
 
        php_http_expect(msg = php_http_message_body_split(obj->message->body, boundary), bad_message, return);
 
-       STR_FREE(boundary);
+       PTR_FREE(boundary);
 
        RETURN_OBJVAL(php_http_message_object_new_ex(php_http_message_class_entry, msg, NULL TSRMLS_CC), 0);
 }
index 4be45ef0ce0b7d26d688e4811b5e271db7036d58..fa9155110d776d37a0e1e26bed4ec5ef627acee6 100644 (file)
@@ -100,7 +100,7 @@ void php_http_message_body_free(php_http_message_body_t **body_ptr)
                        TSRMLS_FETCH_FROM_CTX(body->ts);
                        /* NOFIXME: shows leakinfo in DEBUG mode */
                        zend_list_delete(body->stream_id);
-                       STR_FREE(body->boundary);
+                       PTR_FREE(body->boundary);
                        efree(body);
                }
                *body_ptr = NULL;
@@ -128,25 +128,29 @@ const char *php_http_message_body_boundary(php_http_message_body_t *body)
 
 char *php_http_message_body_etag(php_http_message_body_t *body)
 {
-       const php_stream_statbuf *ssb = php_http_message_body_stat(body);
+       php_http_etag_t *etag;
+       php_stream *s = php_http_message_body_stream(body);
        TSRMLS_FETCH_FROM_CTX(body->ts);
 
        /* real file or temp buffer ? */
-       if (ssb && ssb->sb.st_mtime) {
-               char *etag;
+       if (s->ops != &php_stream_temp_ops && s->ops != &php_stream_memory_ops) {
+               php_stream_stat(php_http_message_body_stream(body), &body->ssb);
 
-               spprintf(&etag, 0, "%lx-%lx-%lx", ssb->sb.st_ino, ssb->sb.st_mtime, ssb->sb.st_size);
-               return etag;
-       } else {
-               php_http_etag_t *etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode TSRMLS_CC);
+               if (body->ssb.sb.st_mtime) {
+                       char *etag;
 
-               if (etag) {
-                       php_http_message_body_to_callback(body, (php_http_pass_callback_t) php_http_etag_update, etag, 0, 0);
-                       return php_http_etag_finish(etag);
-               } else {
-                       return NULL;
+                       spprintf(&etag, 0, "%lx-%lx-%lx", body->ssb.sb.st_ino, body->ssb.sb.st_mtime, body->ssb.sb.st_size);
+                       return etag;
                }
        }
+
+       /* content based */
+       if ((etag = php_http_etag_init(PHP_HTTP_G->env.etag_mode TSRMLS_CC))) {
+               php_http_message_body_to_callback(body, (php_http_pass_callback_t) php_http_etag_update, etag, 0, 0);
+               return php_http_etag_finish(etag);
+       }
+
+       return NULL;
 }
 
 void php_http_message_body_to_string(php_http_message_body_t *body, char **buf, size_t *len, off_t offset, size_t forlen)
@@ -557,7 +561,7 @@ php_http_message_t *php_http_message_body_split(php_http_message_body_t *body, c
        php_http_buffer_free(&tmp);
        php_http_message_parser_free(&arg.parser);
        php_http_buffer_dtor(&arg.buf);
-       STR_FREE(arg.boundary_str);
+       PTR_FREE(arg.boundary_str);
 
        return msg;
 }
index e73360eed1920ffb57a740d32fa629bca7d41ad4..3b55efbab7b295a9cd210fa57cb409769a3a97e6 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";
@@ -123,17 +124,30 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me
        if (!buf->data) {
                php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
        }
-       while (!php_stream_eof(s)) {
+       while (1) {
                size_t justread = 0;
 #if DBG_PARSER
                fprintf(stderr, "#SP: %s (f:%u)\n", php_http_message_parser_state_name(state), flags);
 #endif
+               /* resize if needed */
+               if (buf->free < 0x1000) {
+                       php_http_buffer_resize_ex(buf, 0x1000, 1, 0);
+               }
                switch (state) {
                        case PHP_HTTP_MESSAGE_PARSER_STATE_START:
                        case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER:
                        case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE:
                                /* read line */
                                php_stream_get_line(s, buf->data + buf->used, buf->free, &justread);
+                               /* if we fail reading a whole line, try a single char */
+                               if (!justread) {
+                                       int c = php_stream_getc(s);
+
+                                       if (c != EOF) {
+                                               char s[1] = {c};
+                                               justread = php_http_buffer_append(buf, s, 1);
+                                       }
+                               }
                                php_http_buffer_account(buf, justread);
                                break;
 
@@ -168,6 +182,7 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me
 
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL:
                                /* should not occur */
                                abort();
                                break;
@@ -179,7 +194,9 @@ php_http_message_parser_state_t php_http_message_parser_parse_stream(php_http_me
 
                if (justread) {
                        state = php_http_message_parser_parse(parser, buf, flags, message);
-               } else  {
+               } else if (php_stream_eof(s)) {
+                       return php_http_message_parser_parse(parser, buf, flags | PHP_HTTP_MESSAGE_PARSER_CLEANUP, message);
+               } else {
                        return state;
                }
        }
@@ -240,9 +257,10 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                break;
 
                                        default:
-                                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER);
-                                               if (buffer->used) {
-                                                       return PHP_HTTP_MESSAGE_PARSER_STATE_HEADER;
+                                               if (buffer->used || !(flags & PHP_HTTP_MESSAGE_PARSER_CLEANUP)) {
+                                                       return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER);
+                                               } else {
+                                                       php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE);
                                                }
                                }
                                break;
@@ -252,23 +270,32 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                        {
                                zval *h, *h_loc = NULL, *h_con = NULL, **h_cl = NULL, **h_cr = NULL, **h_te = NULL;
 
+                               /* Content-Range has higher precedence than Content-Length,
+                                * and content-length denotes the original length of the entity,
+                                * so let's *NOT* remove CR/CL, because that would fundamentally
+                                * change the meaning of the whole message
+                                */
                                if ((h = php_http_message_header(*message, ZEND_STRL("Transfer-Encoding"), 1))) {
-                                       zend_hash_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), &h, sizeof(zval *), (void *) &h_te);
+                                       zend_hash_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), (void *) &h, sizeof(zval *), (void *) &h_te);
                                        zend_hash_del(&(*message)->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding"));
+
+                                       /* reset */
+                                       MAKE_STD_ZVAL(h);
+                                       ZVAL_LONG(h, 0);
+                                       zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &h, sizeof(zval *), NULL);
+                               } else if ((h = php_http_message_header(*message, ZEND_STRL("Content-Length"), 1))) {
+                                       zend_hash_update(&(*message)->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), (void *) &h, sizeof(zval *), (void *) &h_cl);
                                }
-                               if ((h = php_http_message_header(*message, ZEND_STRL("Content-Length"), 1))) {
-                                       zend_hash_update(&(*message)->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), &h, sizeof(zval *), (void *) &h_cl);
-                               }
+
                                if ((h = php_http_message_header(*message, ZEND_STRL("Content-Range"), 1))) {
-                                       zend_hash_update(&(*message)->hdrs, "X-Original-Content-Range", sizeof("X-Original-Content-Range"), &h, sizeof(zval *), (void *) &h_cr);
-                                       zend_hash_del(&(*message)->hdrs, "Content-Range", sizeof("Content-Range"));
+                                       zend_hash_find(&(*message)->hdrs, ZEND_STRS("Content-Range"), (void *) &h_cr);
+                                       if (h != *h_cr) {
+                                               zend_hash_update(&(*message)->hdrs, "Content-Range", sizeof("Content-Range"), &h, sizeof(zval *), (void *) &h_cr);
+                                       } else {
+                                               zval_ptr_dtor(&h);
+                                       }
                                }
 
-                               /* default */
-                               MAKE_STD_ZVAL(h);
-                               ZVAL_LONG(h, 0);
-                               zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &h, sizeof(zval *), NULL);
-
                                /* so, if curl sees a 3xx code, a Location header and a Connection:close header
                                 * it decides not to read the response body.
                                 */
@@ -320,30 +347,13 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                }
                                        }
 
-                                       if (h_cl) {
-                                               char *stop;
-
-                                               if (Z_TYPE_PP(h_cl) == IS_STRING) {
-                                                       parser->body_length = strtoul(Z_STRVAL_PP(h_cl), &stop, 10);
-
-                                                       if (stop != Z_STRVAL_PP(h_cl)) {
-                                                               php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
-                                                               break;
-                                                       }
-                                               } else if (Z_TYPE_PP(h_cl) == IS_LONG) {
-                                                       parser->body_length = Z_LVAL_PP(h_cl);
-                                                       php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
-                                                       break;
-                                               }
-                                       }
-
                                        if (h_cr) {
                                                ulong total = 0, start = 0, end = 0;
 
                                                if (!strncasecmp(Z_STRVAL_PP(h_cr), "bytes", lenof("bytes"))
-                                               && (    Z_STRVAL_P(h)[lenof("bytes")] == ':'
-                                                       ||      Z_STRVAL_P(h)[lenof("bytes")] == ' '
-                                                       ||      Z_STRVAL_P(h)[lenof("bytes")] == '='
+                                               && (    Z_STRVAL_PP(h_cr)[lenof("bytes")] == ':'
+                                                       ||      Z_STRVAL_PP(h_cr)[lenof("bytes")] == ' '
+                                                       ||      Z_STRVAL_PP(h_cr)[lenof("bytes")] == '='
                                                        )
                                                ) {
                                                        char *total_at = NULL, *end_at = NULL;
@@ -356,7 +366,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                                        total = strtoul(total_at + 1, NULL, 10);
                                                                }
 
-                                                               if (end >= start && (!total || end < total)) {
+                                                               if (end >= start && (!total || end <= total)) {
                                                                        parser->body_length = end + 1 - start;
                                                                        php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
                                                                        break;
@@ -365,6 +375,22 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                }
                                        }
 
+                                       if (h_cl) {
+                                               char *stop;
+
+                                               if (Z_TYPE_PP(h_cl) == IS_STRING) {
+                                                       parser->body_length = strtoul(Z_STRVAL_PP(h_cl), &stop, 10);
+
+                                                       if (stop != Z_STRVAL_PP(h_cl)) {
+                                                               php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
+                                                               break;
+                                                       }
+                                               } else if (Z_TYPE_PP(h_cl) == IS_LONG) {
+                                                       parser->body_length = Z_LVAL_PP(h_cl);
+                                                       php_http_message_parser_state_push(parser, 1, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH);
+                                                       break;
+                                               }
+                                       }
 
                                        if ((*message)->type == PHP_HTTP_REQUEST) {
                                                php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
@@ -378,8 +404,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                        case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
                        {
                                if (len) {
-                                       zval *zcl;
-
+                                       /* FIXME: what if we re-use the parser? */
                                        if (parser->inflate) {
                                                char *dec_str = NULL;
                                                size_t dec_len;
@@ -389,7 +414,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                }
 
                                                if (str != buffer->data) {
-                                                       STR_FREE(str);
+                                                       PTR_FREE(str);
                                                }
                                                str = dec_str;
                                                len = dec_len;
@@ -397,10 +422,6 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
 
                                        php_stream_write(php_http_message_body_stream((*message)->body), str, len);
 
-                                       /* keep track */
-                                       MAKE_STD_ZVAL(zcl);
-                                       ZVAL_LONG(zcl, php_http_message_body_size((*message)->body));
-                                       zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &zcl, sizeof(zval *), NULL);
                                }
 
                                if (cut) {
@@ -408,7 +429,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                }
 
                                if (str != buffer->data) {
-                                       STR_FREE(str);
+                                       PTR_FREE(str);
                                }
 
                                str = NULL;
@@ -473,7 +494,7 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                        {
                                php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
 
-                               if (parser->dechunk) {
+                               if (parser->dechunk && parser->dechunk->ctx) {
                                        char *dec_str = NULL;
                                        size_t dec_len;
 
@@ -486,14 +507,24 @@ php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_p
                                                str = dec_str;
                                                len = dec_len;
                                                cut = 0;
-                                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
+                                               php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
                                        }
                                }
 
                                break;
                        }
 
-                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: {
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL:
+                       {
+                               zval *zcl;
+                               MAKE_STD_ZVAL(zcl);
+                               ZVAL_LONG(zcl, php_http_message_body_size((*message)->body));
+                               zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &zcl, sizeof(zval *), NULL);
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE:
+                       {
                                char *ptr = buffer->data;
 
                                while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
@@ -655,6 +686,7 @@ PHP_MINIT_FUNCTION(http_message_parser)
        zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_LENGTH"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH TSRMLS_CC);
        zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_CHUNKED"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED TSRMLS_CC);
        zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_BODY_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_UPDATE_CL"), PHP_HTTP_MESSAGE_PARSER_STATE_UPDATE_CL TSRMLS_CC);
        zend_declare_class_constant_long(php_http_message_parser_class_entry, ZEND_STRL("STATE_DONE"), PHP_HTTP_MESSAGE_PARSER_STATE_DONE TSRMLS_CC);
 
        return SUCCESS;
index c2bee1544713ac84c031ff9b2fd9a9bccd9c1352..0bac9da84ea60c5fcb2b11531a169d69c1f92afc 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 51072cf53da8e59e6578849c28a31bf0d28edb91..8e2227da1db0ec1a17135123ff09f7e657d719d0 100644 (file)
@@ -80,8 +80,8 @@ int php_http_match(const char *haystack_str, const char *needle_str, int flags)
                        }
                }
 
-               STR_FREE(haystack);
-               STR_FREE(needle);
+               PTR_FREE(haystack);
+               PTR_FREE(needle);
        }
 
        return result;
@@ -188,8 +188,10 @@ int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_lis
                if ((flags & ARRAY_JOIN_PRETTIFY) && hash_key->nKeyLength) {
                        key = php_http_pretty_key(estrndup(hash_key->arKey, hash_key->nKeyLength - 1), hash_key->nKeyLength - 1, 1, 1);
                        zend_hash_find(dst, key, hash_key->nKeyLength, (void *) &data);
-               } else {
+               } else if (hash_key->nKeyLength) {
                        zend_hash_quick_find(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &data);
+               } else {
+                       zend_hash_index_find(dst, hash_key->h, (void *) &data);
                }
 
                if (flags & ARRAY_JOIN_STRINGIFY) {
@@ -205,8 +207,10 @@ int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_lis
                        add_next_index_zval(*data, value);
                } else if (key) {
                        zend_symtable_update(dst, key, hash_key->nKeyLength, &value, sizeof(zval *), NULL);
-               } else {
+               } else if (hash_key->nKeyLength) {
                        zend_hash_quick_add(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, &value, sizeof(zval *), NULL);
+               } else {
+                       zend_hash_index_update(dst, hash_key->h, (void *) &value, sizeof(zval *), NULL);
                }
 
                if (key) {
@@ -238,8 +242,10 @@ int php_http_array_apply_merge_func(void *pDest TSRMLS_DC, int num_args, va_list
                        key = php_http_pretty_key(estrndup(hash_key->arKey, hash_key->nKeyLength - 1), hash_key->nKeyLength - 1, 1, 1);
                        zend_hash_update(dst, key, hash_key->nKeyLength, (void *) &value, sizeof(zval *), NULL);
                        efree(key);
-               } else {
+               } else if (hash_key->nKeyLength) {
                        zend_hash_quick_update(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &value, sizeof(zval *), NULL);
+               } else {
+                       zend_hash_index_update(dst, hash_key->h, (void *) &value, sizeof(zval *), NULL);
                }
        }
 
index 3bd77d8b931046006373445f7344476a7bd2a1b9..cf00f24ef5a71e3d3b21c1a8e0283f2c439e8b97 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)
@@ -41,10 +44,10 @@ PHP_HTTP_API void php_http_sleep(double s);
 
 /* STRING UTILITIES */
 
-#ifndef STR_SET
-#      define STR_SET(STR, SET) \
+#ifndef PTR_SET
+#      define PTR_SET(STR, SET) \
        { \
-               STR_FREE(STR); \
+               PTR_FREE(STR); \
                STR = SET; \
        }
 #endif
@@ -291,7 +294,7 @@ static inline void php_http_array_hashkey_stringify(php_http_array_hashkey_t *ke
 static inline void php_http_array_hashkey_stringfree(php_http_array_hashkey_t *key)
 {
        if (key->type != HASH_KEY_IS_STRING || key->dup) {
-               STR_FREE(key->str);
+               PTR_FREE(key->str);
        }
 }
 
index 76106a852adc07a4e72d4a44c43d0836d0e13fa6..a74875b7378647664a1fe35a63b72c8f17995d79 100644 (file)
@@ -141,7 +141,7 @@ HashTable *php_http_negotiate(const char *value_str, size_t value_len, HashTable
                                add_index_double(&arr, key.num, q);
                        }
 
-                       STR_FREE(key.str);
+                       PTR_FREE(key.str);
                }
 
 #if 0
index 3a12ab7cd1992a01d48b409a71e32770dfd77ffa..f7405b54e5111a4f66f4fffe743bd3f436384da7 100644 (file)
@@ -24,7 +24,7 @@ static inline HashTable *php_http_negotiate_language(HashTable *supported, php_h
        if (value) {
                result = php_http_negotiate(value, length, supported, "-", 1 TSRMLS_CC);
        }
-       STR_FREE(value);
+       PTR_FREE(value);
 
        return result;
 }
@@ -38,7 +38,7 @@ static inline HashTable *php_http_negotiate_encoding(HashTable *supported, php_h
        if (value) {
                result = php_http_negotiate(value, length, supported, NULL, 0 TSRMLS_CC);
        }
-       STR_FREE(value);
+       PTR_FREE(value);
 
        return result;
 }
@@ -52,7 +52,7 @@ static inline HashTable *php_http_negotiate_charset(HashTable *supported, php_ht
        if (value) {
                result = php_http_negotiate(value, length, supported, NULL, 0 TSRMLS_CC);
        }
-       STR_FREE(value);
+       PTR_FREE(value);
 
        return result;
 }
@@ -66,7 +66,7 @@ static inline HashTable *php_http_negotiate_content_type(HashTable *supported, p
        if (value) {
                result = php_http_negotiate(value, length, supported, "/", 1 TSRMLS_CC);
        }
-       STR_FREE(value);
+       PTR_FREE(value);
 
        return result;
 }
index abf72290e26de0ad9dea5052e0d922a82602e8d2..ba6d28c82b8b7fed832cc9327d73bca8782a3d6d 100644 (file)
@@ -53,30 +53,80 @@ STATUS php_http_new(zend_object_value *ovp, zend_class_entry *ce, php_http_new_t
        return SUCCESS;
 }
 
-STATUS php_http_method_call(zval *object, const char *method_str, size_t method_len, int argc, zval **argv[], zval **retval_ptr TSRMLS_DC)
+static inline zend_function *get_object_method(zval *zobject, zval *zmeth TSRMLS_DC)
+{
+#if PHP_VERSION_ID >= 50400
+       return Z_OBJ_HT_P(zobject)->get_method(&zobject, Z_STRVAL_P(zmeth), Z_STRLEN_P(zmeth), NULL TSRMLS_CC);
+#else
+       return Z_OBJ_HT_P(zobject)->get_method(&zobject, Z_STRVAL_P(zmeth), Z_STRLEN_P(zmeth) TSRMLS_CC);
+#endif
+}
+
+php_http_object_method_t *php_http_object_method_init(php_http_object_method_t *cb, zval *zobject, const char *method_str, size_t method_len TSRMLS_DC)
+{
+       zval *zfn;
+
+       if (!cb) {
+               cb = ecalloc(1, sizeof(*cb));
+       } else {
+               memset(cb, 0, sizeof(*cb));
+       }
+
+       MAKE_STD_ZVAL(zfn);
+       ZVAL_STRINGL(zfn, method_str, method_len, 1);
+
+       cb->fci.size = sizeof(cb->fci);
+       cb->fci.function_name = zfn;
+       cb->fcc.initialized = 1;
+       cb->fcc.calling_scope = cb->fcc.called_scope = Z_OBJCE_P(zobject);
+       cb->fcc.function_handler = get_object_method(zobject, cb->fci.function_name TSRMLS_CC);
+
+       return cb;
+}
+
+void php_http_object_method_dtor(php_http_object_method_t *cb)
+{
+       if (cb->fci.function_name) {
+               zval_ptr_dtor(&cb->fci.function_name);
+               cb->fci.function_name = NULL;
+       }
+}
+
+void php_http_object_method_free(php_http_object_method_t **cb)
+{
+       if (*cb) {
+               php_http_object_method_dtor(*cb);
+               efree(*cb);
+               *cb = NULL;
+       }
+}
+
+STATUS php_http_object_method_call(php_http_object_method_t *cb, zval *zobject, zval **retval_ptr, int argc, zval ***args TSRMLS_DC)
 {
-       zend_fcall_info fci;
-       zval zmethod;
-       zval *retval;
        STATUS rv;
+       zval *retval = NULL;
+
+       Z_ADDREF_P(zobject);
+       cb->fci.object_ptr = zobject;
+       cb->fcc.object_ptr = zobject;
+
+       cb->fci.retval_ptr_ptr = retval_ptr ? retval_ptr : &retval;
 
-       fci.size = sizeof(fci);
-       fci.object_ptr = object;
-       fci.function_name = &zmethod;
-       fci.retval_ptr_ptr = retval_ptr ? retval_ptr : &retval;
-       fci.param_count = argc;
-       fci.params = argv;
-       fci.no_separation = 1;
-       fci.symbol_table = NULL;
-       fci.function_table = NULL;
+       cb->fci.param_count = argc;
+       cb->fci.params = args;
 
-       INIT_PZVAL(&zmethod);
-       ZVAL_STRINGL(&zmethod, method_str, method_len, 0);
-       rv = zend_call_function(&fci, NULL TSRMLS_CC);
+       if (cb->fcc.called_scope != Z_OBJCE_P(zobject)) {
+               cb->fcc.called_scope = Z_OBJCE_P(zobject);
+               cb->fcc.function_handler = get_object_method(zobject, cb->fci.function_name TSRMLS_CC);
+       }
+
+       rv = zend_call_function(&cb->fci, &cb->fcc TSRMLS_CC);
 
+       zval_ptr_dtor(&zobject);
        if (!retval_ptr && retval) {
                zval_ptr_dtor(&retval);
        }
+
        return rv;
 }
 
index 63730b423ae195cc53d9609c43bb2048a6798ecf..1fbd817454ad6ddccbf3cb263aae3fd21b6af261 100644 (file)
@@ -24,7 +24,16 @@ zend_object_value php_http_object_new_ex(zend_class_entry *ce, void *nothing, ph
 typedef zend_object_value (*php_http_new_t)(zend_class_entry *ce, void *, void ** TSRMLS_DC);
 
 STATUS php_http_new(zend_object_value *ov, zend_class_entry *ce, php_http_new_t create, zend_class_entry *parent_ce, void *intern_ptr, void **obj_ptr TSRMLS_DC);
-STATUS php_http_method_call(zval *object, const char *method_str, size_t method_len, int argc, zval **argv[], zval **retval_ptr TSRMLS_DC);
+
+typedef struct php_http_method {
+       zend_fcall_info fci;
+       zend_fcall_info_cache fcc;
+} php_http_object_method_t;
+
+php_http_object_method_t *php_http_object_method_init(php_http_object_method_t *cb, zval *zobject, const char *method_str, size_t method_len TSRMLS_DC);
+STATUS php_http_object_method_call(php_http_object_method_t *cb, zval *zobject, zval **retval, int argc, zval ***args TSRMLS_DC);
+void php_http_object_method_dtor(php_http_object_method_t *cb);
+void php_http_object_method_free(php_http_object_method_t **cb);
 
 #endif
 
index 523424423d3b21ce15704d4804cae4dd73fc1daa..ce785ecbe03efef1daad18b9acb0948fc5f9a61a 100644 (file)
@@ -347,7 +347,7 @@ static inline void sanitize_value(unsigned flags, char *str, size_t len, zval *z
                ZVAL_COPY_VALUE(tmp, zv);
                array_init(zv);
                add_assoc_zval(zv, language, tmp);
-               STR_FREE(language);
+               PTR_FREE(language);
        }
 }
 
@@ -885,7 +885,7 @@ void php_http_params_separator_free(php_http_params_token_t **separator)
        php_http_params_token_t **sep = separator;
        if (sep) {
                while (*sep) {
-                       STR_FREE((*sep)->str);
+                       PTR_FREE((*sep)->str);
                        efree(*sep);
                        ++sep;
                }
diff --git a/php_http_response_codes.h b/php_http_response_codes.h
new file mode 100644 (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.c b/php_http_strlist.c
deleted file mode 100644 (file)
index f457178..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
-    +--------------------------------------------------------------------+
-    | PECL :: http                                                       |
-    +--------------------------------------------------------------------+
-    | Redistribution and use in source and binary forms, with or without |
-    | modification, are permitted provided that the conditions mentioned |
-    | in the accompanying LICENSE file are met.                          |
-    +--------------------------------------------------------------------+
-    | Copyright (c) 2004-2014, Michael Wallner <mike@php.net>            |
-    +--------------------------------------------------------------------+
-*/
-
-#include "php_http_api.h"
-
-php_http_strlist_iterator_t *php_http_strlist_iterator_init(php_http_strlist_iterator_t *iter, const char list[], unsigned factor)
-{
-       if (!iter) {
-               iter = emalloc(sizeof(*iter));
-       }
-       memset(iter, 0, sizeof(*iter));
-
-       iter->p = &list[0];
-       iter->factor = factor;
-
-       return iter;
-}
-
-const char *php_http_strlist_iterator_this(php_http_strlist_iterator_t *iter, unsigned *id)
-{
-       if (id) {
-               *id = (iter->major + 1) * iter->factor + iter->minor;
-       }
-
-       return iter->p;
-}
-
-const char *php_http_strlist_iterator_next(php_http_strlist_iterator_t *iter)
-{
-       if (*iter->p) {
-               while (*iter->p) {
-                       ++iter->p;
-               }
-               ++iter->p;
-               ++iter->minor;
-
-               if (!*iter->p) {
-                       ++iter->p;
-                       ++iter->major;
-                       iter->minor = 0;
-               }
-       }
-
-    return iter->p;
-}
-
-void php_http_strlist_iterator_dtor(php_http_strlist_iterator_t *iter)
-{
-
-}
-
-void php_http_strlist_iterator_free(php_http_strlist_iterator_t **iter)
-{
-       if (*iter) {
-               efree(*iter);
-               *iter = NULL;
-       }
-}
-
-const char *php_http_strlist_find(const char list[], unsigned factor, unsigned item)
-{
-       unsigned M = 0, m = 0, major, minor;
-       const char *p = &list[0];
-
-       if (factor) {
-               major = (item / factor) - 1;
-               minor = item % factor;
-       } else {
-               major = 0;
-               minor = item;
-       }
-    while (*p && major != M++) {
-        while (*p) {
-            while (*p) {
-                ++p;
-            }
-            ++p;
-        }
-        ++p;
-    }
-
-    while (*p && minor != m++) {
-        while (*p) {
-            ++p;
-        }
-        ++p;
-    }
-
-    return p;
-}
-
-/*
- * Local variables:
- * tab-width: 4
- * c-basic-offset: 4
- * End:
- * vim600: noet sw=4 ts=4 fdm=marker
- * vim<600: noet sw=4 ts=4
- */
-
diff --git a/php_http_strlist.h b/php_http_strlist.h
deleted file mode 100644 (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 5aeefa8a3b6b50463fd5757e38171a77bc0cd33a..6bfca2480d0b233685bc7192a48fe6c676cb5853 100644 (file)
@@ -57,7 +57,7 @@ static inline char *localhostname(void)
        return estrndup("localhost", lenof("localhost"));
 }
 
-#define url(buf) ((php_http_url_t *) buf.data)
+#define url(buf) ((php_http_url_t *) (buf).data)
 
 static php_http_url_t *php_http_url_from_env(TSRMLS_D)
 {
@@ -123,29 +123,32 @@ static php_http_url_t *php_http_url_from_env(TSRMLS_D)
        return url(buf);
 }
 
-void php_http_url(int flags, const php_url *old_url, const php_url *new_url, php_url **url_ptr, char **url_str, size_t *url_len TSRMLS_DC)
-{
-       php_http_url_t *url = php_http_url_mod((const php_http_url_t *) old_url, (const php_http_url_t *) new_url, flags TSRMLS_CC);
-
-       if (url_ptr) {
-               *url_ptr = php_http_url_to_php_url(url);
-       }
-       if (url_str) {
-               php_http_url_to_string(url, url_str, url_len TSRMLS_CC);
-       }
-
-       php_http_url_free(&url);
-}
-
 #define url_isset(u,n) \
        ((u)&&(u)->n)
+#define url_append(buf, append) do { \
+       char *_ptr = (buf)->data; \
+       php_http_url_t *_url = (php_http_url_t *) _ptr, _mem = *_url; \
+       append; \
+       /* relocate */ \
+       if (_ptr != (buf)->data) { \
+               ptrdiff_t diff = (buf)->data - _ptr; \
+               _url = (php_http_url_t *) (buf)->data; \
+               if (_mem.scheme)        _url->scheme += diff; \
+               if (_mem.user)          _url->user += diff; \
+               if (_mem.pass)          _url->pass += diff; \
+               if (_mem.host)          _url->host += diff; \
+               if (_mem.path)          _url->path += diff; \
+               if (_mem.query)         _url->query += diff; \
+               if (_mem.fragment)      _url->fragment += diff; \
+       } \
+} while (0)
 #define url_copy(n) do { \
        if (url_isset(new_url, n)) { \
                url(buf)->n = &buf.data[buf.used]; \
-               php_http_buffer_append(&buf, new_url->n, strlen(new_url->n) + 1); \
+               url_append(&buf, php_http_buffer_append(&buf, new_url->n, strlen(new_url->n) + 1)); \
        } else if (url_isset(old_url, n)) { \
                url(buf)->n = &buf.data[buf.used]; \
-               php_http_buffer_append(&buf, old_url->n, strlen(old_url->n) + 1); \
+               url_append(&buf, php_http_buffer_append(&buf, old_url->n, strlen(old_url->n) + 1)); \
        } \
 } while (0)
 
@@ -196,28 +199,23 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u
                        
                        url(buf)->path = &buf.data[buf.used];
                        if (path[0] != '/') {
-                               php_http_buffer_append(&buf, "/", 1);
+                               url_append(&buf, php_http_buffer_append(&buf, "/", 1));
                        }
-                       php_http_buffer_append(&buf, path, strlen(path) + 1);
+                       url_append(&buf, php_http_buffer_append(&buf, path, strlen(path) + 1));
                        efree(path);
                } else {
                        const char *path = NULL;
 
-                       url(buf)->path = &buf.data[buf.used];
-
                        if (url_isset(new_url, path)) {
                                path = new_url->path;
                        } else if (url_isset(old_url, path)) {
                                path = old_url->path;
-                       } else {
-                               php_http_buffer_append(&buf, "/", sizeof("/"));
                        }
 
                        if (path) {
-                               if (path[0] != '/') {
-                                       php_http_buffer_append(&buf, "/", 1);
-                               }
-                               php_http_buffer_append(&buf, path, strlen(path) + 1);
+                               url(buf)->path = &buf.data[buf.used];
+
+                               url_append(&buf, php_http_buffer_append(&buf, path, strlen(path) + 1));
                        }
 
 
@@ -241,7 +239,7 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u
                        php_http_querystring_update(&qarr, NULL, &qstr TSRMLS_CC);
 
                        url(buf)->query = &buf.data[buf.used];
-                       php_http_buffer_append(&buf, Z_STRVAL(qstr), Z_STRLEN(qstr) + 1);
+                       url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL(qstr), Z_STRLEN(qstr) + 1));
 
                        zval_dtor(&qstr);
                        zval_dtor(&qarr);
@@ -261,22 +259,6 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u
                php_http_url_free(&tmp_url);
        }
 
-       /* set some sane defaults */
-
-       if (!url(buf)->scheme) {
-               url(buf)->scheme = &buf.data[buf.used];
-               php_http_buffer_append(&buf, "http", sizeof("http"));
-       }
-
-       if (!url(buf)->host) {
-               url(buf)->host = &buf.data[buf.used];
-               php_http_buffer_append(&buf, "localhost", sizeof("localhost"));
-       }
-       
-       if (!url(buf)->path) {
-               url(buf)->path = &buf.data[buf.used];
-               php_http_buffer_append(&buf, "/", sizeof("/"));
-       }
        /* replace directory references if path is not a single slash */
        if ((flags & PHP_HTTP_URL_SANITIZE_PATH)
        &&      url(buf)->path[0] && url(buf)->path[1]) {
@@ -329,8 +311,8 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u
        }
        /* unset default ports */
        if (url(buf)->port) {
-               if (    ((url(buf)->port == 80) && !strcmp(url(buf)->scheme, "http"))
-                       ||      ((url(buf)->port ==443) && !strcmp(url(buf)->scheme, "https"))
+               if (    ((url(buf)->port == 80) && url(buf)->scheme && !strcmp(url(buf)->scheme, "http"))
+                       ||      ((url(buf)->port ==443) && url(buf)->scheme && !strcmp(url(buf)->scheme, "https"))
                ) {
                        url(buf)->port = 0;
                }
@@ -339,16 +321,17 @@ php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_u
        return url(buf);
 }
 
-void php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len TSRMLS_DC)
+char *php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len, zend_bool persistent)
 {
        php_http_buffer_t buf;
 
-       php_http_buffer_init(&buf);
+       php_http_buffer_init_ex(&buf, PHP_HTTP_BUFFER_DEFAULT_SIZE, persistent ?
+                       PHP_HTTP_BUFFER_INIT_PERSISTENT : 0);
 
        if (url->scheme && *url->scheme) {
                php_http_buffer_appendl(&buf, url->scheme);
                php_http_buffer_appends(&buf, "://");
-       } else {
+       } else if ((url->user && *url->user) || (url->host && *url->host)) {
                php_http_buffer_appends(&buf, "//");
        }
 
@@ -363,16 +346,18 @@ void php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *u
 
        if (url->host && *url->host) {
                php_http_buffer_appendl(&buf, url->host);
-       } else {
-               php_http_buffer_appends(&buf, "localhost");
-       }
-
-       if (url->port) {
-               php_http_buffer_appendf(&buf, ":%hu", url->port);
+               if (url->port) {
+                       php_http_buffer_appendf(&buf, ":%hu", url->port);
+               }
        }
 
        if (url->path && *url->path) {
+               if (*url->path != '/') {
+                       php_http_buffer_appends(&buf, "/");
+               }
                php_http_buffer_appendl(&buf, url->path);
+       } else if (buf.used) {
+               php_http_buffer_appends(&buf, "/");
        }
 
        if (url->query && *url->query) {
@@ -394,12 +379,68 @@ void php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *u
 
        if (url_str) {
                *url_str = buf.data;
-       } else {
-               php_http_buffer_dtor(&buf);
        }
+
+       return buf.data;
 }
 
-php_http_url_t *php_http_url_from_struct(HashTable *ht TSRMLS_DC)
+char *php_http_url_authority_to_string(const php_http_url_t *url, char **url_str, size_t *url_len)
+{
+       php_http_buffer_t buf;
+
+       php_http_buffer_init(&buf);
+
+       if (url->user && *url->user) {
+               php_http_buffer_appendl(&buf, url->user);
+               if (url->pass && *url->pass) {
+                       php_http_buffer_appends(&buf, ":");
+                       php_http_buffer_appendl(&buf, url->pass);
+               }
+               php_http_buffer_appends(&buf, "@");
+       }
+
+       if (url->host && *url->host) {
+               php_http_buffer_appendl(&buf, url->host);
+               if (url->port) {
+                       php_http_buffer_appendf(&buf, ":%hu", url->port);
+               }
+       }
+
+       php_http_buffer_shrink(&buf);
+       php_http_buffer_fix(&buf);
+
+       if (url_len) {
+               *url_len = buf.used;
+       }
+
+       if (url_str) {
+               *url_str = buf.data;
+       }
+
+       return buf.data;
+}
+
+php_http_url_t *php_http_url_from_zval(zval *value, unsigned flags TSRMLS_DC)
+{
+       zval *zcpy;
+       php_http_url_t *purl;
+
+       switch (Z_TYPE_P(value)) {
+       case IS_ARRAY:
+       case IS_OBJECT:
+               purl = php_http_url_from_struct(HASH_OF(value));
+               break;
+
+       default:
+               zcpy = php_http_ztyp(IS_STRING, value);
+               purl = php_http_url_parse(Z_STRVAL_P(zcpy), Z_STRLEN_P(zcpy), flags TSRMLS_CC);
+               zval_ptr_dtor(&zcpy);
+       }
+
+       return purl;
+}
+
+php_http_url_t *php_http_url_from_struct(HashTable *ht)
 {
        zval **e;
        php_http_buffer_t buf;
@@ -411,25 +452,25 @@ php_http_url_t *php_http_url_from_struct(HashTable *ht TSRMLS_DC)
        if (SUCCESS == zend_hash_find(ht, "scheme", sizeof("scheme"), (void *) &e)) {
                zval *cpy = php_http_ztyp(IS_STRING, *e);
                url(buf)->scheme = &buf.data[buf.used];
-               php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1);
+               url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1));
                zval_ptr_dtor(&cpy);
        }
        if (SUCCESS == zend_hash_find(ht, "user", sizeof("user"), (void *) &e)) {
                zval *cpy = php_http_ztyp(IS_STRING, *e);
                url(buf)->user = &buf.data[buf.used];
-               php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1);
+               url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1));
                zval_ptr_dtor(&cpy);
        }
        if (SUCCESS == zend_hash_find(ht, "pass", sizeof("pass"), (void *) &e)) {
                zval *cpy = php_http_ztyp(IS_STRING, *e);
                url(buf)->pass = &buf.data[buf.used];
-               php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1);
+               url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1));
                zval_ptr_dtor(&cpy);
        }
        if (SUCCESS == zend_hash_find(ht, "host", sizeof("host"), (void *) &e)) {
                zval *cpy = php_http_ztyp(IS_STRING, *e);
                url(buf)->host = &buf.data[buf.used];
-               php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1);
+               url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1));
                zval_ptr_dtor(&cpy);
        }
        if (SUCCESS == zend_hash_find(ht, "port", sizeof("port"), (void *) &e)) {
@@ -440,19 +481,19 @@ php_http_url_t *php_http_url_from_struct(HashTable *ht TSRMLS_DC)
        if (SUCCESS == zend_hash_find(ht, "path", sizeof("path"), (void *) &e)) {
                zval *cpy = php_http_ztyp(IS_STRING, *e);
                url(buf)->path = &buf.data[buf.used];
-               php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1);
+               url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1));
                zval_ptr_dtor(&cpy);
        }
        if (SUCCESS == zend_hash_find(ht, "query", sizeof("query"), (void *) &e)) {
                zval *cpy = php_http_ztyp(IS_STRING, *e);
                url(buf)->query = &buf.data[buf.used];
-               php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1);
+               url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1));
                zval_ptr_dtor(&cpy);
        }
        if (SUCCESS == zend_hash_find(ht, "fragment", sizeof("fragment"), (void *) &e)) {
                zval *cpy = php_http_ztyp(IS_STRING, *e);
                url(buf)->fragment = &buf.data[buf.used];
-               php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1);
+               url_append(&buf, php_http_buffer_append(&buf, Z_STRVAL_P(cpy), Z_STRLEN_P(cpy) + 1));
                zval_ptr_dtor(&cpy);
        }
 
@@ -562,6 +603,43 @@ void php_http_url_free(php_http_url_t **url)
        }
 }
 
+php_http_url_t *php_http_url_copy(const php_http_url_t *url, zend_bool persistent)
+{
+       php_http_url_t *cpy;
+       const char *end = NULL, *url_ptr = (const char *) url;
+       char *cpy_ptr;
+
+       end = MAX(url->scheme, end);
+       end = MAX(url->pass, end);
+       end = MAX(url->user, end);
+       end = MAX(url->host, end);
+       end = MAX(url->path, end);
+       end = MAX(url->query, end);
+       end = MAX(url->fragment, end);
+
+       if (end) {
+               end += strlen(end) + 1;
+               cpy_ptr = pecalloc(1, end - url_ptr, persistent);
+               cpy = (php_http_url_t *) cpy_ptr;
+
+               memcpy(cpy_ptr + sizeof(*cpy), url_ptr + sizeof(*url), end - url_ptr - sizeof(*url));
+
+               cpy->scheme = url->scheme ? cpy_ptr + (url->scheme - url_ptr) : NULL;
+               cpy->pass = url->pass ? cpy_ptr + (url->pass - url_ptr) : NULL;
+               cpy->user = url->user ? cpy_ptr + (url->user - url_ptr) : NULL;
+               cpy->host = url->host ? cpy_ptr + (url->host - url_ptr) : NULL;
+               cpy->path = url->path ? cpy_ptr + (url->path - url_ptr) : NULL;
+               cpy->query = url->query ? cpy_ptr + (url->query - url_ptr) : NULL;
+               cpy->fragment = url->fragment ? cpy_ptr + (url->fragment - url_ptr) : NULL;
+       } else {
+               cpy = ecalloc(1, sizeof(*url));
+       }
+
+       cpy->port = url->port;
+
+       return cpy;
+}
+
 static size_t parse_mb_utf8(unsigned *wc, const char *ptr, const char *end)
 {
        unsigned wchar;
@@ -834,7 +912,9 @@ static STATUS parse_hostinfo(struct parse_state *state, const char *ptr)
                        break;
 
                default:
-                       if (port) {
+                       if (ptr == end) {
+                               break;
+                       } else if (port) {
                                php_error_docref(NULL TSRMLS_CC, E_WARNING,
                                                "Failed to parse port; unexpected byte 0x%02x at pos %u in '%s'",
                                                (unsigned char) *ptr, (unsigned) (ptr - tmp), tmp);
@@ -905,6 +985,7 @@ static const char *parse_authority(struct parse_state *state)
                case '?':
                case '#':
                case '\0':
+                       EOD:
                        /* host delimiter */
                        if (tmp != state->ptr && SUCCESS != parse_hostinfo(state, tmp)) {
                                return NULL;
@@ -913,7 +994,8 @@ static const char *parse_authority(struct parse_state *state)
                }
        } while (++state->ptr <= state->end);
 
-       return NULL;
+       --state->ptr;
+       goto EOD;
 }
 
 static const char *parse_path(struct parse_state *state)
@@ -933,14 +1015,7 @@ static const char *parse_path(struct parse_state *state)
                switch (*state->ptr) {
                case '#':
                case '?':
-               case '\0':
-                       /* did we have any path component ? */
-                       if (tmp != state->ptr) {
-                               state->buffer[state->offset++] = 0;
-                       } else {
-                               state->url.path = NULL;
-                       }
-                       return state->ptr;
+                       goto done;
 
                case '%':
                        if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
@@ -979,9 +1054,16 @@ static const char *parse_path(struct parse_state *state)
                        }
                        state->ptr += mb - 1;
                }
-       } while (++state->ptr <= state->end);
+       } while (++state->ptr < state->end);
 
-       return NULL;
+       done:
+       /* did we have any path component ? */
+       if (tmp != state->ptr) {
+               state->buffer[state->offset++] = 0;
+       } else {
+               state->url.path = NULL;
+       }
+       return state->ptr;
 }
 
 static const char *parse_query(struct parse_state *state)
@@ -1002,9 +1084,7 @@ static const char *parse_query(struct parse_state *state)
        do {
                switch (*state->ptr) {
                case '#':
-               case '\0':
-                       state->buffer[state->offset++] = 0;
-                       return state->ptr;
+                       goto done;
 
                case '%':
                        if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
@@ -1053,9 +1133,11 @@ static const char *parse_query(struct parse_state *state)
                        }
                        state->ptr += mb - 1;
                }
-       } while (++state->ptr <= state->end);
+       } while (++state->ptr < state->end);
 
-       return NULL;
+       done:
+       state->buffer[state->offset++] = 0;
+       return state->ptr;
 }
 
 static const char *parse_fragment(struct parse_state *state)
@@ -1075,10 +1157,6 @@ static const char *parse_fragment(struct parse_state *state)
 
        do {
                switch (*state->ptr) {
-               case '\0':
-                       state->buffer[state->offset++] = 0;
-                       return state->ptr;
-
                case '%':
                        if (state->ptr[1] != '%' && (state->end - state->ptr <= 2 || !isxdigit(*(state->ptr+1)) || !isxdigit(*(state->ptr+2)))) {
                                php_error_docref(NULL TSRMLS_CC, E_WARNING,
@@ -1116,9 +1194,10 @@ static const char *parse_fragment(struct parse_state *state)
                        }
                        state->ptr += mb - 1;
                }
-       } while (++state->ptr <= state->end);
+       } while (++state->ptr < state->end);
 
-       return NULL;
+       state->buffer[state->offset++] = 0;
+       return state->ptr;
 }
 
 static const char *parse_hier(struct parse_state *state)
@@ -1217,6 +1296,33 @@ php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags T
        return (php_http_url_t *) state;
 }
 
+php_http_url_t *php_http_url_parse_authority(const char *str, size_t len, unsigned flags TSRMLS_DC)
+{
+       size_t maxlen = 3 * len;
+       struct parse_state *state = ecalloc(1, sizeof(*state) + maxlen);
+
+       state->end = str + len;
+       state->ptr = str;
+       state->flags = flags;
+       state->maxlen = maxlen;
+       TSRMLS_SET_CTX(state->ts);
+
+       if (!(state->ptr = parse_authority(state))) {
+               efree(state);
+               return NULL;
+       }
+
+       if (state->ptr != state->end) {
+               php_error_docref(NULL TSRMLS_CC, E_WARNING,
+                               "Failed to parse URL authority, unexpected character at pos %u in '%s'",
+                               (unsigned) (state->ptr - str), str);
+               efree(state);
+               return NULL;
+       }
+
+       return (php_http_url_t *) state;
+}
+
 ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl___construct, 0, 0, 0)
        ZEND_ARG_INFO(0, old_url)
        ZEND_ARG_INFO(0, new_url)
@@ -1235,38 +1341,14 @@ PHP_METHOD(HttpUrl, __construct)
                php_http_url_t *res_purl, *new_purl = NULL, *old_purl = NULL;
 
                if (new_url) {
-                       switch (Z_TYPE_P(new_url)) {
-                               case IS_OBJECT:
-                               case IS_ARRAY:
-                                       new_purl = php_http_url_from_struct(HASH_OF(new_url) TSRMLS_CC);
-                                       break;
-                               default: {
-                                       zval *cpy = php_http_ztyp(IS_STRING, new_url);
-
-                                       new_purl = php_http_url_parse(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy), flags TSRMLS_CC);
-                                       zval_ptr_dtor(&cpy);
-                                       break;
-                               }
-                       }
+                       new_purl = php_http_url_from_zval(new_url, flags TSRMLS_CC);
                        if (!new_purl) {
                                zend_restore_error_handling(&zeh TSRMLS_CC);
                                return;
                        }
                }
                if (old_url) {
-                       switch (Z_TYPE_P(old_url)) {
-                               case IS_OBJECT:
-                               case IS_ARRAY:
-                                       old_purl = php_http_url_from_struct(HASH_OF(old_url) TSRMLS_CC);
-                                       break;
-                               default: {
-                                       zval *cpy = php_http_ztyp(IS_STRING, old_url);
-
-                                       old_purl = php_http_url_parse(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy), flags TSRMLS_CC);
-                                       zval_ptr_dtor(&cpy);
-                                       break;
-                               }
-                       }
+                       old_purl = php_http_url_from_zval(old_url, flags TSRMLS_CC);
                        if (!old_purl) {
                                if (new_purl) {
                                        php_http_url_free(&new_purl);
@@ -1307,26 +1389,14 @@ PHP_METHOD(HttpUrl, mod)
                php_http_url_t *new_purl = NULL, *old_purl = NULL;
 
                if (new_url) {
-                       switch (Z_TYPE_P(new_url)) {
-                               case IS_OBJECT:
-                               case IS_ARRAY:
-                                       new_purl = php_http_url_from_struct(HASH_OF(new_url) TSRMLS_CC);
-                                       break;
-                               default: {
-                                       zval *cpy = php_http_ztyp(IS_STRING, new_url);
-
-                                       new_purl = php_http_url_parse(Z_STRVAL_P(new_url), Z_STRLEN_P(new_url), flags TSRMLS_CC);
-                                       zval_ptr_dtor(&cpy);
-                                       break;
-                               }
-                       }
+                       new_purl = php_http_url_from_zval(new_url, flags TSRMLS_CC);
                        if (!new_purl) {
                                zend_restore_error_handling(&zeh TSRMLS_CC);
                                return;
                        }
                }
 
-               if ((old_purl = php_http_url_from_struct(HASH_OF(getThis()) TSRMLS_CC))) {
+               if ((old_purl = php_http_url_from_struct(HASH_OF(getThis())))) {
                        php_http_url_t *res_purl;
 
                        ZVAL_OBJVAL(return_value, zend_objects_clone_obj(getThis() TSRMLS_CC), 0);
@@ -1351,11 +1421,11 @@ PHP_METHOD(HttpUrl, toString)
        if (SUCCESS == zend_parse_parameters_none()) {
                php_http_url_t *purl;
 
-               if ((purl = php_http_url_from_struct(HASH_OF(getThis()) TSRMLS_CC))) {
+               if ((purl = php_http_url_from_struct(HASH_OF(getThis())))) {
                        char *str;
                        size_t len;
 
-                       php_http_url_to_string(purl, &str, &len TSRMLS_CC);
+                       php_http_url_to_string(purl, &str, &len, 0);
                        php_http_url_free(&purl);
                        RETURN_STRINGL(str, len, 0);
                }
@@ -1374,72 +1444,17 @@ PHP_METHOD(HttpUrl, toArray)
        }
 
        /* strip any non-URL properties */
-       purl = php_http_url_from_struct(HASH_OF(getThis()) TSRMLS_CC);
+       purl = php_http_url_from_struct(HASH_OF(getThis()));
        php_http_url_to_struct(purl, return_value TSRMLS_CC);
        php_http_url_free(&purl);
 }
 
-ZEND_BEGIN_ARG_INFO_EX(ai_HttpUrl_parse, 0, 0, 1)
-       ZEND_ARG_INFO(0, url)
-       ZEND_ARG_INFO(0, flags)
-ZEND_END_ARG_INFO();
-PHP_METHOD(HttpUrl, parse)
-{
-       char *str;
-       int len;
-       long flags = 0;
-       php_http_url_t *url;
-       zend_error_handling zeh;
-
-       php_http_expect(SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &len, &flags), invalid_arg, return);
-
-       zend_replace_error_handling(EH_THROW, php_http_exception_bad_url_class_entry, &zeh TSRMLS_CC);
-       if ((url = php_http_url_parse(str, len, flags TSRMLS_CC))) {
-               object_init_ex(return_value, php_http_url_class_entry);
-               if (url->scheme) {
-                       zend_update_property_string(php_http_url_class_entry, return_value,
-                                       ZEND_STRL("scheme"), url->scheme TSRMLS_CC);
-               }
-               if (url->user) {
-                       zend_update_property_string(php_http_url_class_entry, return_value,
-                                       ZEND_STRL("user"), url->user TSRMLS_CC);
-               }
-               if (url->pass) {
-                       zend_update_property_string(php_http_url_class_entry, return_value,
-                                       ZEND_STRL("pass"), url->pass TSRMLS_CC);
-               }
-               if (url->host) {
-                       zend_update_property_string(php_http_url_class_entry, return_value,
-                                       ZEND_STRL("host"), url->host TSRMLS_CC);
-               }
-               if (url->port) {
-                       zend_update_property_long(php_http_url_class_entry, return_value,
-                                       ZEND_STRL("port"), url->port TSRMLS_CC);
-               }
-               if (url->path) {
-                       zend_update_property_string(php_http_url_class_entry, return_value,
-                                       ZEND_STRL("path"), url->path TSRMLS_CC);
-               }
-               if (url->query) {
-                       zend_update_property_string(php_http_url_class_entry, return_value,
-                                       ZEND_STRL("query"), url->query TSRMLS_CC);
-               }
-               if (url->fragment) {
-                       zend_update_property_string(php_http_url_class_entry, return_value,
-                                       ZEND_STRL("fragment"), url->fragment TSRMLS_CC);
-               }
-               php_http_url_free(&url);
-       }
-       zend_restore_error_handling(&zeh TSRMLS_CC);
-}
-
 static zend_function_entry php_http_url_methods[] = {
        PHP_ME(HttpUrl, __construct,  ai_HttpUrl___construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
        PHP_ME(HttpUrl, mod,          ai_HttpUrl_mod, ZEND_ACC_PUBLIC)
        PHP_ME(HttpUrl, toString,     ai_HttpUrl_toString, ZEND_ACC_PUBLIC)
        ZEND_MALIAS(HttpUrl, __toString, toString, ai_HttpUrl_toString, ZEND_ACC_PUBLIC)
        PHP_ME(HttpUrl, toArray,      ai_HttpUrl_toArray, ZEND_ACC_PUBLIC)
-       PHP_ME(HttpUrl, parse,        ai_HttpUrl_parse, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
        EMPTY_FUNCTION_ENTRY
 };
 
index a6dda5351fee73c6f5b21e127b6d83e2ca54bfd2..303b9361b83fa895f188855948ed9420afb05be4 100644 (file)
@@ -15,6 +15,7 @@
 
 #include <ext/standard/url.h>
 
+/* php_http_url_mod() */
 #define PHP_HTTP_URL_REPLACE           0x000
 #define PHP_HTTP_URL_JOIN_PATH         0x001
 #define PHP_HTTP_URL_JOIN_QUERY                0x002
@@ -57,43 +58,28 @@ typedef struct php_http_url {
 } php_http_url_t;
 
 PHP_HTTP_API php_http_url_t *php_http_url_parse(const char *str, size_t len, unsigned flags TSRMLS_DC);
-PHP_HTTP_API void php_http_url_free(php_http_url_t **url);
-
-/* deprecated */
-PHP_HTTP_API void php_http_url(int flags, const php_url *old_url, const php_url *new_url, php_url **url_ptr, char **url_str, size_t *url_len TSRMLS_DC);
-/* use this instead */
+PHP_HTTP_API php_http_url_t *php_http_url_parse_authority(const char *str, size_t len, unsigned flags TSRMLS_DC);
 PHP_HTTP_API php_http_url_t *php_http_url_mod(const php_http_url_t *old_url, const php_http_url_t *new_url, unsigned flags TSRMLS_DC);
+PHP_HTTP_API php_http_url_t *php_http_url_copy(const php_http_url_t *url, zend_bool persistent);
+PHP_HTTP_API php_http_url_t *php_http_url_from_struct(HashTable *ht);
+PHP_HTTP_API php_http_url_t *php_http_url_from_zval(zval *value, unsigned flags TSRMLS_DC);
+PHP_HTTP_API HashTable *php_http_url_to_struct(const php_http_url_t *url, zval *strct TSRMLS_DC);
+PHP_HTTP_API char *php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len, zend_bool persistent);
+PHP_HTTP_API char *php_http_url_authority_to_string(const php_http_url_t *url, char **url_str, size_t *url_len);
+PHP_HTTP_API void php_http_url_free(php_http_url_t **url);
 
 PHP_HTTP_API STATUS php_http_url_encode_hash(HashTable *hash, const char *pre_encoded_str, size_t pre_encoded_len, char **encoded_str, size_t *encoded_len TSRMLS_DC);
 PHP_HTTP_API STATUS php_http_url_encode_hash_ex(HashTable *hash, php_http_buffer_t *qstr, const char *arg_sep_str, size_t arg_sep_len, const char *val_sep_str, size_t val_sep_len, const char *pre_encoded_str, size_t pre_encoded_len TSRMLS_DC);
 
 static inline void php_http_url_argsep(const char **str, size_t *len TSRMLS_DC)
 {
-       if (SUCCESS != php_http_ini_entry(ZEND_STRL("arg_separator.output"), str, len, 0 TSRMLS_CC) || !*len) {
-               *str = PHP_HTTP_URL_ARGSEP;
-               *len = lenof(PHP_HTTP_URL_ARGSEP);
-       }
+       php_http_ini_entry(ZEND_STRL("arg_separator.output"), str, len, 0 TSRMLS_CC);
 }
 
-static inline php_url *php_http_url_to_php_url(php_http_url_t *url)
-{
-       php_url *purl = ecalloc(1, sizeof(*purl));
-
-       if (url->scheme)   purl->scheme   = estrdup(url->scheme);
-       if (url->pass)     purl->pass     = estrdup(url->pass);
-       if (url->user)     purl->user     = estrdup(url->user);
-       if (url->host)     purl->host     = estrdup(url->host);
-       if (url->path)     purl->path     = estrdup(url->path);
-       if (url->query)    purl->query    = estrdup(url->query);
-       if (url->fragment) purl->fragment = estrdup(url->fragment);
-
-       return purl;
+static inline zend_bool php_http_url_is_empty(const php_http_url_t *url) {
+       return !(url->scheme || url->pass || url->user || url->host || url->port ||     url->path || url->query || url->fragment);
 }
 
-PHP_HTTP_API php_http_url_t *php_http_url_from_struct(HashTable *ht TSRMLS_DC);
-PHP_HTTP_API HashTable *php_http_url_to_struct(const php_http_url_t *url, zval *strct TSRMLS_DC);
-PHP_HTTP_API void php_http_url_to_string(const php_http_url_t *url, char **url_str, size_t *url_len TSRMLS_DC);
-
 PHP_HTTP_API zend_class_entry *php_http_url_class_entry;
 PHP_MINIT_FUNCTION(http_url);
 
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 f4fcfc85367417dee7cb8f711fe39d89f18250b3..7adef9d3c897ce66f5923b981c74bd3b46a992d0 100644 (file)
@@ -27,7 +27,7 @@ php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major,
 php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str TSRMLS_DC)
 {
        long major, minor;
-       char separator = 0, *stop = NULL;
+       char separator = 0;
        register const char *ptr = str;
 
        switch (*ptr) {
@@ -40,16 +40,16 @@ php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *st
                ++ptr;
                /* no break */
        default:
-               major = strtol(ptr, &stop, 10);
-               if (stop && stop != ptr && major != LONG_MIN && major != LONG_MAX) {
-                       separator = *stop;
+               /* rfc7230#2.6 The HTTP version number consists of two decimal digits separated by a "." (period or decimal point) */
+               major = *ptr++ - '0';
+               if (major >= 0 && major <= 9) {
+                       separator = *ptr++;
                        if (separator) {
                                if (separator != '.' && separator != ',') {
-                                       php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr);
+                                       php_error_docref(NULL TSRMLS_CC, E_NOTICE, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, ptr - 2);
                                }
-                               ptr = stop + 1;
-                               minor = strtol(ptr, &stop, 10);
-                               if (minor != LONG_MIN && minor != LONG_MAX) {
+                               minor = *ptr - '0';
+                               if (minor >= 0 && minor <= 9) {
                                        return php_http_version_init(v, major, minor TSRMLS_CC);
                                }
                        }
index 0fd84f882b4f03a0c026452d18d056e475490bfa..30f9d7e9b16aaf8373fb375ba6426ef06368d46f 100644 (file)
@@ -12,7 +12,7 @@ header("WWW-Authenticate: none");
 $r = new http\Env\Response;
 $r->setResponseCode(200);
 $r->send();
-var_dump(http_response_code());
+var_dump(http\Env::getResponseCode());
 ?>
 --EXPECT--
 int(200)
\ No newline at end of file
diff --git a/tests/bug69000.phpt b/tests/bug69000.phpt
new file mode 100644 (file)
index 0000000..9b23e47
--- /dev/null
@@ -0,0 +1,17 @@
+--TEST--
+Bug #69000 (http\Url breaks down with very long URL query strings)
+--SKIPIF--
+<?php 
+include "skipif.inc";
+?>
+--FILE--
+<?php 
+echo "Test\n";
+echo new http\Url("http://foo.bar/?".str_repeat("a", 1024));
+?>
+
+===DONE===
+--EXPECT--
+Test
+http://foo.bar/?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+===DONE===
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..98fc8f8aebc34d9da458bbf1a252a10f208b8e83 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(array("pipelining" => true, "use_eventloop" => true));
+
        $client->enqueue($request);
+       $client->send();
+       
        $client->enqueue(clone $request);
        $client->enqueue(clone $request);
-       
+
        $client->send();
-       
+
        while ($client->getResponse()) {
                echo "R\n";
        }
-}
+});
 
 ?>
 Done
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 17c60ed61ec869b6b4bf8544e0e78f6e04c24b1a..59097420c72f5b35d63469a9d3fa588f4ed231af 100644 (file)
@@ -4,6 +4,7 @@ client history
 <?php 
 include "skipif.inc";
 skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php 
@@ -31,13 +32,8 @@ echo $client->getHistory()->toString(true);
 Done
 --EXPECTF--
 Test
-POST /ext-http/.print_request.php HTTP/1.1
-User-Agent: %s
-Host: dev.iworks.at
-Accept: */*
+POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1
 Content-Length: 6
-Content-Type: application/x-www-form-urlencoded
-X-Original-Content-Length: 6
 
 foobar
 HTTP/1.1 200 OK
@@ -50,13 +46,8 @@ Content-Length: 19
 
 string(6) "foobar"
 
-POST /ext-http/.print_request.php HTTP/1.1
-User-Agent: %s
-Host: dev.iworks.at
-Accept: */*
+POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1
 Content-Length: 6
-Content-Type: application/x-www-form-urlencoded
-X-Original-Content-Length: 6
 
 foobar
 HTTP/1.1 200 OK
@@ -69,13 +60,8 @@ Content-Length: 19
 
 string(6) "foobar"
 
-POST /ext-http/.print_request.php HTTP/1.1
-User-Agent: %s
-Host: dev.iworks.at
-Accept: */*
+POST http://dev.iworks.at/ext-http/.print_request.php HTTP/1.1
 Content-Length: 6
-Content-Type: application/x-www-form-urlencoded
-X-Original-Content-Length: 6
 
 foobar
 HTTP/1.1 200 OK
index ad6c2eb15f0d477cddc9e7712b6dd18999233176..306f26eb9d3101c52e7538b1b44d220e4b85374d 100644 (file)
@@ -4,20 +4,20 @@ client ssl
 <?php 
 include "skipif.inc";
 skip_online_test();
+skip_client_test();
 ?>
 --FILE--
 <?php 
-
 echo "Test\n";
 
 $client = new http\Client;
 
-$client->setSslOptions(array("verify_peer" => true));
-$client->addSslOptions(array("verify_host" => 2));
+$client->setSslOptions(array("verifypeer" => true));
+$client->addSslOptions(array("verifyhost" => 2));
 var_dump(
        array(
-               "verify_peer" => true,
-               "verify_host" => 2,
+               "verifypeer" => true,
+               "verifyhost" => 2,
        ) === $client->getSslOptions()
 );
 
@@ -26,7 +26,7 @@ $client->send();
 
 $ti = (array) $client->getTransferInfo($req);
 var_dump(array_key_exists("ssl_engines", $ti));
-var_dump(0 < count($ti["ssl_engines"]));
+var_dump(0 < count($ti["ssl_engines"] || $ti["tls_session"]["backend"] != "openssl"));
 ?>
 Done
 --EXPECTF--
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..f378653808824a2eb873857fe5242d8267fb86bc 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(array("use_eventloop" => true));
+       $client2->configure(array("use_eventloop" => true));
+       
+       $client1->enqueue(new http\Client\Request("GET", "http://localhost:$port/"));
+       $client2->enqueue(new http\Client\Request("GET", "http://localhost:$port/"));
+       
+       $client1->send();
+       
+       if (($r = $client1->getResponse())) {
+               var_dump($r->getTransferInfo("response_code"));
+       }
+       if (($r = $client2->getResponse())) {
+               var_dump($r->getTransferInfo("response_code"));
+       }
+       
+});
 ?>
 DONE
 --EXPECT--
index f50d9bb8cda5d0236e18936fff8eabeed87098f2..d2aef073d08d986cec6cdc839a315aafe17224bf 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(array("use_eventloop" => true));
+               $client->enqueue($request);
+       
+               while ($client->once()) {
+                       $client->wait(.1);
+               }
        
-       if (!$client->getResponse()) {
-               var_dump($client);
+               if (!$client->getResponse()) {
+                       var_dump($client);
+               }
        }
-}
+});
 ?>
 Done
 --EXPECT--
diff --git a/tests/client017.phpt b/tests/client017.phpt
new file mode 100644 (file)
index 0000000..a505028
--- /dev/null
@@ -0,0 +1,33 @@
+--TEST--
+client request gzip
+--SKIPIF--
+<?php 
+include "skipif.inc";
+skip_online_test();
+skip_client_test();
+?>
+--FILE--
+<?php 
+
+echo "Test\n";
+
+$client = new http\Client;
+$client->setOptions(array("compress" => true));
+
+$client->enqueue(new http\Client\Request("GET", "http://dev.iworks.at/ext-http/.print_request.php"));
+$client->send();
+
+echo $client->getResponse();
+
+?>
+===DONE===
+--EXPECTF--
+Test
+HTTP/1.1 200 OK
+Vary: Accept-Encoding
+Content-Type: text/html
+Date: %s
+Server: %s
+X-Original-Transfer-Encoding: chunked
+X-Original-Content-Encoding: gzip
+===DONE===
\ No newline at end of file
diff --git a/tests/client018.phpt b/tests/client018.phpt
new file mode 100644 (file)
index 0000000..c3ca9f9
--- /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(array("pipelining" => true, "max_host_connections" => 0));
+       
+       /* this is just to let curl know the server may be capable of pipelining */
+       $client->enqueue(new http\Client\Request("GET", "http://localhost:$port"));
+       $client->send();
+       
+       $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/1"));
+       $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/2"));
+       $client->enqueue(new http\Client\Request("GET", "http://localhost:$port/3"));
+       $client->send();
+       
+       while (($response = $client->getResponse())) {
+               echo $response;
+       }
+});
+
+?>
+===DONE===
+--EXPECT--
+Test
+HTTP/1.1 200 OK
+X-Req: /3
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+X-Req: /2
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+X-Req: /1
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+X-Req: /
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+===DONE===
diff --git a/tests/client019.phpt b/tests/client019.phpt
new file mode 100644 (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..1335aaa
--- /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(array("cookiestore" => $tmpfile));
+
+server("cookie.inc", function($port) use($request) {
+       $request->setOptions(array("port" => $port));
+       $client = new http\Client;
+       echo $client->requeue($request)->send()->getResponse();
+       echo $client->requeue($request)->send()->getResponse();
+       echo $client->requeue($request)->send()->getResponse();
+});
+
+server("cookie.inc", function($port) use($request) {
+       $request->setOptions(array("port" => $port));
+       $client = new http\Client;
+       echo $client->requeue($request)->send()->getResponse();
+       echo $client->requeue($request)->send()->getResponse();
+       echo $client->requeue($request)->send()->getResponse();
+});
+
+server("cookie.inc", function($port) use($request) {
+       $request->setOptions(array("port" => $port, "cookiesession" => true));
+       $client = new http\Client;
+       echo $client->requeue($request)->send()->getResponse();
+       echo $client->requeue($request)->send()->getResponse();
+       echo $client->requeue($request)->send()->getResponse();
+});
+
+server("cookie.inc", function($port) use($request) {
+       $request->setOptions(array("port" => $port, "cookiesession" => false));
+       $client = new http\Client;
+       echo $client->requeue($request)->send()->getResponse();
+       echo $client->requeue($request)->send()->getResponse();
+       echo $client->requeue($request)->send()->getResponse();
+});
+
+unlink($tmpfile);
+
+?>
+===DONE===
+--EXPECT--
+Test
+HTTP/1.1 200 OK
+Set-Cookie: counter=1;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=2;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=3;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=4;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=5;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=6;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=1;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=1;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=1;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=2;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=3;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+HTTP/1.1 200 OK
+Set-Cookie: counter=4;
+Etag: ""
+X-Original-Transfer-Encoding: chunked
+===DONE===
diff --git a/tests/client022.phpt b/tests/client022.phpt
new file mode 100644 (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..77ea7ce
--- /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(array($kk => array($vv, $oo[$kk])));
+                       }
+               }
+       } else if (isset($v) && $opt[$k] !== $v) {
+               var_dump(array($k => array($v, $opt[$k])));
+       }
+}
+var_dump($client === $client->configure($client->getAvailableConfiguration()));
+
+?>
+===DONE===
+--EXPECT--
+Test
+bool(true)
+===DONE===
diff --git a/tests/client024.phpt b/tests/client024.phpt
new file mode 100644 (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..866cd2e
--- /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(array("resume" => 1, "expect_100_timeout" => 0));
+       $request->getBody()->append("123");
+       echo $client->enqueue($request)->send()->getResponse();
+});
+
+?>
+===DONE===
+--EXPECTF--
+Test
+HTTP/1.1 200 OK
+Accept-Ranges: bytes
+Etag: "%x"
+X-Original-Transfer-Encoding: chunked
+Content-Length: %d
+
+PUT / HTTP/1.1
+Content-Range: bytes 1-2/3
+User-Agent: %s
+Host: localhost:%d
+Accept: */*
+Content-Length: 3
+Expect: 100-continue
+X-Original-Content-Length: 3
+
+23===DONE===
diff --git a/tests/client026.phpt b/tests/client026.phpt
new file mode 100644 (file)
index 0000000..1bafdc1
--- /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(array("timeout" => 10, "expect_100_timeout" => 0));
+       $client->enqueue($request);
+       $client->send();
+       var_dump($client->getResponse()->getHeaders());
+});
+
+?>
+===DONE===
+--EXPECTF--
+Test
+array(5) {
+  ["Accept-Ranges"]=>
+  string(5) "bytes"
+  ["Etag"]=>
+  string(%d) "%s"
+  ["Last-Modified"]=>
+  string(%d) "%s"
+  ["X-Original-Transfer-Encoding"]=>
+  string(7) "chunked"
+  ["Content-Length"]=>
+  int(134217%d%d%d)
+}
+===DONE===
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/envrequestcookie001.phpt b/tests/envrequestcookie001.phpt
new file mode 100644 (file)
index 0000000..00ac9df
--- /dev/null
@@ -0,0 +1,32 @@
+--TEST--
+env request cookie
+--SKIPIF--
+<?php 
+include "skipif.inc";
+?>
+--COOKIE--
+foo=bar;bar=123
+--FILE--
+<?php 
+echo "Test\n";
+
+$r = new http\Env\Request;
+var_dump($r->getCookie()->toArray());
+var_dump($r->getCookie("foo", "s"));
+var_dump($r->getCookie("bar", "i"));
+var_dump($r->getCookie("baz", "b", true));
+
+?>
+DONE
+--EXPECT--
+Test
+array(2) {
+  ["foo"]=>
+  string(3) "bar"
+  ["bar"]=>
+  string(3) "123"
+}
+string(3) "bar"
+int(123)
+bool(true)
+DONE
index deee3f2081b8d397fafb4a6a77259d46fb276862..da5801d06ac3f8bcfea5674cea1fe9f6ab4ca73d 100644 (file)
@@ -20,6 +20,7 @@ echo "bar";
 
 ob_end_flush();
 $r->send();
+?>
 --EXPECTHEADERS--
 Accept-Ranges: bytes
 Cache-Control: public,must-revalidate,max-age=0
index 76141c16258ef3f098d8c84d3e1585aa65f0e9bc..248b999becb07c5c0bf47a7b465d89865bb79de8 100644 (file)
@@ -22,10 +22,15 @@ var_dump(stream_get_contents($f));
 Done
 --EXPECT--
 Test
-string(77) "HTTP/1.1 200 OK
+string(115) "HTTP/1.1 200 OK
 Accept-Ranges: bytes
 Foo: bar, baz
 ETag: "8c736521"
+Transfer-Encoding: chunked
 
-foo"
+3
+foo
+0
+
+"
 Done
index 5495fe2ba2708a0ee1d24b78a3dea093f8001455..d41be58907a5eeb1c83f00d573a91d84c9e0e84e 100644 (file)
@@ -34,6 +34,11 @@ Accept-Ranges: bytes%c
 X-Powered-By: %s%c
 Content-Type: text/plain%c
 Content-Range: bytes 2-4/9%c
+Transfer-Encoding: chunked%c
 %c
-234"
+3%c
+234%c
+0%c
+%c
+"
 Done
index 379ab57a8eb72ee94149bfb9a3ff7aeda9e65316..acbda8901e42ce308d94be6fa98fa461c5c61d14 100644 (file)
@@ -18,6 +18,7 @@ $r->send($f);
 rewind($f);
 var_dump(stream_get_contents($f));
 
+?>
 --EXPECTREGEX--
 string\(\d+\) "HTTP\/1\.1 200 OK
 Accept-Ranges: bytes
@@ -26,5 +27,10 @@ Content-Encoding: gzip
 Vary: Accept-Encoding
 ETag: "\w+-\w+-\w+"
 Last-Modified: \w+, \d+ \w+ \d{4} \d\d:\d\d:\d\d GMT
+Transfer-Encoding: chunked
 
+d0
 \x1f\x8b\x08.+
+0
+
+"
index 2a337e4b59d53dc03fb9d01e79b0096be3b38efb..1ca5fad1da91cf168c15759bb5719ecfef4409ce 100644 (file)
@@ -24,7 +24,9 @@ Accept-Ranges: bytes%c
 X-Powered-By: %s%c
 ETag: "abc"%c
 Last-Modified: %s%c
+Transfer-Encoding: chunked%c
 %c
+e1%c
 <?php
 $f = tmpfile();
 $r = new http\Env\Response;
@@ -35,5 +37,8 @@ $r->send($f);
 rewind($f);
 var_dump(stream_get_contents($f));
 ?>
+%c
+0%c
+%c
 "
 
index d365497e4aa334b6e0e4520f21c47f214c77bfd4..9b040d55619d870a29d37cab9878314aeb035e43 100644 (file)
@@ -25,5 +25,10 @@ Accept-Ranges: bytes%c
 X-Powered-By: PHP/%s%c
 Content-Type: text/plain%c
 Content-Range: bytes 2-4/311%c
+Transfer-Encoding: chunked%c
 %c
-php"
+3%c
+php%c
+0%c
+%c
+"
index ef85e2c2f7cbc157f9dae7fa5c4f8f12bfb3f8d4..6b71aacaae5816d66c3882897e2bca4ee874c8af 100644 (file)
@@ -16,9 +16,12 @@ $res->send($f);
 rewind($f);
 var_dump(stream_get_contents($f));
 --EXPECTF--
-string(96) "HTTP/1.1 416 Requested Range Not Satisfiable
+string(129) "HTTP/1.1 416 Requested Range Not Satisfiable
 Accept-Ranges: bytes
 Content-Range: bytes */6
+Transfer-Encoding: chunked
+
+0
 
 "
 
index abad2bb095c36b5e4be4ff04cb40cdf2466843b2..1e8b15eae0b3042ef793173177b1c24738273560 100644 (file)
@@ -32,5 +32,10 @@ echo stream_get_contents($f);
 HTTP/1.1 200 OK
 Accept-Ranges: bytes
 ETag: "fc8305a1"
+Transfer-Encoding: chunked
 
+9
 foo: bar
+
+0
+
diff --git a/tests/envresponse017.phpt b/tests/envresponse017.phpt
new file mode 100644 (file)
index 0000000..4c832f2
--- /dev/null
@@ -0,0 +1,28 @@
+--TEST--
+env response stream: no chunked transfer encoding for CONNECTs
+--SKIPIF--
+<?php 
+include "skipif.inc";
+?>
+--FILE--
+<?php 
+echo "Test\n";
+
+$req = new http\Env\Request;
+$req->setRequestMethod("CONNECT");
+$req->setRequestUrl(array("host"=>"www.example.com", "port"=>80));
+
+echo $req;
+
+$res = new http\Env\Response;
+$res->setEnvRequest($req);
+$res->send(STDOUT);
+
+?>
+===DONE===
+--EXPECTF--
+Test
+CONNECT www.example.com:80 HTTP/1.1
+HTTP/1.1 200 OK
+
+===DONE===
diff --git a/tests/envresponse018.phpt b/tests/envresponse018.phpt
new file mode 100644 (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===
diff --git a/tests/envresponsecookie001.phpt b/tests/envresponsecookie001.phpt
new file mode 100644 (file)
index 0000000..c0e93ed
--- /dev/null
@@ -0,0 +1,29 @@
+--TEST--
+env response cookie
+--SKIPIF--
+<?php 
+include "skipif.inc";
+?>
+--FILE--
+<?php 
+
+$r = new http\Env\Response;
+$c = new http\Cookie;
+$c->addCookie("foo","bar");
+$c->setMaxAge(60);
+$r->setCookie($c);
+$r->setCookie("baz");
+$r->setCookie(123);
+$r->send(STDOUT);
+
+?>
+--EXPECT--
+HTTP/1.1 200 OK
+Set-Cookie: foo=bar; max-age=60; 
+Set-Cookie: baz=1; 
+Set-Cookie: 123=1; 
+ETag: ""
+Transfer-Encoding: chunked
+
+0
+
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..cdd55e4
--- /dev/null
@@ -0,0 +1,61 @@
+--TEST--
+header parser
+--SKIPIF--
+<?php
+include "skipif.inc";
+?>
+--FILE--
+<?php
+echo "Test\n";
+
+$headers = array(
+       "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 = array(-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..67e6681
--- /dev/null
@@ -0,0 +1,59 @@
+--TEST--
+header parser errors
+--SKIPIF--
+<?php 
+include "skipif.inc";
+?>
+--FILE--
+<?php 
+echo "Test\n";
+
+$headers = array(
+       "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..1216fac
--- /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 = array(
+"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..b7175c1
--- /dev/null
@@ -0,0 +1,26 @@
+<?php 
+
+include "server.inc";
+
+function respond($client, $msg) {
+       $r = new http\Env\Response;
+       $r->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..aa8559f
--- /dev/null
@@ -0,0 +1,99 @@
+<?php 
+
+// PHP-5.3
+if (!defined("PHP_BINARY")) {
+       define("PHP_BINARY", PHP_BINDIR.DIRECTORY_SEPARATOR."php");
+}
+
+function serve($cb) {
+       foreach (range(8000, 9000) as $port) {
+               if (($server = @stream_socket_server("tcp://localhost:$port"))) {
+                       fprintf(STDERR, "%s\n", $port);
+                       do {
+                               $R = array($server); $W = array(); $E = array();
+                               $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, $cb) {
+       proc(PHP_BINARY, array(__DIR__."/$handler"), $cb);
+}
+
+function nghttpd($cb) {
+       $spec = array(array("pipe","r"), array("pipe","w"), array("pipe","w"));
+       foreach (range(8000, 9000) as $port) {
+               $comm = "exec nghttpd -d html $port http2.key http2.crt";
+               if (($proc = proc_open($comm, $spec, $pipes, __DIR__))) {
+                       $stdin = $pipes[0];
+                       $stdout = $pipes[1];
+                       $stderr = $pipes[2];
+                       
+                       usleep(50000);
+                       $status = proc_get_status($proc);
+                       
+                       if (!$status["running"]) {
+                               continue;
+                       }
+                       
+                       try {
+                               $cb($port, $stdin, $stdout, $stderr);
+                       } catch (Exception $e) {
+                               echo $e,"\n";
+                       }
+               
+                       proc_terminate($proc);
+               
+                       fpassthru($stderr);
+                       fpassthru($stdout);
+                       return;
+               }
+       }
+                       
+}
+
+function proc($bin, $args, $cb) {
+       $spec = array(array("pipe","r"), array("pipe","w"), array("pipe","w"));
+       $comm = escapeshellcmd($bin) . " ". implode(" ", array_map("escapeshellarg", $args));
+       if (($proc = proc_open($comm, $spec, $pipes, __DIR__))) {
+               $stdin = $pipes[0];
+               $stdout = $pipes[1];
+               $stderr = $pipes[2];
+               
+               do {
+                       $port = trim(fgets($stderr));
+                       $R = array($stderr); $W = array(); $E = array();
+               } while (is_numeric($port) && stream_select($R, $W, $E, 0, 10000));
+       
+               if (is_numeric($port)) {
+                       try {
+                               $cb($port, $stdin, $stdout, $stderr);
+                       } catch (Exception $e) {
+                               echo $e,"\n";
+                       }
+               }
+       
+               proc_terminate($proc);
+       
+               fpassthru($stderr);
+               fpassthru($stdout);
+       }
+}
\ No newline at end of file
diff --git a/tests/info.phpt b/tests/info.phpt
deleted file mode 100644 (file)
index 373cb45..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 => 2.%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..093dcd1
--- /dev/null
@@ -0,0 +1,49 @@
+--TEST--
+invalid HTTP info
+--SKIPIF--
+<?php 
+include "skipif.inc";
+?>
+--FILE--
+<?php 
+
+echo "Test\n";
+
+function trap($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
index 0809676e8dc797273d595efb787ba841c142afba..e16f8dc97359741c01b5b2a07d4e4331acdc892e 100644 (file)
@@ -33,7 +33,7 @@ var_dump(file_get_contents("php://input"));
 Done
 --EXPECTF--
 Test
-object(%s)#%d (12) {
+object(%s)#%d (13) {
   ["type":protected]=>
   int(1)
   ["body":protected]=>
@@ -62,17 +62,25 @@ object(%s)#%d (12) {
   ["parentMessage":protected]=>
   NULL
   ["query":protected]=>
-  object(http\QueryString)#2 (1) {
+  object(http\QueryString)#%d (1) {
     ["queryArray":"http\QueryString":private]=>
     array(0) {
     }
   }
   ["form":protected]=>
-  object(http\QueryString)#3 (1) {
+  object(http\QueryString)#%d (1) {
     ["queryArray":"http\QueryString":private]=>
     array(0) {
     }
   }
+  ["cookie":protected]=>
+  object(http\QueryString)#%d (1) {
+    ["queryArray":"http\QueryString":private]=>
+    array(1) {
+      ["foo"]=>
+      string(3) "bar"
+    }
+  }
   ["files":protected]=>
   array(0) {
   }
diff --git a/tests/message016.phpt b/tests/message016.phpt
new file mode 100644 (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..1167c64115b2d85d8ef07958dad756f36f77e017 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 = array("START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "UPDATE_CL", "DONE");
+               printf("Unexpected state: %s (%s)\n", $s[$parser->getState()], $file);
+       }
 
        $parser = new Parser;
        rewind($fd);
@@ -38,12 +44,14 @@ foreach (glob(__DIR__."/data/message_*.txt") as $file) {
        if ($string !== (string) $message) {
                $a = explode("\n", $string);
                $b = explode("\n", (string) $message);
-               while ((null !== ($aa = array_shift($a))) || (null !== ($bb = array_shift($b)))) {
+               do {
+                       $aa = array_shift($a);
+                       $bb = array_shift($b);
                        if ($aa !== $bb) {
                                isset($aa) and printf("-- %s\n", $aa);
                                isset($bb) and printf("++ %s\n", $bb);
                        }
-               }
+               } while ($a || $b);
        }
 }
 ?>
index 2030e93cfd448147006b1557944c40ea1000685c..71f2b5e2b2741874893336cc0caf340d22fedd83 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"
   }
diff --git a/tests/phpinfo.phpt b/tests/phpinfo.phpt
new file mode 100644 (file)
index 0000000..373cb45
--- /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 => 2.%s
+%a
+Done
index 552a71318732377d67714b1e62c1f8f9cb327ffd..1001f8e5c3c981eccd6eb4a88a4d34091604fcc1 100644 (file)
@@ -4,6 +4,8 @@ property proxy
 <?php
 include "skipif.inc";
 ?>
+--XFAIL--
+TBD
 --FILE--
 <?php
 
index 28d9a1325ffb9c50a6f8746e611f4eaa12a359b4..4ab631e63d5b9b00b2e1be2c3efe174bfc3791bb 100644 (file)
@@ -8,8 +8,25 @@ function skip_online_test($message = "skip test requiring internet connection\n"
        }
 }
 
-function skip_slow_test($message = "skip slow test") {
+function skip_slow_test($message = "skip slow test\n") {
        if (getenv("SKIP_SLOW_TESTS")) {
                die($message);
        }
 }
+
+function skip_client_test($message = "skip need a client driver\n") {
+       if (!http\Client::getAvailableDrivers()) {
+               die($message);
+       }
+}
+
+function skip_http2_test($message = "skip need http2 support (nghttpd in PATH)\n") {
+       if (defined("http\\Client\\Curl\\HTTP_VERSION_2_0")) {
+               foreach (explode(":", $_ENV["PATH"]) as $path) {
+                       if (is_executable($path . "/nghttpd")) {
+                               return;
+                       }
+               }
+       }
+       die($message);
+}
\ No newline at end of file
index 5109d3ab60401b3fc1d67bd05cd91d6c079c4809..e1bc2504875273bcfc98f75593f222f5225d2a9e 100644 (file)
@@ -23,5 +23,5 @@ http://example.com:55555/?s=b&i=0&e=&a[]=1&a[]=2
 http://example.com:55555/index?s=b&i=0&e=&a[]=1&a[]=2
 https://example.com/?s=b&i=0&e=&a[]=1&a[]=2
 http://example.com:55555/index.php/
-http://localhost/
+
 DONE
index 73bd9d4e81b318b09ef478cec300ac823951c69a..e7ccc5999a5cdd31e2b737bbc3e6031cbac073e3 100644 (file)
@@ -21,7 +21,7 @@ $urls = array(
 
 foreach ($urls as $url) {
        printf("\n%s\n", $url);
-       var_dump(http\Url::parse($url));
+       var_dump(new http\Url($url, null, 0));
 }
 
 ?>
index be1cd66f5b93941670f379cc5f687f9f6f0fdea6..92114dce1d296f08c1fa510a5a909c5d8dfe59c7 100644 (file)
@@ -22,7 +22,7 @@ $urls = array(
 
 foreach ($urls as $url) {
        printf("\n%s\n", $url);
-       var_dump(http\Url::parse($url));
+       var_dump(new http\Url($url, null, 0));
 }
 ?>
 DONE
index 68b1e4a526be5fb9d6d3151ce40cd9e79524535f..9a368a75f2338d2a2539273c79b12c807bd5548d 100644 (file)
@@ -25,7 +25,7 @@ $urls = array(
 
 foreach ($urls as $url) {
        printf("\n%s\n", $url);
-       var_dump(http\Url::parse($url));
+       var_dump(new http\Url($url, null, 0));
 }
 ?>
 DONE
index 3aa57fd3daa4a99abc2d6441e571c60d4b81e10b..d1cd1eea061a1ac4f362b6e49d2dc8913bf1f5cd 100644 (file)
@@ -20,7 +20,7 @@ $urls = array(
 
 foreach ($urls as $url) {
        printf("\n%s\n", $url);
-       var_dump(http\Url::parse($url, http\Url::PARSE_MBLOC));
+       var_dump(new http\Url($url, null, http\Url::PARSE_MBLOC));
 }
 ?>
 DONE
index ff18fe48cd4533fc6aba922011fac695395a2b4a..d6ec61f11ea1f1c9a3b724a33ee4d095ebc0eec3 100644 (file)
@@ -16,7 +16,7 @@ $urls = array(
 
 foreach ($urls as $url) {
        printf("\n%s\n", $url);
-       var_dump(http\Url::parse($url, http\Url::PARSE_MBUTF8));
+       var_dump(new http\Url($url, null, http\Url::PARSE_MBUTF8));
 }
 ?>
 DONE
index 72ee3580ae360e1c0c7172c795dbf885c45b4a22..c47e6a2f60876bf47fc9aa4727a0dae55be11ff4 100644 (file)
@@ -21,7 +21,7 @@ $urls = array(
 
 foreach ($urls as $url) {
        printf("\n%s\n", $url);
-       var_dump(http\Url::parse($url, http\Url::PARSE_MBLOC|http\Url::PARSE_TOIDN));
+       var_dump(new http\Url($url, null, http\Url::PARSE_MBLOC|http\Url::PARSE_TOIDN));
 }
 ?>
 DONE
index 518bb725671c141c47bd0459036c956df223558f..d7ec657e533c71ca8cfa672424df2170cd14a3a9 100644 (file)
@@ -19,7 +19,7 @@ $urls = array(
 
 foreach ($urls as $url) {
        printf("\n%s\n", $url);
-       var_dump(http\Url::parse($url, http\Url::PARSE_MBUTF8|http\Url::PARSE_TOIDN));
+       var_dump(new http\Url($url, null, http\Url::PARSE_MBUTF8|http\Url::PARSE_TOIDN));
 }
 ?>
 DONE
index 98382f43c4cb97823923e0555bd2ccb7d3ab7750..9646fa0d67aae97b4c56020cdc52d983270878c1 100644 (file)
@@ -18,7 +18,7 @@ $urls = array(
 foreach ($urls as $url) {
        try {
                printf("\n%s\n", $url);
-               var_dump(http\Url::parse($url));
+               var_dump(new http\Url($url, null, 0));
        } catch (Exception $e) {
                echo $e->getMessage(),"\n";
        }
@@ -29,10 +29,10 @@ DONE
 Test
 
 s://[a:80
-http\Url::parse(): Failed to parse hostinfo; expected ']'
+http\Url::__construct(): Failed to parse hostinfo; expected ']'
 
 s://[0]
-http\Url::parse(): Failed to parse hostinfo; unexpected '['
+http\Url::__construct(): Failed to parse hostinfo; unexpected '['
 
 s://[::1]:80
 object(http\Url)#%d (8) {
index f3e2b831d993073eaff789841dccf0d08063d862..57080f4a999212ec82a36403b317b0fb1eacd417 100644 (file)
@@ -26,7 +26,7 @@ $urls = array(
 foreach ($urls as $url) {
        try {
                printf("\n%s\n", $url);
-               var_dump(http\Url::parse($url));
+               var_dump(new http\Url($url, null, 0));
        } catch (Exception $e) {
                echo $e->getMessage(),"\n";
        }
index a82b7a86df7a42258e9620655f6bc0f6bb41cb65..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(http\Url::parse($url, 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