import 2.0 devl branch, suitable for PHP-trunk
authorMichael Wallner <mike@php.net>
Tue, 7 Sep 2010 15:16:54 +0000 (15:16 +0000)
committerMichael Wallner <mike@php.net>
Tue, 7 Sep 2010 15:16:54 +0000 (15:16 +0000)
63 files changed:
CREDITS [new file with mode: 0644]
KnownIssues.txt [new file with mode: 0644]
LICENSE [new file with mode: 0644]
ThanksTo.txt [new file with mode: 0644]
config.m4 [new file with mode: 0644]
config9.m4 [new file with mode: 0644]
php_http.c [new file with mode: 0644]
php_http.h [new file with mode: 0644]
php_http_buffer.c [new file with mode: 0644]
php_http_buffer.h [new file with mode: 0644]
php_http_cookie.c [new file with mode: 0644]
php_http_cookie.h [new file with mode: 0644]
php_http_encoding.c [new file with mode: 0644]
php_http_encoding.h [new file with mode: 0644]
php_http_env.c [new file with mode: 0644]
php_http_env.h [new file with mode: 0644]
php_http_etag.c [new file with mode: 0644]
php_http_etag.h [new file with mode: 0644]
php_http_exception.c [new file with mode: 0644]
php_http_exception.h [new file with mode: 0644]
php_http_filter.c [new file with mode: 0644]
php_http_filter.h [new file with mode: 0644]
php_http_header_parser.c [new file with mode: 0644]
php_http_header_parser.h [new file with mode: 0644]
php_http_headers.c [new file with mode: 0644]
php_http_headers.h [new file with mode: 0644]
php_http_info.c [new file with mode: 0644]
php_http_info.h [new file with mode: 0644]
php_http_message.c [new file with mode: 0644]
php_http_message.h [new file with mode: 0644]
php_http_message_body.c [new file with mode: 0644]
php_http_message_body.h [new file with mode: 0644]
php_http_message_parser.c [new file with mode: 0644]
php_http_message_parser.h [new file with mode: 0644]
php_http_misc.c [new file with mode: 0644]
php_http_misc.h [new file with mode: 0644]
php_http_negotiate.c [new file with mode: 0644]
php_http_negotiate.h [new file with mode: 0644]
php_http_object.c [new file with mode: 0644]
php_http_object.h [new file with mode: 0644]
php_http_params.c [new file with mode: 0644]
php_http_params.h [new file with mode: 0644]
php_http_persistent_handle.c [new file with mode: 0644]
php_http_persistent_handle.h [new file with mode: 0644]
php_http_property_proxy.c [new file with mode: 0644]
php_http_property_proxy.h [new file with mode: 0644]
php_http_querystring.c [new file with mode: 0644]
php_http_querystring.h [new file with mode: 0644]
php_http_request.c [new file with mode: 0644]
php_http_request.h [new file with mode: 0644]
php_http_request_datashare.c [new file with mode: 0644]
php_http_request_datashare.h [new file with mode: 0644]
php_http_request_info.c [new file with mode: 0644]
php_http_request_method.c [new file with mode: 0644]
php_http_request_method.h [new file with mode: 0644]
php_http_request_pool.c [new file with mode: 0644]
php_http_request_pool.h [new file with mode: 0644]
php_http_strlist.c [new file with mode: 0644]
php_http_strlist.h [new file with mode: 0644]
php_http_url.c [new file with mode: 0644]
php_http_url.h [new file with mode: 0644]
php_http_version.c [new file with mode: 0644]
php_http_version.h [new file with mode: 0644]

diff --git a/CREDITS b/CREDITS
new file mode 100644 (file)
index 0000000..4ca42f7
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,2 @@
+HTTP extension for PHP
+Michael Wallner
diff --git a/KnownIssues.txt b/KnownIssues.txt
new file mode 100644 (file)
index 0000000..94a0a94
--- /dev/null
@@ -0,0 +1,29 @@
+Known Issues
+============
+$Id: KnownIssues.txt 292753 2009-12-29 12:30:43Z mike $
+
+Windows:
+       If you keep getting "SSL connect error" when trying to issue 
+               requests, try another (newer) libeay32.dll/ssleay32.dll pair.
+
+Internals:
+       Inflating raw deflated data causes a re-initialization of the inflate
+               stream where the corresponding window bits are modified to tell libz
+               to not check for zlib header bytes.  This is not preventable AFAICS.
+       LFS dependant parts of libcurl are left out because of off_t,
+               respectively off64_t confusion.
+       Persistent handles and "cookiestore" request option do interfere,
+               as libcurl saves the cookies to the file on curl_easy_destroy(),
+               cookies are not saved until the CURL handle will be recycled.
+                       Thus one would either need to
+                               * run PHP with http.persistent.handles.limit = 0
+                               * call http_persistent_handles_clean() every request
+                               * call $HttpRequest->flushCookies(), which is available
+                                 since libcurl v7.17.1 and does not work with the
+                                 procedural API
+                       Anyway, none of these options is really perfect, so using
+                       HttpRequestDatashare with cookies enabled is probably the
+                       best thing to do.
+       HTTP and Proxy authentication information (username/password) can not be
+               unset with NULL prior libcurl v7.19.6 and separate options for setting
+               username and password--which work--are only available since v7.19.6.
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..b3f886e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2004-2010, Michael Wallner <mike@iworks.at>.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright notice, 
+      this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright 
+      notice, this list of conditions and the following disclaimer in the 
+      documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/ThanksTo.txt b/ThanksTo.txt
new file mode 100644 (file)
index 0000000..8dbd1d8
--- /dev/null
@@ -0,0 +1,20 @@
+Thanks To
+=========
+$Id: ThanksTo.txt 275653 2009-02-12 13:11:05Z mike $
+
+People who repeatedly reported issues with this extension in a manner
+so they could be fixed in a reasonable way, or suggested useful features
+to implement, in alphabetical order:
+
+       Ilia Alshanetsky (ilia at php dot net)
+       Petr Czaderna (petr at hroch dot info)
+       David James (james82 at gmail dot com)
+       Thomas Landro Johnsen (thomas dot l dot johnsen at gmail dot com)
+       Clay Loveless (clay at killersoft dot com)
+       Felipe Pena (felipe at php dot net)
+       David Sklar (sklar at sklar dot com)
+       Travis Swicegood (travis at mashery dot com)
+       Alexey Zakhlestin (indeyets at gmail dot com)
+       Alexander Zhuravlev (zaa at zaa dot pp dot ru)
+
+Thanks a lot!
diff --git a/config.m4 b/config.m4
new file mode 100644 (file)
index 0000000..aeb9a97
--- /dev/null
+++ b/config.m4
@@ -0,0 +1,5 @@
+dnl phpize stub of config9.m4 for pecl/http
+dnl $Id: config.m4 214417 2006-06-07 21:05:34Z mike $
+dnl vim: noet ts=1 sw=1
+
+sinclude(config9.m4)
diff --git a/config9.m4 b/config9.m4
new file mode 100644 (file)
index 0000000..001d4f4
--- /dev/null
@@ -0,0 +1,423 @@
+dnl config.m4 for pecl/http
+dnl $Id: config9.m4 242664 2007-09-18 19:13:37Z mike $
+dnl vim: noet ts=1 sw=4
+
+PHP_ARG_WITH([http], [whether to enable extended HTTP support],
+[  --with-http             Enable extended HTTP support])
+PHP_ARG_WITH([http-zlib-dir], [],
+[  --with-http-zlib-dir[=DIR]     HTTP: where to find zlib], $PHP_HTTP, $PHP_HTTP)
+PHP_ARG_WITH([http-libcurl-dir], [],
+[  --with-http-libcurl-dir[=DIR]  HTTP: where to find libcurl], $PHP_HTTP, $PHP_HTTP)
+PHP_ARG_WITH([http-libevent-dir], [],
+[  --with-http-libevent-dir[=DIR] HTTP: where to find libevent], $PHP_HTTP_LIBCURL_DIR, "")
+
+if test "$PHP_HTTP" != "no"; then
+
+       ifdef([AC_PROG_EGREP], [
+               AC_PROG_EGREP
+       ], [
+               AC_CHECK_PROG(EGREP, egrep, egrep)
+       ])
+       ifdef([AC_PROG_SED], [
+               AC_PROG_SED
+       ], [
+               ifdef([LT_AC_PROG_SED], [
+                       LT_AC_PROG_SED
+               ], [
+                       AC_CHECK_PROG(SED, sed, sed)
+               ])
+       ])
+       
+       AC_PROG_CPP
+       
+       if test "$PHP_HTTP_SHARED_DEPS" != "no"; then
+               AC_DEFINE([PHP_HTTP_SHARED_DEPS], [1], [ ])
+       else
+               AC_DEFINE([PHP_HTTP_SHARED_DEPS], [0], [ ])
+       fi
+       
+       dnl
+       dnl HTTP_SHARED_DEP(name[, code-if-yes[, code-if-not]])
+       dnl
+       AC_DEFUN([HTTP_SHARED_DEP], [
+               extname=$1
+               haveext=$[PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)
+               
+               AC_MSG_CHECKING([whether to add a dependency on ext/$extname])
+               if test "$PHP_HTTP_SHARED_DEPS" = "no"; then
+                       AC_MSG_RESULT([no])
+                       $3
+               elif test "$haveext"; then
+                       AC_MSG_RESULT([yes])
+                       AC_DEFINE([PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__), [1], [ ])
+                       ifdef([PHP_ADD_EXTENSION_DEP], [
+                               PHP_ADD_EXTENSION_DEP([http], $1, true)
+                       ])
+                       $2
+               else
+                       AC_MSG_RESULT([no])
+                       $3
+               fi
+       ])
+       
+       dnl
+       dnl HTTP_HAVE_PHP_EXT(name[, code-if-yes[, code-if-not]])
+       dnl
+       AC_DEFUN([HTTP_HAVE_PHP_EXT], [
+               extname=$1
+               haveext=$[PHP_]translit($1,a-z_-,A-Z__)
+               
+               AC_MSG_CHECKING([for ext/$extname support])
+               if test -x "$PHP_EXECUTABLE"; then
+                       grepext=`$PHP_EXECUTABLE -m | $EGREP ^$extname\$`
+                       if test "$grepext" = "$extname"; then
+                               [PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)=1
+                               AC_MSG_RESULT([yes])
+                               $2
+                       else
+                               [PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)=
+                               AC_MSG_RESULT([no])
+                               $3
+                       fi
+               elif test "$haveext" != "no" && test "x$haveext" != "x"; then
+                       [PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)=1
+                       AC_MSG_RESULT([yes])
+                       $2
+               else
+                       [PHP_HTTP_HAVE_EXT_]translit($1,a-z_-,A-Z__)=
+                       AC_MSG_RESULT([no])
+                       $3
+               fi
+       ])
+       
+
+dnl ----
+dnl STDC
+dnl ----
+       AC_CHECK_HEADERS([netdb.h unistd.h])
+       PHP_CHECK_FUNC(gethostname, nsl)
+       PHP_CHECK_FUNC(getdomainname, nsl)
+       PHP_CHECK_FUNC(getservbyport, nsl)
+       PHP_CHECK_FUNC(getservbyname, nsl)
+
+dnl ----
+dnl ZLIB
+dnl ----
+       AC_MSG_CHECKING([for zlib.h])
+       ZLIB_DIR=
+       for i in "$PHP_HTTP_ZLIB_DIR" "$PHP_ZLIB_DIR" "$PHP_ZLIB" /usr/local /usr /opt; do
+               if test -f "$i/include/zlib.h"; then
+                       ZLIB_DIR=$i
+                       break;
+               fi
+       done
+       if test "x$ZLIB_DIR" = "x"; then
+               AC_MSG_RESULT([not found])
+               AC_MSG_ERROR([could not find zlib.h])
+       else
+               AC_MSG_RESULT([found in $ZLIB_DIR])
+               AC_MSG_CHECKING([for zlib version >= 1.2.0.4])
+               ZLIB_VERSION=`$EGREP "define ZLIB_VERSION" $ZLIB_DIR/include/zlib.h | $SED -e 's/[[^0-9\.]]//g'`
+               AC_MSG_RESULT([$ZLIB_VERSION])
+               if test `echo $ZLIB_VERSION | $SED -e 's/[[^0-9]]/ /g' | $AWK '{print $1*1000000 + $2*10000 + $3*100 + $4}'` -lt 1020004; then
+                       AC_MSG_ERROR([zlib version greater or equal to 1.2.0.4 required])
+               else
+                       PHP_ADD_INCLUDE($ZLIB_DIR/include)
+                       PHP_ADD_LIBRARY_WITH_PATH(z, $ZLIB_DIR/$PHP_LIBDIR, HTTP_SHARED_LIBADD)
+                       AC_DEFINE([PHP_HTTP_HAVE_ZLIB], [1], [Have zlib support])
+               fi
+       fi
+       
+dnl ----
+dnl CURL
+dnl ----
+       AC_MSG_CHECKING([for curl/curl.h])
+       CURL_DIR=
+       for i in "$PHP_HTTP_LIBCURL_DIR" /usr/local /usr /opt; do
+               if test -f "$i/include/curl/curl.h"; then
+                       CURL_DIR=$i
+                       break
+               fi
+       done
+       if test "x$CURL_DIR" = "x"; then
+               AC_MSG_RESULT([not found])
+               AC_MSG_ERROR([could not find curl/curl.h])
+       else
+               AC_MSG_RESULT([found in $CURL_DIR])
+       fi
+       
+       AC_MSG_CHECKING([for curl-config])
+       CURL_CONFIG=
+       for i in "$CURL_DIR/bin/curl-config" "$CURL_DIR/curl-config" `which curl-config`; do
+               if test -x "$i"; then
+                       CURL_CONFIG=$i
+                       break
+               fi
+       done
+       if test "x$CURL_CONFIG" = "x"; then
+               AC_MSG_RESULT([not found])
+               AC_MSG_ERROR([could not find curl-config])
+       else
+               AC_MSG_RESULT([found: $CURL_CONFIG])
+       fi
+       
+       dnl Debian stable has currently 7.18.2
+       AC_MSG_CHECKING([for curl version >= 7.18.2])
+       CURL_VERSION=`$CURL_CONFIG --version | $SED -e 's/[[^0-9\.]]//g'`
+       AC_MSG_RESULT([$CURL_VERSION])
+       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
+       
+       dnl
+       dnl compile tests
+       dnl
+       
+       save_INCLUDES="$INCLUDES"
+       INCLUDES=
+       save_LIBS="$LIBS"
+       LIBS=
+       save_CFLAGS="$CFLAGS"
+       CFLAGS=`$CURL_CONFIG --cflags`
+       save_LDFLAGS="$LDFLAGS"
+       LDFLAGS=`$CURL_CONFIG --libs`
+       LDFLAGS="$LDFLAGS $ld_runpath_switch$CURL_DIR/$PHP_LIBDIR"
+       
+       AC_MSG_CHECKING([for SSL support in libcurl])
+       CURL_SSL=`$CURL_CONFIG --feature | $EGREP SSL`
+       if test "$CURL_SSL" = "SSL"; then
+               AC_MSG_RESULT([yes])
+               AC_DEFINE([PHP_HTTP_HAVE_SSL], [1], [ ])
+               
+               AC_MSG_CHECKING([for openssl support in libcurl])
+               AC_TRY_RUN([
+                       #include <curl/curl.h>
+                       int main(int argc, char *argv[]) {
+                               curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
+                               if (data && data->ssl_version && *data->ssl_version) {
+                                       const char *ptr = data->ssl_version;
+                                       while(*ptr == ' ') ++ptr;
+                                       return strncasecmp(ptr, "OpenSSL", sizeof("OpenSSL")-1);
+                               }
+                               return 1;
+                       }
+               ], [
+                       AC_MSG_RESULT([yes])
+                       AC_CHECK_HEADER([openssl/crypto.h], [
+                               AC_DEFINE([PHP_HTTP_HAVE_OPENSSL], [1], [ ])
+                       ])
+               ], [
+                       AC_MSG_RESULT([no])
+               ], [
+                       AC_MSG_RESULT([no])
+               ])
+               
+               AC_MSG_CHECKING([for gnutls support in libcurl])
+               AC_TRY_RUN([
+                       #include <curl/curl.h>
+                       int main(int argc, char *argv[]) {
+                               curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
+                               if (data && data->ssl_version && *data->ssl_version) {
+                                       const char *ptr = data->ssl_version;
+                                       while(*ptr == ' ') ++ptr;
+                                       return strncasecmp(ptr, "GnuTLS", sizeof("GnuTLS")-1);
+                               }
+                               return 1;
+                       }
+               ], [
+                       AC_MSG_RESULT([yes])
+                       AC_CHECK_HEADER([gcrypt.h], [
+                               AC_DEFINE([PHP_HTTP_HAVE_GNUTLS], [1], [ ])
+                       ])
+               ], [
+                       AC_MSG_RESULT([no])
+               ], [
+                       AC_MSG_RESULT([no])
+               ])
+       else
+               AC_MSG_RESULT([no])
+       fi
+       
+       INCLUDES="$save_INCLUDES"
+       LIBS="$save_LIBS"
+       CFLAGS="$save_CFLAGS"
+       LDFLAGS="$save_LDFLAGS"
+       
+       dnl end compile tests
+       
+       AC_MSG_CHECKING([for bundled SSL CA info])
+       CURL_CAINFO=
+       for i in `$CURL_CONFIG --ca` "/etc/ssl/certs/ca-certificates.crt"; do
+               if test -f "$i"; then
+                       CURL_CAINFO="$i"
+                       break
+               fi
+       done
+       if test "x$CURL_CAINFO" = "x"; then
+               AC_MSG_RESULT([not found])
+       else
+               AC_MSG_RESULT([$CURL_CAINFO])
+               AC_DEFINE_UNQUOTED([PHP_HTTP_CURL_CAINFO], ["$CURL_CAINFO"], [path to bundled SSL CA info])
+       fi
+       
+       PHP_ADD_INCLUDE($CURL_DIR/include)
+       PHP_ADD_LIBRARY_WITH_PATH(curl, $CURL_DIR/$PHP_LIBDIR, HTTP_SHARED_LIBADD)
+       PHP_EVAL_LIBLINE(`$CURL_CONFIG --libs`, HTTP_SHARED_LIBADD)
+       AC_DEFINE([PHP_HTTP_HAVE_CURL], [1], [Have cURL support])
+       
+       dnl ----
+       dnl EVENT
+       dnl ----
+       
+       if test "$PHP_HTTP_LIBEVENT_DIR" != "no"; then
+               HTTP_HAVE_PHP_EXT([event], [
+                       AC_MSG_WARN([event support is incompatible with pecl/event; continuing without libevent support])
+               ], [
+                       AC_MSG_CHECKING([for event.h])
+                       EVENT_DIR=
+                       for i in "$PHP_HTTP_LIBEVENT_DIR" /usr/local /usr /opt; do
+                               if test -f "$i/include/event.h"; then
+                                       EVENT_DIR=$i
+                                       break
+                               fi
+                       done
+                       if test "x$EVENT_DIR" = "x"; then
+                               AC_MSG_RESULT([not found])
+                               AC_MSG_WARN([continuing without libevent support])
+                       else
+                               AC_MSG_RESULT([found in $EVENT_DIR])
+                               
+                               AC_MSG_CHECKING([for libevent version, roughly])
+                               EVENT_VER="1.1b or lower"
+                               if test -f "$EVENT_DIR/include/evhttp.h" && test -f "$EVENT_DIR/include/evdns.h"; then
+                                       if test -f "$EVENT_DIR/include/evrpc.h"; then
+                                               EVENT_VER="1.4 or greater"
+                                       else
+                                               EVENT_VER="1.2 or greater"
+                                       fi
+                               fi
+                               AC_DEFINE_UNQUOTED([PHP_HTTP_EVENT_VERSION], ["$EVENT_VER"], [ ])
+                               AC_MSG_RESULT([$EVENT_VER])
+                               
+                               PHP_ADD_INCLUDE($EVENT_DIR/include)
+                               PHP_ADD_LIBRARY_WITH_PATH(event, $EVENT_DIR/$PHP_LIBDIR, HTTP_SHARED_LIBADD)
+                               AC_DEFINE([PHP_HTTP_HAVE_EVENT], [1], [Have libevent support for cURL])
+                       fi
+               ])
+       fi
+
+PHP_ARG_WITH([http-shared-deps], [whether to depend on extensions which have been built shared],
+[  --without-http-shared-deps     HTTP: do not depend on extensions like hash
+                                        and iconv (when they're built shared)], $PHP_HTTP, $PHP_HTTP)
+dnl ----
+dnl HASH
+dnl ----
+       HTTP_HAVE_PHP_EXT([hash], [
+               AC_MSG_CHECKING([for php_hash.h])
+               HTTP_EXT_HASH_INCDIR=
+               for i in `echo $INCLUDES | $SED -e's/-I//g'` $abs_srcdir ../hash; do
+                       if test -d $i; then
+                               if test -f $i/php_hash.h; then
+                                       HTTP_EXT_HASH_INCDIR=$i
+                                       break
+                               elif test -f $i/ext/hash/php_hash.h; then
+                                       HTTP_EXT_HASH_INCDIR=$i/ext/hash
+                                       break
+                               fi
+                       fi
+               done
+               if test "x$HTTP_EXT_HASH_INCDIR" = "x"; then
+                       AC_MSG_RESULT([not found])
+               else
+                       AC_MSG_RESULT([$HTTP_EXT_HASH_INCDIR])
+                       AC_DEFINE([PHP_HTTP_HAVE_PHP_HASH_H], [1], [Have ext/hash support])
+                       PHP_ADD_INCLUDE([$HTTP_EXT_HASH_INCDIR])
+               fi
+       ])
+
+dnl ----
+dnl ICONV
+dnl ----
+       HTTP_HAVE_PHP_EXT([iconv])
+
+dnl ----
+dnl DONE
+dnl ----
+       PHP_HTTP_SOURCES="\
+               php_http.c \
+               php_http_buffer.c \
+               php_http_cookie.c \
+               php_http_encoding.c \
+               php_http_env.c \
+               php_http_etag.c \
+               php_http_exception.c \
+               php_http_filter.c \
+               php_http_headers.c \
+               php_http_header_parser.c \
+               php_http_info.c \
+               php_http_message_body.c \
+               php_http_message.c \
+               php_http_message_parser.c \
+               php_http_misc.c \
+               php_http_negotiate.c \
+               php_http_object.c \
+               php_http_params.c \
+               php_http_persistent_handle.c \
+               php_http_property_proxy.c \
+               php_http_querystring.c \
+               php_http_request.c \
+               php_http_request_datashare.c \
+               php_http_request_info.c \
+               php_http_request_method.c \
+               php_http_request_pool.c \
+               php_http_strlist.c \
+               php_http_url.c \
+               php_http_version.c \
+       "
+       PHP_NEW_EXTENSION([http], $PHP_HTTP_SOURCES, $ext_shared)
+       
+       dnl shared extension deps
+       HTTP_SHARED_DEP([hash])
+       HTTP_SHARED_DEP([iconv])
+       
+       PHP_SUBST([HTTP_SHARED_LIBADD])
+
+       PHP_HTTP_HEADERS="
+               php_http.h \
+               php_http_buffer.h \
+               php_http_cookie.h \
+               php_http_encoding.h \
+               php_http_env.h \
+               php_http_etag.h \
+               php_http_exception.h \
+               php_http_filter.h \
+               php_http_headers.h \
+               php_http_header_parser.h \
+               php_http_info.h \
+               php_http_message_body.h \
+               php_http_message.h \
+               php_http_message_parser.h \
+               php_http_misc.h \
+               php_http_negotiate.h \
+               php_http_object.h \
+               php_http_params.h \
+               php_http_persistent_handle.h \
+               php_http_property_proxy.h \
+               php_http_querystring.h \
+               php_http_request_datashare.h \
+               php_http_request.h \
+               php_http_request_method.h \
+               php_http_request_pool.h \
+               php_http_strlist.h \
+               php_http_url.h \
+               php_http_version.h \
+       "
+       ifdef([PHP_INSTALL_HEADERS], [
+               PHP_INSTALL_HEADERS(ext/http, $PHP_HTTP_HEADERS)
+       ], [
+               PHP_SUBST([PHP_HTTP_HEADERS])
+               PHP_ADD_MAKEFILE_FRAGMENT
+       ])
+
+       AC_DEFINE([HAVE_HTTP], [1], [Have extended HTTP support])
+fi
diff --git a/php_http.c b/php_http.c
new file mode 100644 (file)
index 0000000..273ef7b
--- /dev/null
@@ -0,0 +1,262 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http.c 300300 2010-06-09 07:29:35Z mike $ */
+
+#include "php_http.h"
+
+#include <main/php_ini.h>
+#include <ext/standard/info.h>
+#include <Zend/zend_extensions.h>
+
+ZEND_DECLARE_MODULE_GLOBALS(php_http);
+
+#ifdef COMPILE_DL_HTTP
+ZEND_GET_MODULE(http)
+#endif
+
+zend_function_entry http_functions[] = {
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_MINIT_FUNCTION(http);
+PHP_MSHUTDOWN_FUNCTION(http);
+PHP_RINIT_FUNCTION(http);
+PHP_RSHUTDOWN_FUNCTION(http);
+PHP_MINFO_FUNCTION(http);
+
+static zend_module_dep http_module_deps[] = {
+       ZEND_MOD_REQUIRED("spl")
+#ifdef PHP_HTTP_HAVE_HASH
+       ZEND_MOD_REQUIRED("hash")
+#endif
+#ifdef PHP_HTTP_HAVE_ICONV
+       ZEND_MOD_REQUIRED("iconv")
+#endif
+#ifdef PHP_HTTP_HAVE_EVENT
+       ZEND_MOD_CONFLICTS("event")
+#endif
+       {NULL, NULL, NULL, 0}
+};
+
+zend_module_entry http_module_entry = {
+       STANDARD_MODULE_HEADER_EX,
+       NULL,
+       http_module_deps,
+       "http",
+       http_functions,
+       PHP_MINIT(http),
+       PHP_MSHUTDOWN(http),
+       PHP_RINIT(http),
+       PHP_RSHUTDOWN(http),
+       PHP_MINFO(http),
+       PHP_HTTP_EXT_VERSION,
+       STANDARD_MODULE_PROPERTIES
+};
+
+int http_module_number;
+
+static void php_http_globals_init_once(zend_php_http_globals *G)
+{
+       memset(G, 0, sizeof(*G));
+}
+
+static inline void php_http_globals_init(zend_php_http_globals *G TSRMLS_DC)
+{
+}
+
+static inline void php_http_globals_free(zend_php_http_globals *G TSRMLS_DC)
+{
+}
+
+#if defined(ZTS) && defined(PHP_DEBUG)
+#if ZTS && PHP_DEBUG
+zend_http_globals *php_http_globals(void)
+{
+       TSRMLS_FETCH();
+       return PHP_HTTP_G;
+}
+#endif
+#endif
+PHP_INI_MH(http_update_persistent_handle_ident)
+{
+       PHP_HTTP_G->persistent_handle.ident.h = zend_hash_func(new_value, PHP_HTTP_G->persistent_handle.ident.l = new_value_length+1);
+       return OnUpdateString(entry, new_value, new_value_length, mh_arg1, mh_arg2, mh_arg3, stage TSRMLS_CC);
+}
+
+PHP_INI_BEGIN()
+       PHP_HTTP_INI_ENTRY("http.etag.mode", "md5", PHP_INI_ALL, OnUpdateString, env.etag_mode)
+       PHP_HTTP_INI_ENTRY("http.request_datashare.cookie", "0", PHP_INI_SYSTEM, OnUpdateBool, request_datashare.cookie)
+       PHP_HTTP_INI_ENTRY("http.request_datashare.dns", "1", PHP_INI_SYSTEM, OnUpdateBool, request_datashare.dns)
+       PHP_HTTP_INI_ENTRY("http.request_datashare.ssl", "0", PHP_INI_SYSTEM, OnUpdateBool, request_datashare.ssl)
+       PHP_HTTP_INI_ENTRY("http.request_datashare.connect", "0", PHP_INI_SYSTEM, OnUpdateBool, request_datashare.connect)
+       PHP_HTTP_INI_ENTRY("http.persistent_handle.limit", "-1", PHP_INI_SYSTEM, OnUpdateLong, persistent_handle.limit)
+       PHP_HTTP_INI_ENTRY("http.persistent_handle.ident", "GLOBAL", PHP_INI_ALL, http_update_persistent_handle_ident, persistent_handle.ident.s)
+PHP_INI_END()
+
+PHP_MINIT_FUNCTION(http)
+{
+       http_module_number = module_number;
+       ZEND_INIT_MODULE_GLOBALS(php_http, php_http_globals_init_once, NULL);
+       REGISTER_INI_ENTRIES();
+       
+       if (0
+       || SUCCESS != PHP_MINIT_CALL(http_object)
+       || SUCCESS != PHP_MINIT_CALL(http_exception)
+       || SUCCESS != PHP_MINIT_CALL(http_cookie)
+       || SUCCESS != PHP_MINIT_CALL(http_encoding)
+       || SUCCESS != PHP_MINIT_CALL(http_filter)
+       || SUCCESS != PHP_MINIT_CALL(http_message)
+       || SUCCESS != PHP_MINIT_CALL(http_message_body)
+       || SUCCESS != PHP_MINIT_CALL(http_persistent_handle)
+       || SUCCESS != PHP_MINIT_CALL(http_property_proxy)
+       || SUCCESS != PHP_MINIT_CALL(http_querystring)
+       || SUCCESS != PHP_MINIT_CALL(http_request)
+       || SUCCESS != PHP_MINIT_CALL(http_request_datashare)
+       || SUCCESS != PHP_MINIT_CALL(http_request_method)
+       || SUCCESS != PHP_MINIT_CALL(http_request_pool)
+       || SUCCESS != PHP_MINIT_CALL(http_url)
+       || SUCCESS != PHP_MINIT_CALL(http_env)
+       ) {
+               return FAILURE;
+       }
+       
+       return SUCCESS;
+}
+
+
+
+PHP_MSHUTDOWN_FUNCTION(http)
+{
+       UNREGISTER_INI_ENTRIES();
+       
+       if (0
+       || SUCCESS != PHP_MSHUTDOWN_CALL(http_message)
+       || SUCCESS != PHP_MSHUTDOWN_CALL(http_request)
+       || SUCCESS != PHP_MSHUTDOWN_CALL(http_request_datashare)
+       || SUCCESS != PHP_MSHUTDOWN_CALL(http_persistent_handle)
+       ) {
+               return FAILURE;
+       }
+       
+       return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(http)
+{
+       if (0
+       || SUCCESS != PHP_RINIT_CALL(http_env)
+       || SUCCESS != PHP_RINIT_CALL(http_request_datashare)
+       || SUCCESS != PHP_RINIT_CALL(http_request_pool)
+       ) {
+               return FAILURE;
+       }
+       
+       return SUCCESS;
+}
+
+PHP_RSHUTDOWN_FUNCTION(http)
+{
+       if (0
+       || SUCCESS != PHP_RSHUTDOWN_CALL(http_env)
+       || SUCCESS != PHP_RSHUTDOWN_CALL(http_request_datashare)
+       ) {
+               return FAILURE;
+       }
+       
+       return SUCCESS;
+}
+
+
+
+PHP_MINFO_FUNCTION(http)
+{
+       php_info_print_table_start();
+       {
+               php_info_print_table_header(2, "HTTP Support", "enabled");
+               php_info_print_table_row(2, "Extension Version", PHP_HTTP_EXT_VERSION);
+       }
+       php_info_print_table_end();
+       
+       php_info_print_table_start();
+       php_info_print_table_header(3, "Used Library", "Compiled", "Linked");
+       {
+#ifdef PHP_HTTP_HAVE_CURL
+               curl_version_info_data *cv = curl_version_info(CURLVERSION_NOW);
+               php_info_print_table_row(3, "libcurl", LIBCURL_VERSION, cv->version);
+#else
+               php_info_print_table_row(3, "libcurl", "disabled", "disabled");
+#endif
+#ifdef PHP_HTTP_HAVE_EVENT
+               php_info_print_table_row(3, "libevent", PHP_HTTP_EVENT_VERSION, event_get_version());
+#else
+               php_info_print_table_row(3, "libevent", "disabled", "disabled");
+#endif
+#ifdef PHP_HTTP_HAVE_ZLIB
+               php_info_print_table_row(3, "libz", ZLIB_VERSION, zlibVersion());
+#else
+               php_info_print_table_row(3, "libz", "disabled", "disabled");
+#endif
+       }
+       php_info_print_table_end();
+       
+       php_info_print_table_start();
+       php_info_print_table_colspan_header(4, "Persistent Handles");
+       php_info_print_table_header(4, "Provider", "Ident", "Used", "Free");
+       {
+               HashTable *ht;
+               HashPosition pos1, pos2;
+               php_http_array_hashkey_t provider = php_http_array_hashkey_init(0), ident = php_http_array_hashkey_init(0);
+               zval **val, **sub, **zused, **zfree;
+               
+               if ((ht = php_http_persistent_handle_statall(NULL TSRMLS_CC)) && zend_hash_num_elements(ht)) {
+                       FOREACH_HASH_KEYVAL(pos1, ht, provider, val) {
+                               if (zend_hash_num_elements(Z_ARRVAL_PP(val))) {
+                                       FOREACH_KEYVAL(pos2, *val, ident, sub) {
+                                               if (    SUCCESS == zend_hash_find(Z_ARRVAL_PP(sub), ZEND_STRS("used"), (void *) &zused) &&
+                                                               SUCCESS == zend_hash_find(Z_ARRVAL_PP(sub), ZEND_STRS("free"), (void *) &zfree)) {
+                                                       zval *used = php_http_zsep(IS_STRING, *zused);
+                                                       zval *free = php_http_zsep(IS_STRING, *zfree);
+                                                       php_info_print_table_row(4, provider.str, ident.str, Z_STRVAL_P(used), Z_STRVAL_P(free));
+                                                       zval_ptr_dtor(&used);
+                                                       zval_ptr_dtor(&free);
+                                               } else {
+                                                       php_info_print_table_row(4, provider.str, ident.str, "0", "0");
+                                               }
+                                       }
+                               } else {
+                                       php_info_print_table_row(4, provider.str, "N/A", "0", "0");
+                               }
+                       }
+               } else {
+                       php_info_print_table_row(4, "N/A", "N/A", "0", "0");
+               }
+               if (ht) {
+                       zend_hash_destroy(ht);
+                       FREE_HASHTABLE(ht);
+               }
+       }
+       php_info_print_table_end();
+       
+       DISPLAY_INI_ENTRIES();
+}
+
+
+/*
+ * 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.h b/php_http.h
new file mode 100644 (file)
index 0000000..67dcc35
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http.h 300300 2010-06-09 07:29:35Z mike $ */
+
+#ifndef PHP_EXT_HTTP_H
+#define PHP_EXT_HTTP_H
+
+#define PHP_HTTP_EXT_VERSION "2.0.0dev"
+
+#ifdef HAVE_CONFIG_H
+#      include "config.h"
+#else
+#      ifndef PHP_WIN32
+#              include "php_config.h"
+#      endif
+#endif
+
+#include "php.h"
+#if defined(PHP_WIN32)
+#      if defined(PHP_HTTP_EXPORTS)
+#              define PHP_HTTP_API __declspec(dllexport)
+#      elif defined(COMPILE_DL_HTTP)
+#              define PHP_HTTP_API __declspec(dllimport)
+#      else
+#              define PHP_HTTP_API
+#      endif
+#else
+#      define PHP_HTTP_API
+#endif
+
+#include <main/SAPI.h>
+#include <main/fopen_wrappers.h>
+#include <main/php_streams.h>
+#include <main/php_variables.h>
+#include <Zend/zend_exceptions.h>
+#include <Zend/zend_interfaces.h>
+#include <ext/date/php_date.h>
+#include <ext/spl/spl_array.h>
+#include <ext/spl/spl_iterators.h>
+#include <ext/standard/php_lcg.h>
+#include <ext/standard/php_string.h>
+#include <ext/standard/url.h>
+
+/* make functions that return SUCCESS|FAILURE more obvious */
+typedef int STATUS;
+
+#include "php_http_buffer.h"
+#include "php_http_strlist.h"
+
+#if (defined(HAVE_ICONV) || defined(PHP_HTTP_HAVE_EXT_ICONV)) && (PHP_HTTP_SHARED_DEPS || !defined(COMPILE_DL_ICONV))
+#      define PHP_HTTP_HAVE_ICONV
+#      undef PHP_ATOM_INC
+#      include <ext/iconv/php_iconv.h>
+#endif
+
+#if (defined(HAVE_HASH_EXT) || defined(PHP_HTTP_HAVE_EXT_HASH)) && (PHP_HTTP_SHARED_DEPS || !defined(COMPILE_DL_HASH)) && defined(PHP_HTTP_HAVE_PHP_HASH_H)
+#      define PHP_HTTP_HAVE_HASH
+#      include "php_hash.h"
+#endif
+
+#ifdef PHP_WIN32
+#      define CURL_STATICLIB
+#      define PHP_HTTP_HAVE_NETDB
+#      include <winsock2.h>
+#elif defined(HAVE_NETDB_H)
+#      define PHP_HTTP_HAVE_NETDB
+#      include <netdb.h>
+#      ifdef HAVE_UNISTD_H
+#              include <unistd.h>
+#      endif
+#endif
+
+#ifdef PHP_HTTP_HAVE_EVENT
+#      include <event.h>
+#endif
+
+#include <curl/curl.h>
+#define PHP_HTTP_CURL_VERSION(x, y, z) (LIBCURL_VERSION_NUM >= (((x)<<16) + ((y)<<8) + (z)))
+
+#if defined(ZTS) && defined(PHP_HTTP_HAVE_SSL)
+#      ifdef PHP_WIN32
+#              define PHP_HTTP_NEED_OPENSSL_TSL
+#              include <openssl/crypto.h>
+#      else /* !PHP_WIN32 */
+#              if defined(PHP_HTTP_HAVE_OPENSSL)
+#                      define PHP_HTTP_NEED_OPENSSL_TSL
+#                      include <openssl/crypto.h>
+#              elif defined(PHP_HTTP_HAVE_GNUTLS)
+#                      define PHP_HTTP_NEED_GNUTLS_TSL
+#                      include <gcrypt.h>
+#              else
+#                      warning \
+                               "libcurl was compiled with SSL support, but configure could not determine which" \
+                               "library was used; thus no SSL crypto locking callbacks will be set, which may " \
+                               "cause random crashes on SSL requests"
+#              endif /* PHP_HTTP_HAVE_OPENSSL || PHP_HTTP_HAVE_GNUTLS */
+#      endif /* PHP_WIN32 */
+#endif /* ZTS && PHP_HTTP_HAVE_SSL */
+
+#include <zlib.h>
+#include <ctype.h>
+#define PHP_HTTP_IS_CTYPE(type, c) is##type((int) (unsigned char) (c))
+#define PHP_HTTP_TO_CTYPE(type, c) to##type((int) (unsigned char) (c))
+
+extern zend_module_entry http_module_entry;
+#define phpext_http_ptr &http_module_entry
+
+extern int http_module_number;
+
+#include "php_http_misc.h"
+
+#include "php_http_cookie.h"
+#include "php_http_encoding.h"
+#include "php_http_env.h"
+#include "php_http_etag.h"
+#include "php_http_exception.h"
+#include "php_http_filter.h"
+#include "php_http_headers.h"
+#include "php_http_info.h"
+#include "php_http_header_parser.h"
+#include "php_http_message_body.h"
+#include "php_http_message.h"
+#include "php_http_message_parser.h"
+#include "php_http_negotiate.h"
+#include "php_http_object.h"
+#include "php_http_params.h"
+#include "php_http_persistent_handle.h"
+#include "php_http_property_proxy.h"
+#include "php_http_querystring.h"
+#include "php_http_request_datashare.h"
+#include "php_http_request.h"
+#include "php_http_request_method.h"
+#include "php_http_request_pool.h"
+#include "php_http_url.h"
+#include "php_http_version.h"
+
+ZEND_BEGIN_MODULE_GLOBALS(php_http)
+       struct php_http_env_globals env;
+       struct php_http_persistent_handle_globals persistent_handle;
+       struct php_http_request_datashare_globals request_datashare;
+       struct php_http_request_pool_globals request_pool;
+ZEND_END_MODULE_GLOBALS(php_http)
+
+ZEND_EXTERN_MODULE_GLOBALS(php_http);
+
+#ifdef ZTS
+#      include "TSRM/TSRM.h"
+#      define PHP_HTTP_G ((zend_http_globals *) (*((void ***) tsrm_ls))[TSRM_UNSHUFFLE_RSRC_ID(php_http_globals_id)])
+#else
+#      define PHP_HTTP_G (&php_http_globals)
+#endif
+
+
+#endif /* PHP_EXT_HTTP_H */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
+
diff --git a/php_http_buffer.c b/php_http_buffer.c
new file mode 100644 (file)
index 0000000..e65a8f8
--- /dev/null
@@ -0,0 +1,424 @@
+
+/* $Id: php_http_buffer.c 211942 2006-04-24 17:17:09Z mike $ */
+
+#include "php.h"
+#include "php_http_buffer.h"
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_init_ex(php_http_buffer *buf, size_t chunk_size, int flags)
+{
+       if (!buf) {
+               buf = pemalloc(sizeof(php_http_buffer), flags & PHP_HTTP_BUFFER_INIT_PERSISTENT);
+       }
+
+       if (buf) {
+               buf->size = (chunk_size) ? chunk_size : PHP_HTTP_BUFFER_DEFAULT_SIZE;
+               buf->pmem = (flags & PHP_HTTP_BUFFER_INIT_PERSISTENT) ? 1 : 0;
+               buf->data = (flags & PHP_HTTP_BUFFER_INIT_PREALLOC) ? pemalloc(buf->size, buf->pmem) : NULL;
+               buf->free = (flags & PHP_HTTP_BUFFER_INIT_PREALLOC) ? buf->size : 0;
+               buf->used = 0;
+       }
+       
+       return buf;
+}
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_from_string_ex(php_http_buffer *buf, const char *string, size_t length)
+{
+       if ((buf = php_http_buffer_init(buf))) {
+               if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(buf, string, length)) {
+                       pefree(buf, buf->pmem);
+                       buf = NULL;
+               }
+       }
+       return buf;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_resize_ex(php_http_buffer *buf, size_t len, size_t override_size, int allow_error)
+{
+       char *ptr = NULL;
+#if 0
+       fprintf(stderr, "RESIZE: size=%lu, used=%lu, free=%lu\n", buf->size, buf->used, buf->free);
+#endif
+       if (buf->free < len) {
+               size_t size = override_size ? override_size : buf->size;
+               
+               while ((size + buf->free) < len) {
+                       size <<= 1;
+               }
+               
+               if (allow_error) {
+                       ptr = perealloc_recoverable(buf->data, buf->used + buf->free + size, buf->pmem);
+               } else {
+                       ptr = perealloc(buf->data, buf->used + buf->free + size, buf->pmem);
+               }
+               
+               if (ptr) {
+                       buf->data = ptr;
+               } else {
+                       return PHP_HTTP_BUFFER_NOMEM;
+               }
+               
+               buf->free += size;
+               return size;
+       }
+       return 0;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_shrink(php_http_buffer *buf)
+{
+       /* avoid another realloc on fixation */
+       if (buf->free > 1) {
+               char *ptr = perealloc(buf->data, buf->used + 1, buf->pmem);
+               
+               if (ptr) {
+                       buf->data = ptr;
+               } else {
+                       return PHP_HTTP_BUFFER_NOMEM;
+               }
+               buf->free = 1;
+       }
+       return buf->used;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_append(php_http_buffer *buf, const char *append, size_t append_len)
+{
+       if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_resize(buf, append_len)) {
+               return PHP_HTTP_BUFFER_NOMEM;
+       }
+       memcpy(buf->data + buf->used, append, append_len);
+       buf->used += append_len;
+       buf->free -= append_len;
+       return append_len;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_appendf(php_http_buffer *buf, const char *format, ...)
+{
+       va_list argv;
+       char *append;
+       size_t append_len, alloc;
+
+       va_start(argv, format);
+       append_len = vspprintf(&append, 0, format, argv);
+       va_end(argv);
+
+       alloc = php_http_buffer_append(buf, append, append_len);
+       efree(append);
+
+       if (PHP_HTTP_BUFFER_NOMEM == alloc) {
+               return PHP_HTTP_BUFFER_NOMEM;
+       }
+       return append_len;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_insert(php_http_buffer *buf, const char *insert, size_t insert_len, size_t offset)
+{
+       if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_resize(buf, insert_len)) {
+               return PHP_HTTP_BUFFER_NOMEM;
+       }
+       memmove(buf->data + offset + insert_len, buf->data + offset, insert_len);
+       memcpy(buf->data + offset, insert, insert_len);
+       buf->used += insert_len;
+       buf->free -= insert_len;
+       return insert_len;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_insertf(php_http_buffer *buf, size_t offset, const char *format, ...)
+{
+       va_list argv;
+       char *insert;
+       size_t insert_len, alloc;
+
+       va_start(argv, format);
+       insert_len = vspprintf(&insert, 0, format, argv);
+       va_end(argv);
+
+       alloc = php_http_buffer_insert(buf, insert, insert_len, offset);
+       efree(insert);
+
+       if (PHP_HTTP_BUFFER_NOMEM == alloc) {
+               return PHP_HTTP_BUFFER_NOMEM;
+       }
+       return insert_len;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_prepend(php_http_buffer *buf, const char *prepend, size_t prepend_len)
+{
+       if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_resize(buf, prepend_len)) {
+               return PHP_HTTP_BUFFER_NOMEM;
+       }
+       memmove(buf->data + prepend_len, buf->data, buf->used);
+       memcpy(buf->data, prepend, prepend_len);
+       buf->used += prepend_len;
+       buf->free -= prepend_len;
+       return prepend_len;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_prependf(php_http_buffer *buf, const char *format, ...)
+{
+       va_list argv;
+       char *prepend;
+       size_t prepend_len, alloc;
+
+       va_start(argv, format);
+       prepend_len = vspprintf(&prepend, 0, format, argv);
+       va_end(argv);
+
+       alloc = php_http_buffer_prepend(buf, prepend, prepend_len);
+       efree(prepend);
+
+       if (PHP_HTTP_BUFFER_NOMEM == alloc) {
+               return PHP_HTTP_BUFFER_NOMEM;
+       }
+       return prepend_len;
+}
+
+PHP_HTTP_BUFFER_API char *php_http_buffer_data(const php_http_buffer *buf, char **into, size_t *len)
+{
+       char *copy = ecalloc(1, buf->used + 1);
+       memcpy(copy, buf->data, buf->used);
+       if (into) {
+               *into = copy;
+       }
+       if (len) {
+               *len = buf->used;
+       }
+       return copy;
+}
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_dup(const php_http_buffer *buf)
+{
+       php_http_buffer *dup = php_http_buffer_clone(buf);
+       if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(dup, buf->data, buf->used)) {
+               php_http_buffer_free(&dup);
+       }
+       return dup;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_cut(php_http_buffer *buf, size_t offset, size_t length)
+{
+       if (offset > buf->used) {
+               return 0;
+       }
+       if (offset + length > buf->used) {
+               length = buf->used - offset;
+       }
+       memmove(buf->data + offset, buf->data + offset + length, buf->used - length - offset);
+       buf->used -= length;
+       buf->free += length;
+       return length;
+}
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_sub(const php_http_buffer *buf, size_t offset, size_t length)
+{
+       if (offset >= buf->used) {
+               return NULL;
+       } else {
+               size_t need = 1 + ((length + offset) > buf->used ? (buf->used - offset) : (length - offset));
+               php_http_buffer *sub = php_http_buffer_init_ex(NULL, need, PHP_HTTP_BUFFER_INIT_PREALLOC | (buf->pmem ? PHP_HTTP_BUFFER_INIT_PERSISTENT:0));
+               if (sub) {
+                       if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(sub, buf->data + offset, need)) {
+                               php_http_buffer_free(&sub);
+                       } else {
+                               sub->size = buf->size;
+                       }
+               }
+               return sub;
+       }
+}
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_right(const php_http_buffer *buf, size_t length)
+{
+       if (length < buf->used) {
+               return php_http_buffer_sub(buf, buf->used - length, length);
+       } else {
+               return php_http_buffer_sub(buf, 0, buf->used);
+       }
+}
+
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_merge_va(php_http_buffer *buf, unsigned argc, va_list argv)
+{
+       unsigned i = 0;
+       buf = php_http_buffer_init(buf);
+
+       if (buf) {
+               while (argc > i++) {
+                       php_http_buffer_free_t f = va_arg(argv, php_http_buffer_free_t);
+                       php_http_buffer *current = va_arg(argv, php_http_buffer *);
+                       php_http_buffer_append(buf, current->data, current->used);
+                       FREE_PHP_HTTP_BUFFER(f, current);
+               }
+       }
+
+       return buf;
+}
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_merge_ex(php_http_buffer *buf, unsigned argc, ...)
+{
+       va_list argv;
+       php_http_buffer *ret;
+
+       va_start(argv, argc);
+       ret = php_http_buffer_merge_va(buf, argc, argv);
+       va_end(argv);
+       return ret;
+}
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_merge(unsigned argc, ...)
+{
+       va_list argv;
+       php_http_buffer *ret;
+
+       va_start(argv, argc);
+       ret = php_http_buffer_merge_va(NULL, argc, argv);
+       va_end(argv);
+       return ret;
+}
+
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_fix(php_http_buffer *buf)
+{
+       if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_resize_ex(buf, 1, 1, 0)) {
+               return NULL;
+       }
+       buf->data[buf->used] = '\0';
+       return buf;
+}
+
+PHP_HTTP_BUFFER_API int php_http_buffer_cmp(php_http_buffer *left, php_http_buffer *right)
+{
+       if (left->used > right->used) {
+               return -1;
+       } else if (right->used > left->used) {
+               return 1;
+       } else {
+               return memcmp(left->data, right->data, left->used);
+       }
+}
+
+PHP_HTTP_BUFFER_API void php_http_buffer_reset(php_http_buffer *buf)
+{
+       buf->free += buf->used;
+       buf->used = 0;
+}
+
+PHP_HTTP_BUFFER_API void php_http_buffer_dtor(php_http_buffer *buf)
+{
+       if (buf->data) {
+               pefree(buf->data, buf->pmem);
+               buf->data = NULL;
+       }
+       buf->used = 0;
+       buf->free = 0;
+}
+
+PHP_HTTP_BUFFER_API void php_http_buffer_free(php_http_buffer **buf)
+{
+       if (*buf) {
+               php_http_buffer_dtor(*buf);
+               pefree(*buf, (*buf)->pmem);
+               *buf = NULL;
+       }
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_chunk_buffer(php_http_buffer **s, const char *data, size_t data_len, char **chunk, size_t chunk_size)
+{
+       php_http_buffer *storage;
+       
+       *chunk = NULL;
+       
+       if (!*s) {
+               *s = php_http_buffer_init_ex(NULL, chunk_size << 1, chunk_size ? PHP_HTTP_BUFFER_INIT_PREALLOC : 0);
+       }
+       storage = *s;
+       
+       if (data_len) {
+               php_http_buffer_append(storage, data, data_len);
+       }
+       
+       if (!chunk_size) {
+               php_http_buffer_data(storage, chunk, &chunk_size);
+               php_http_buffer_free(s);
+               return chunk_size;
+       }
+       
+       if (storage->used >= (chunk_size = storage->size >> 1)) {
+               *chunk = estrndup(storage->data, chunk_size);
+               php_http_buffer_cut(storage, 0, chunk_size);
+               return chunk_size;
+       }
+       
+       return 0;
+}
+
+PHP_HTTP_BUFFER_API void php_http_buffer_chunked_output(php_http_buffer **s, const char *data, size_t data_len, size_t chunk_len, php_http_buffer_pass_func_t passout, void *opaque TSRMLS_DC)
+{
+       char *chunk = NULL;
+       size_t got = 0;
+       
+       while ((got = php_http_buffer_chunk_buffer(s, data, data_len, &chunk, chunk_len))) {
+               passout(opaque, chunk, got TSRMLS_CC);
+               if (!chunk_len) {
+                       /*      we already got the last chunk,
+                               and freed all resources */
+                       break;
+               }
+               data = NULL;
+               data_len = 0;
+               STR_SET(chunk, NULL);
+       }
+       STR_FREE(chunk);
+}
+
+PHP_HTTP_BUFFER_API ssize_t php_http_buffer_passthru(php_http_buffer *s, size_t chunk_size, php_http_buffer_pass_func_t passin, void *passin_arg, php_http_buffer_pass_func_t passon, void *passon_arg TSRMLS_DC)
+{
+       size_t passed_on = 0, passed_in = php_http_buffer_chunked_input(&s, chunk_size, passin, passin_arg TSRMLS_CC);
+
+       if (passed_in == PHP_HTTP_BUFFER_PASS0) {
+               return passed_in;
+       }
+       if (passed_in) {
+               passed_on = passon(passon_arg, s->data, passed_in TSRMLS_CC);
+
+               if (passed_on == PHP_HTTP_BUFFER_PASS0) {
+                       return passed_on;
+               }
+
+               if (passed_on) {
+                       php_http_buffer_cut(s, 0, passed_on);
+               }
+       }
+
+       return passed_on - passed_in;
+}
+
+PHP_HTTP_BUFFER_API size_t php_http_buffer_chunked_input(php_http_buffer **s, size_t chunk_size, php_http_buffer_pass_func_t passin, void *opaque TSRMLS_DC)
+{
+       php_http_buffer *str;
+       size_t passed;
+
+       if (!*s) {
+               *s = php_http_buffer_init_ex(NULL, chunk_size, chunk_size ? PHP_HTTP_BUFFER_INIT_PREALLOC : 0);
+       }
+       str = *s;
+
+       php_http_buffer_resize(str, chunk_size);
+       passed = passin(opaque, str->data + str->used, chunk_size TSRMLS_CC);
+
+       if (passed != PHP_HTTP_BUFFER_PASS0) {
+               str->used += passed;
+               str->free -= passed;
+       }
+
+       php_http_buffer_fix(str);
+
+       return passed;
+}
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
+
diff --git a/php_http_buffer.h b/php_http_buffer.h
new file mode 100644 (file)
index 0000000..57729bb
--- /dev/null
@@ -0,0 +1,229 @@
+
+/* $Id: php_http_buffer.h 229282 2007-02-07 15:31:50Z mike $ */
+
+#ifndef _PHP_HTTP_BUFFER_H
+#define _PHP_HTTP_BUFFER_H
+
+#ifndef PHP_HTTP_BUFFER_DEFAULT_SIZE
+#      define PHP_HTTP_BUFFER_DEFAULT_SIZE 256
+#endif
+
+#define PHP_HTTP_BUFFER_ERROR ((size_t) -1)
+#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) \
+       { \
+               if (STR) { \
+                       efree(STR); \
+               } \
+       }
+#endif
+#ifndef STR_SET
+#      define STR_SET(STR, SET) \
+       { \
+               STR_FREE(STR); \
+               STR = SET; \
+       }
+#endif
+#ifndef TSRMLS_D
+#      define TSRMLS_D
+#      define TSRMLS_DC
+#      define TSRMLS_CC
+#      define TSRMLS_C
+#endif
+#ifdef PHP_ATTRIBUTE_FORMAT
+#      define PHP_HTTP_BUFFER_ATTRIBUTE_FORMAT(f, a, b) PHP_ATTRIBUTE_FORMAT(f, a, b)
+#else
+#      define PHP_HTTP_BUFFER_ATTRIBUTE_FORMAT(f, a, b)
+#endif
+#ifndef pemalloc
+#      define pemalloc(s,p)    malloc(s)
+#      define pefree(x,p)              free(x)
+#      define perealloc(x,s,p) realloc(x,s)
+#      define perealloc_recoverable perealloc
+#      define ecalloc calloc
+static inline void *estrndup(void *p, size_t s)
+{
+       char *r = (char *) malloc(s+1);
+       if (r) memcpy((void *) r, p, s), r[s] = '\0';
+       return (void *) r;
+}
+#endif
+
+#if defined(PHP_WIN32)
+#      if defined(PHP_HTTP_BUFFER_EXPORTS)
+#              define PHP_HTTP_BUFFER_API __declspec(dllexport)
+#      elif defined(COMPILE_DL_PHP_HTTP_BUFFER)
+#              define PHP_HTTP_BUFFER_API __declspec(dllimport)
+#      else
+#              define PHP_HTTP_BUFFER_API
+#      endif
+#else
+#      define PHP_HTTP_BUFFER_API
+#endif
+
+#define PHP_HTTP_BUFFER(p) ((php_http_buffer *) (p))
+#define PHP_HTTP_BUFFER_VAL(p) (PHP_HTTP_BUFFER(p))->data
+#define PHP_HTTP_BUFFER_LEN(p) (PHP_HTTP_BUFFER(p))->used
+
+#define FREE_PHP_HTTP_BUFFER_PTR(STR) pefree(STR, STR->pmem)
+#define FREE_PHP_HTTP_BUFFER_VAL(STR) php_http_buffer_dtor(STR)
+#define FREE_PHP_HTTP_BUFFER_ALL(STR) php_http_buffer_free(&(STR))
+#define FREE_PHP_HTTP_BUFFER(free, STR) \
+       switch (free) \
+       { \
+               case PHP_HTTP_BUFFER_FREE_NOT:                                                  break; \
+               case PHP_HTTP_BUFFER_FREE_PTR:  pefree(STR, STR->pmem); break; \
+               case PHP_HTTP_BUFFER_FREE_VAL:  php_http_buffer_dtor(STR);              break; \
+               case PHP_HTTP_BUFFER_FREE_ALL: \
+               { \
+                       php_http_buffer *PTR = (STR); \
+                       php_http_buffer_free(&PTR); \
+               } \
+               break; \
+               default:                                                                                break; \
+       }
+
+#define RETURN_PHP_HTTP_BUFFER_PTR(STR) RETURN_PHP_HTTP_BUFFER((STR), PHP_HTTP_BUFFER_FREE_PTR, 0)
+#define RETURN_PHP_HTTP_BUFFER_VAL(STR) RETURN_PHP_HTTP_BUFFER((STR), PHP_HTTP_BUFFER_FREE_NOT, 0)
+#define RETURN_PHP_HTTP_BUFFER_DUP(STR) RETURN_PHP_HTTP_BUFFER((STR), PHP_HTTP_BUFFER_FREE_NOT, 1)
+#define RETVAL_PHP_HTTP_BUFFER_PTR(STR) RETVAL_PHP_HTTP_BUFFER((STR), PHP_HTTP_BUFFER_FREE_PTR, 0)
+#define RETVAL_PHP_HTTP_BUFFER_VAL(STR) RETVAL_PHP_HTTP_BUFFER((STR), PHP_HTTP_BUFFER_FREE_NOT, 0)
+#define RETVAL_PHP_HTTP_BUFFER_DUP(STR) RETVAL_PHP_HTTP_BUFFER((STR), PHP_HTTP_BUFFER_FREE_NOT, 1)
+/* RETURN_PHP_HTTP_BUFFER(buf, PHP_HTTP_BUFFER_FREE_PTR, 0) */
+#define RETURN_PHP_HTTP_BUFFER(STR, free, dup) \
+       RETVAL_PHP_HTTP_BUFFER((STR), (free), (dup)); \
+       return;
+
+#define RETVAL_PHP_HTTP_BUFFER(STR, free, dup) \
+       php_http_buffer_fix(STR); \
+       RETVAL_STRINGL((STR)->data, (STR)->used, (dup)); \
+       FREE_PHP_HTTP_BUFFER((free), (STR));
+
+typedef struct _php_http_buffer_t {
+       char  *data;
+       size_t used;
+       size_t free;
+       size_t size;
+       unsigned pmem:1;
+       unsigned reserved:31;
+} php_http_buffer;
+
+typedef enum _php_http_buffer_free_t {
+       PHP_HTTP_BUFFER_FREE_NOT = 0,
+       PHP_HTTP_BUFFER_FREE_PTR,       /* pefree() */
+       PHP_HTTP_BUFFER_FREE_VAL,       /* php_http_buffer_dtor() */
+       PHP_HTTP_BUFFER_FREE_ALL                /* php_http_buffer_free() */
+} php_http_buffer_free_t;
+
+#define PHP_HTTP_BUFFER_ALL_FREE(STR) PHP_HTTP_BUFFER_FREE_ALL,(STR)
+#define PHP_HTTP_BUFFER_PTR_FREE(STR) PHP_HTTP_BUFFER_FREE_PTR,(STR)
+#define PHP_HTTP_BUFFER_VAL_FREE(STR) PHP_HTTP_BUFFER_FREE_VAL,(STR)
+#define PHP_HTTP_BUFFER_NOT_FREE(STR) PHP_HTTP_BUFFER_FREE_NOT,(STR)
+
+#define PHP_HTTP_BUFFER_INIT_PREALLOC  0x01
+#define PHP_HTTP_BUFFER_INIT_PERSISTENT        0x02
+
+/* create a new php_http_buffer */
+#define php_http_buffer_new() php_http_buffer_init(NULL)
+#define php_http_buffer_init(b) php_http_buffer_init_ex(b, PHP_HTTP_BUFFER_DEFAULT_SIZE, 0)
+#define php_http_buffer_clone(php_http_buffer_pointer) php_http_buffer_init_ex(NULL, (php_http_buffer_pointer)->size, (php_http_buffer_pointer)->pmem ? PHP_HTTP_BUFFER_INIT_PERSISTENT:0)
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_init_ex(php_http_buffer *buf, size_t chunk_size, int flags);
+
+/* create a php_http_buffer from a zval or c-string */
+#define php_http_buffer_from_zval(z) php_http_buffer_from_string(Z_STRVAL(z), Z_STRLEN(z))
+#define php_http_buffer_from_zval_ex(b, z) php_http_buffer_from_string_ex(b, Z_STRVAL(z), Z_STRLEN(z))
+#define php_http_buffer_from_string(s, l) php_http_buffer_from_string_ex(NULL, (s), (l))
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_from_string_ex(php_http_buffer *buf, const char *string, size_t length);
+
+/* usually only called from within the internal functions */
+#define php_http_buffer_resize(b, s) php_http_buffer_resize_ex((b), (s), 0, 0)
+PHP_HTTP_BUFFER_API size_t php_http_buffer_resize_ex(php_http_buffer *buf, size_t len, size_t override_size, int allow_error);
+
+/* shrink memory chunk to actually used size (+1) */
+PHP_HTTP_BUFFER_API size_t php_http_buffer_shrink(php_http_buffer *buf);
+
+/* append data to the php_http_buffer */
+#define php_http_buffer_appends(b, a) php_http_buffer_append((b), (a), sizeof(a)-1)
+#define php_http_buffer_appendl(b, a) php_http_buffer_append((b), (a), strlen(a))
+PHP_HTTP_BUFFER_API size_t php_http_buffer_append(php_http_buffer *buf, const char *append, size_t append_len);
+PHP_HTTP_BUFFER_API size_t php_http_buffer_appendf(php_http_buffer *buf, const char *format, ...) PHP_HTTP_BUFFER_ATTRIBUTE_FORMAT(printf, 2, 3);
+
+/* insert data at a specific position of the php_http_buffer */
+#define php_http_buffer_inserts(b, i, o) php_http_buffer_insert((b), (i), sizeof(i)-1, (o))
+#define php_http_buffer_insertl(b, i, o) php_http_buffer_insert((b), (i), strlen(i), (o))
+PHP_HTTP_BUFFER_API size_t php_http_buffer_insert(php_http_buffer *buf, const char *insert, size_t insert_len, size_t offset);
+PHP_HTTP_BUFFER_API size_t php_http_buffer_insertf(php_http_buffer *buf, size_t offset, const char *format, ...) PHP_HTTP_BUFFER_ATTRIBUTE_FORMAT(printf, 3, 4);
+
+/* prepend data */
+#define php_http_buffer_prepends(b, p) php_http_buffer_prepend((b), (p), sizeof(p)-1)
+#define php_http_buffer_prependl(b, p) php_http_buffer_prepend((b), (p), strlen(p))
+PHP_HTTP_BUFFER_API size_t php_http_buffer_prepend(php_http_buffer *buf, const char *prepend, size_t prepend_len);
+PHP_HTTP_BUFFER_API size_t php_http_buffer_prependf(php_http_buffer *buf, const char *format, ...) PHP_HTTP_BUFFER_ATTRIBUTE_FORMAT(printf, 2, 3);
+
+/* get a zero-terminated string */
+PHP_HTTP_BUFFER_API char *php_http_buffer_data(const php_http_buffer *buf, char **into, size_t *len);
+
+/* get a part of the php_http_buffer */
+#define php_http_buffer_mid(b, o, l) php_http_buffer_sub((b), (o), (l))
+#define php_http_buffer_left(b, l) php_http_buffer_sub((b), 0, (l))
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_right(const php_http_buffer *buf, size_t length);
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_sub(const php_http_buffer *buf, size_t offset, size_t len);
+
+/* remove a substring */
+PHP_HTTP_BUFFER_API size_t php_http_buffer_cut(php_http_buffer *buf, size_t offset, size_t length);
+
+/* get a complete php_http_buffer duplicate */
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_dup(const php_http_buffer *buf);
+
+/* merge several php_http_buffer objects
+   use like:
+
+       php_http_buffer *final = php_http_buffer_merge(3,
+               PHP_HTTP_BUFFER_NOT_FREE(&keep),
+               PHP_HTTP_BUFFER_ALL_FREE(middle_ptr),
+               PHP_HTTP_BUFFER_VAL_FREE(&local);
+*/
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_merge(unsigned argc, ...);
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_merge_ex(php_http_buffer *buf, unsigned argc, ...);
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_merge_va(php_http_buffer *buf, unsigned argc, va_list argv);
+
+/* sets a trailing NUL byte */
+PHP_HTTP_BUFFER_API php_http_buffer *php_http_buffer_fix(php_http_buffer *buf);
+
+/* memcmp for php_http_buffer objects */
+PHP_HTTP_BUFFER_API int php_http_buffer_cmp(php_http_buffer *left, php_http_buffer *right);
+
+/* reset php_http_buffer object */
+PHP_HTTP_BUFFER_API void php_http_buffer_reset(php_http_buffer *buf);
+
+/* free a php_http_buffer objects contents */
+PHP_HTTP_BUFFER_API void php_http_buffer_dtor(php_http_buffer *buf);
+
+/* free a php_http_buffer object completely */
+PHP_HTTP_BUFFER_API void php_http_buffer_free(php_http_buffer **buf);
+
+/* stores data in a php_http_buffer until it reaches chunk_size */
+PHP_HTTP_BUFFER_API size_t php_http_buffer_chunk_buffer(php_http_buffer **s, const char *data, size_t data_len, char **chunk, size_t chunk_size);
+
+typedef size_t (*php_http_buffer_pass_func_t)(void *opaque, const char *, size_t TSRMLS_DC);
+
+/* wrapper around php_http_buffer_chunk_buffer, which passes available chunks to passthru() */
+PHP_HTTP_BUFFER_API void php_http_buffer_chunked_output(php_http_buffer **s, const char *data, size_t data_len, size_t chunk_size, php_http_buffer_pass_func_t passout, void *opaque TSRMLS_DC);
+
+/* write chunks directly into php_http_buffer buffer */
+PHP_HTTP_BUFFER_API size_t php_http_buffer_chunked_input(php_http_buffer **s, size_t chunk_size, php_http_buffer_pass_func_t passin, void *opaque TSRMLS_DC);
+
+#endif
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: sw=4 ts=4 fdm=marker
+ * vim<600: sw=4 ts=4
+ */
diff --git a/php_http_cookie.c b/php_http_cookie.c
new file mode 100644 (file)
index 0000000..1ac08c2
--- /dev/null
@@ -0,0 +1,890 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_cookie_api.c 298662 2010-04-27 13:42:32Z mike $ */
+
+#include "php_http.h"
+
+PHP_HTTP_API php_http_cookie_list_t *php_http_cookie_list_init(php_http_cookie_list_t *list TSRMLS_DC)
+{
+       if (!list) {
+               list = emalloc(sizeof(*list));
+       }
+       
+       zend_hash_init(&list->cookies, 0, NULL, ZVAL_PTR_DTOR, 0);
+       zend_hash_init(&list->extras, 0, NULL, ZVAL_PTR_DTOR, 0);
+       
+       list->path = NULL;
+       list->domain = NULL;
+       list->expires = 0;
+       list->flags = 0;
+       
+       return list;
+}
+
+PHP_HTTP_API php_http_cookie_list_t *php_http_cookie_list_copy(php_http_cookie_list_t *from, php_http_cookie_list_t *to TSRMLS_DC)
+{
+       to = php_http_cookie_list_init(to TSRMLS_CC);
+
+       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);
+       to->expires = from->expires;
+       to->flags = from->flags;
+
+       return to;
+}
+
+PHP_HTTP_API void php_http_cookie_list_dtor(php_http_cookie_list_t *list TSRMLS_DC)
+{
+       if (list) {
+               zend_hash_destroy(&list->cookies);
+               zend_hash_destroy(&list->extras);
+       
+               STR_SET(list->path, NULL);
+               STR_SET(list->domain, NULL);
+       }
+}
+
+
+
+PHP_HTTP_API void php_http_cookie_list_free(php_http_cookie_list_t **list TSRMLS_DC)
+{
+       if (*list) {
+               php_http_cookie_list_dtor(*list);
+               efree(*list);
+               *list = NULL;
+       }
+}
+
+
+
+PHP_HTTP_API const char *php_http_cookie_list_get_cookie(php_http_cookie_list_t *list, const char *name, size_t name_len TSRMLS_DC)
+{
+       zval **cookie = NULL;
+       if ((SUCCESS != zend_hash_find(&list->cookies, name, name_len + 1, (void *) &cookie)) || (Z_TYPE_PP(cookie) != IS_STRING)) {
+               return NULL;
+       }
+       return Z_STRVAL_PP(cookie);
+}
+
+
+
+PHP_HTTP_API const char *php_http_cookie_list_get_extra(php_http_cookie_list_t *list, const char *name, size_t name_len TSRMLS_DC)
+{
+       zval **extra = NULL;
+       if ((SUCCESS != zend_hash_find(&list->extras, name, name_len + 1, (void *) &extra)) || (Z_TYPE_PP(extra) != IS_STRING)) {
+               return NULL;
+       }
+       return Z_STRVAL_PP(extra);
+}
+
+
+
+PHP_HTTP_API void php_http_cookie_list_add_cookie(php_http_cookie_list_t *list, const char *name, size_t name_len, const char *value, size_t value_len TSRMLS_DC)
+{
+       zval *cookie_value;
+       char *key = estrndup(name, name_len);
+       MAKE_STD_ZVAL(cookie_value);
+       ZVAL_STRINGL(cookie_value, estrndup(value, value_len), value_len, 0);
+       zend_hash_update(&list->cookies, key, name_len + 1, (void *) &cookie_value, sizeof(zval *), NULL);
+       efree(key);
+}
+
+
+
+PHP_HTTP_API void php_http_cookie_list_add_extra(php_http_cookie_list_t *list, const char *name, size_t name_len, const char *value, size_t value_len TSRMLS_DC)
+{
+       zval *cookie_value;
+       char *key = estrndup(name, name_len);
+       MAKE_STD_ZVAL(cookie_value);
+       ZVAL_STRINGL(cookie_value, estrndup(value, value_len), value_len, 0);
+       zend_hash_update(&list->extras, key, name_len + 1, (void *) &cookie_value, sizeof(zval *), NULL);
+       efree(key);
+}
+
+
+typedef struct php_http_param_parse_cb_arg {
+       php_http_cookie_list_t *list;
+       long flags;
+       char **allowed_extras;
+} php_http_parse_param_cb_arg_t;
+
+
+static void php_http_cookie_parse_callback(void *ptr, const char *key, int keylen, const char *val, int vallen TSRMLS_DC)
+{
+       php_http_parse_param_cb_arg_t *arg = (php_http_parse_param_cb_arg_t *) ptr;
+       
+#define _KEY_IS(s) (keylen == lenof(s) && !strncasecmp(key, (s), keylen))
+       if _KEY_IS("path") {
+               STR_SET(arg->list->path, estrndup(val, vallen));
+       } else if _KEY_IS("domain") {
+               STR_SET(arg->list->domain, estrndup(val, vallen));
+       } else if _KEY_IS("expires") {
+               char *date = estrndup(val, vallen);
+               arg->list->expires = php_parse_date(date, NULL TSRMLS_CC);
+               efree(date);
+       } else if _KEY_IS("secure") {
+               arg->list->flags |= PHP_HTTP_COOKIE_SECURE;
+       } else if _KEY_IS("httpOnly") {
+               arg->list->flags |= PHP_HTTP_COOKIE_HTTPONLY;
+       } else {
+               /* check for extra */
+               if (arg->allowed_extras) {
+                       char **ae = arg->allowed_extras;
+                       
+                       for (; *ae; ++ae) {
+                               if ((size_t) keylen == strlen(*ae) && !strncasecmp(key, *ae, keylen)) {
+                                       if (arg->flags & PHP_HTTP_COOKIE_PARSE_RAW) {
+                                               php_http_cookie_list_add_extra(arg->list, key, keylen, val, vallen TSRMLS_CC);
+                                       } else {
+                                               char *dec = estrndup(val, vallen);
+                                               int declen = php_url_decode(dec, vallen);
+                                               
+                                               php_http_cookie_list_add_extra(arg->list, key, keylen, dec, declen TSRMLS_CC);
+                                               efree(dec);
+                                       }
+                                       return;
+                               }
+                       }
+               }
+               /* new cookie */
+               if (arg->flags & PHP_HTTP_COOKIE_PARSE_RAW) {
+                       php_http_cookie_list_add_cookie(arg->list, key, keylen, val, vallen TSRMLS_CC);
+               } else {
+                       char *dec = estrndup(val, vallen);
+                       int declen = php_url_decode(dec, vallen);
+                       
+                       php_http_cookie_list_add_cookie(arg->list, key, keylen, dec, declen TSRMLS_CC);
+                       efree(dec);
+               }
+       }
+}
+
+
+
+PHP_HTTP_API php_http_cookie_list_t *php_http_cookie_list_parse(php_http_cookie_list_t *list, const char *string, long flags, char **allowed_extras TSRMLS_DC)
+{
+       int free_list = !list;
+       php_http_parse_param_cb_arg_t arg;
+       
+       list = php_http_cookie_list_init(list TSRMLS_CC);
+       
+       arg.list = list;
+       arg.flags = flags;
+       arg.allowed_extras = allowed_extras;
+       
+       if (SUCCESS != php_http_params_parse(string, PHP_HTTP_PARAMS_RAISE_ERROR, php_http_cookie_parse_callback, &arg TSRMLS_CC)) {
+               if (free_list) {
+                       php_http_cookie_list_free(&list TSRMLS_CC);
+               } else {
+                       php_http_cookie_list_dtor(list TSRMLS_CC);
+               }
+               list = NULL;
+       }
+       
+       return list;
+}
+
+
+
+PHP_HTTP_API void php_http_cookie_list_to_struct(php_http_cookie_list_t *list, zval *strct TSRMLS_DC)
+{
+       zval array, *cookies, *extras;
+       
+       INIT_PZVAL_ARRAY(&array, HASH_OF(strct));
+       
+       MAKE_STD_ZVAL(cookies);
+       array_init(cookies);
+       zend_hash_copy(Z_ARRVAL_P(cookies), &list->cookies, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+       add_assoc_zval(&array, "cookies", cookies);
+       
+       MAKE_STD_ZVAL(extras);
+       array_init(extras);
+       zend_hash_copy(Z_ARRVAL_P(extras), &list->extras, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+       add_assoc_zval(&array, "extras", extras);
+       
+       add_assoc_long(&array, "flags", list->flags);
+       add_assoc_long(&array, "expires", (long) list->expires);
+       add_assoc_string(&array, "path", STR_PTR(list->path), 1);
+       add_assoc_string(&array, "domain", STR_PTR(list->domain), 1);
+}
+
+
+
+PHP_HTTP_API php_http_cookie_list_t *php_http_cookie_list_from_struct(php_http_cookie_list_t *list, zval *strct TSRMLS_DC)
+{
+       zval **tmp, *cpy;
+       HashTable *ht = HASH_OF(strct);
+       
+       list = php_http_cookie_list_init(list TSRMLS_CC);
+       
+       if (SUCCESS == zend_hash_find(ht, "cookies", sizeof("cookies"), (void *) &tmp) && Z_TYPE_PP(tmp) == IS_ARRAY) {
+               zend_hash_copy(&list->cookies, Z_ARRVAL_PP(tmp), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+       }
+       if (SUCCESS == zend_hash_find(ht, "extras", sizeof("extras"), (void *) &tmp) && Z_TYPE_PP(tmp) == IS_ARRAY) {
+               zend_hash_copy(&list->extras, Z_ARRVAL_PP(tmp), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+       }
+       if (SUCCESS == zend_hash_find(ht, "flags", sizeof("flags"), (void *) &tmp)) {
+               switch (Z_TYPE_PP(tmp)) {
+                       case IS_LONG:
+                               list->flags = Z_LVAL_PP(tmp);
+                               break;
+                       case IS_DOUBLE:
+                               list->flags = (long) Z_DVAL_PP(tmp);
+                               break;
+                       case IS_STRING:
+                               cpy = php_http_zsep(IS_LONG, *tmp);
+                               list->flags = Z_LVAL_P(cpy);
+                               zval_ptr_dtor(&cpy);
+                               break;
+                       default:
+                               break;
+               }
+       }
+       if (SUCCESS == zend_hash_find(ht, "expires", sizeof("expires"), (void *) &tmp)) {
+               switch (Z_TYPE_PP(tmp)) {
+                       case IS_LONG:
+                               list->expires = Z_LVAL_PP(tmp);
+                               break;
+                       case IS_DOUBLE:
+                               list->expires = (long) Z_DVAL_PP(tmp);
+                               break;
+                       case IS_STRING:
+                               cpy = php_http_zsep(IS_LONG, *tmp);
+                               if (Z_LVAL_P(cpy)) {
+                                       list->expires = Z_LVAL_P(cpy);
+                               } else {
+                                       time_t expires = php_parse_date(Z_STRVAL_PP(tmp), NULL TSRMLS_CC);
+                                       if (expires > 0) {
+                                               list->expires = expires;
+                                       }
+                               }
+                               zval_ptr_dtor(&cpy);
+                               break;
+                       default:
+                               break;
+               }
+       }
+       if (SUCCESS == zend_hash_find(ht, "path", sizeof("path"), (void *) &tmp) && Z_TYPE_PP(tmp) == IS_STRING) {
+               list->path = estrndup(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));
+       }
+       if (SUCCESS == zend_hash_find(ht, "domain", sizeof("domain"), (void *) &tmp) && Z_TYPE_PP(tmp) == IS_STRING) {
+               list->domain = estrndup(Z_STRVAL_PP(tmp), Z_STRLEN_PP(tmp));
+       }
+       
+       return list;
+}
+
+
+
+static inline void append_encoded(php_http_buffer *buf, const char *key, size_t key_len, const char *val, size_t val_len)
+{
+       char *enc_str[2];
+       int enc_len[2];
+       
+       enc_str[0] = php_url_encode(key, key_len, &enc_len[0]);
+       enc_str[1] = php_url_encode(val, val_len, &enc_len[1]);
+       
+       php_http_buffer_append(buf, enc_str[0], enc_len[0]);
+       php_http_buffer_appends(buf, "=");
+       php_http_buffer_append(buf, enc_str[1], enc_len[1]);
+       php_http_buffer_appends(buf, "; ");
+       
+       efree(enc_str[0]);
+       efree(enc_str[1]);
+}
+
+
+
+PHP_HTTP_API void php_http_cookie_list_to_string(php_http_cookie_list_t *list, char **str, size_t *len TSRMLS_DC)
+{
+       php_http_buffer buf;
+       zval **val;
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       HashPosition pos;
+       
+       php_http_buffer_init(&buf);
+       
+       FOREACH_HASH_KEYVAL(pos, &list->cookies, key, val) {
+               if (key.type == HASH_KEY_IS_STRING && key.len) {
+                       zval *tmp = php_http_zsep(IS_STRING, *val);
+                       append_encoded(&buf, key.str, key.len-1, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
+                       zval_ptr_dtor(&tmp);
+               }
+       }
+       
+       if (list->domain && *list->domain) {
+               php_http_buffer_appendf(&buf, "domain=%s; ", list->domain);
+       }
+       if (list->path && *list->path) {
+               php_http_buffer_appendf(&buf, "path=%s; ", list->path);
+       }
+       if (list->expires) {
+               char *date = php_format_date(ZEND_STRL(PHP_HTTP_DATE_FORMAT), list->expires, 0 TSRMLS_CC);
+               php_http_buffer_appendf(&buf, "expires=%s; ", date);
+               efree(date);
+       }
+       
+       FOREACH_HASH_KEYVAL(pos, &list->extras, key, val) {
+               if (key.type == HASH_KEY_IS_STRING && key.len) {
+                       zval *tmp = php_http_zsep(IS_STRING, *val);
+                       append_encoded(&buf, key.str, key.len-1, Z_STRVAL_P(tmp), Z_STRLEN_P(tmp));
+               }
+       }
+       
+       if (list->flags & PHP_HTTP_COOKIE_SECURE) {
+               php_http_buffer_appends(&buf, "secure; ");
+       }
+       if (list->flags & PHP_HTTP_COOKIE_HTTPONLY) {
+               php_http_buffer_appends(&buf, "httpOnly; ");
+       }
+       
+       php_http_buffer_fix(&buf);
+       *str = PHP_HTTP_BUFFER_VAL(&buf);
+       *len = PHP_HTTP_BUFFER_LEN(&buf);
+}
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpCookie, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpCookie, method, 0)
+#define PHP_HTTP_COOKIE_ME(method, visibility) PHP_ME(HttpCookie, method, PHP_HTTP_ARGS(HttpCookie, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(__construct, 0)
+       PHP_HTTP_ARG_VAL(cookie_string, 0)
+       PHP_HTTP_ARG_VAL(parser_flags, 0)
+       PHP_HTTP_ARG_VAL(allowed_extras, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(toArray);
+PHP_HTTP_EMPTY_ARGS(getCookies);
+PHP_HTTP_EMPTY_ARGS(getExtras);
+PHP_HTTP_EMPTY_ARGS(getDomain);
+PHP_HTTP_EMPTY_ARGS(getPath);
+PHP_HTTP_EMPTY_ARGS(getExpires);
+PHP_HTTP_EMPTY_ARGS(getFlags);
+PHP_HTTP_EMPTY_ARGS(toString);
+
+PHP_HTTP_BEGIN_ARGS(setDomain, 0)
+       PHP_HTTP_ARG_VAL(value, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(setPath, 0)
+       PHP_HTTP_ARG_VAL(value, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(setExpires, 0)
+       PHP_HTTP_ARG_VAL(value, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(setFlags, 0)
+       PHP_HTTP_ARG_VAL(value, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setCookies, 0)
+       PHP_HTTP_ARG_VAL(cookies, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(addCookies, 1)
+       PHP_HTTP_ARG_VAL(cookies, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(setExtras, 0)
+       PHP_HTTP_ARG_VAL(extras, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(addExtras, 1)
+       PHP_HTTP_ARG_VAL(extras, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(setCookie, 1)
+       PHP_HTTP_ARG_VAL(cookie_name, 0)
+       PHP_HTTP_ARG_VAL(cookie_value, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(addCookie, 1)
+       PHP_HTTP_ARG_VAL(cookie_name, 0)
+       PHP_HTTP_ARG_VAL(cookie_value, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(getCookie, 1)
+       PHP_HTTP_ARG_VAL(name, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(setExtra, 1)
+       PHP_HTTP_ARG_VAL(extra_name, 0)
+       PHP_HTTP_ARG_VAL(extra_value, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(addExtra, 1)
+       PHP_HTTP_ARG_VAL(extra_name, 0)
+       PHP_HTTP_ARG_VAL(extra_value, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(getExtra, 1)
+       PHP_HTTP_ARG_VAL(name, 0)
+PHP_HTTP_END_ARGS;
+
+zend_class_entry *php_http_cookie_class_entry;
+zend_function_entry php_http_cookie_method_entry[] = {
+       PHP_HTTP_COOKIE_ME(__construct, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(getCookies, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(setCookies, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(addCookies, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(getCookie, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(setCookie, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(addCookie, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_COOKIE_ME(getExtras, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(setExtras, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(addExtras, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(getExtra, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(setExtra, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(addExtra, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_COOKIE_ME(getDomain, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(setDomain, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(getPath, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(setPath, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(getExpires, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(setExpires, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(getFlags, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(setFlags, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_COOKIE_ME(toArray, ZEND_ACC_PUBLIC)
+       PHP_HTTP_COOKIE_ME(toString, ZEND_ACC_PUBLIC)
+       ZEND_MALIAS(HttpCookie, __toString, toString, PHP_HTTP_ARGS(HttpCookie, toString), ZEND_ACC_PUBLIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+static zend_object_handlers php_http_cookie_object_handlers;
+
+zend_object_value php_http_cookie_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_cookie_object_new_ex(ce, NULL, NULL TSRMLS_CC);
+}
+
+zend_object_value php_http_cookie_object_new_ex(zend_class_entry *ce, php_http_cookie_list_t *list, php_http_cookie_object_t **ptr TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_cookie_object_t *o;
+
+       o = ecalloc(sizeof(*o), 1);
+       zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       if (list) {
+               o->list = list;
+       }
+
+       if (ptr) {
+               *ptr = o;
+       }
+
+       ov.handle = zend_objects_store_put(o, NULL, php_http_cookie_object_free, NULL TSRMLS_CC);
+       ov.handlers = &php_http_cookie_object_handlers;
+
+       return ov;
+}
+
+zend_object_value php_http_cookie_object_clone(zval *this_ptr TSRMLS_CC)
+{
+       php_http_cookie_object_t *new_obj, *old_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+       zend_object_value ov;
+
+       ov = php_http_cookie_object_new_ex(old_obj->o.ce, php_http_cookie_list_copy(old_obj->list, NULL TSRMLS_CC), &new_obj TSRMLS_CC);
+       zend_objects_clone_members((zend_object *) new_obj, ov, (zend_object *) old_obj, Z_OBJ_HANDLE_P(getThis()) TSRMLS_CC);
+
+       return ov;
+}
+
+void php_http_cookie_object_free(void *object TSRMLS_CC)
+{
+       php_http_cookie_object_t *obj = object;
+
+       php_http_cookie_list_free(&obj->list TSRMLS_CC);
+       zend_object_std_dtor((zend_object *) obj TSRMLS_CC);
+       efree(obj);
+}
+
+PHP_METHOD(HttpCookie, __construct)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               zval *zcookie = NULL;
+               long flags = 0;
+               HashTable *allowed_extras = NULL;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z!lH", &zcookie, &flags, &allowed_extras)) {
+                       if (zcookie) {
+                               with_error_handling(EH_THROW, PHP_HTTP_EX_CE(cookie)) {
+                                       char **ae = NULL;
+                                       php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                                       if (allowed_extras && zend_hash_num_elements(allowed_extras)) {
+                                               char **ae_ptr = safe_emalloc(zend_hash_num_elements(allowed_extras) + 1, sizeof(char *), 0);
+                                               HashPosition pos;
+                                               zval **val;
+
+                                               ae = ae_ptr;
+                                               FOREACH_HASH_VAL(pos, allowed_extras, val) {
+                                                       zval *cpy = php_http_zsep(IS_STRING, *val);
+
+                                                       *ae_ptr++ = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy));
+                                                       zval_ptr_dtor(&cpy);
+                                               }
+                                               *ae_ptr = NULL;
+                                       }
+
+                                       switch (Z_TYPE_P(zcookie)) {
+                                               case IS_ARRAY:
+                                               case IS_OBJECT:
+                                                       obj->list = php_http_cookie_list_from_struct(obj->list, zcookie TSRMLS_CC);
+                                                       break;
+                                               default: {
+                                                       zval *cpy = php_http_zsep(IS_STRING, zcookie);
+
+                                                       obj->list = php_http_cookie_list_parse(obj->list, Z_STRVAL_P(cpy), flags, ae TSRMLS_CC);
+                                                       zval_ptr_dtor(&cpy);
+                                                       break;
+                                               }
+                                       }
+                               } end_error_handling();
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpCookie, getCookies)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_init(return_value);
+               array_copy(&obj->list->cookies, Z_ARRVAL_P(return_value));
+               return;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, setCookies)
+{
+       HashTable *cookies = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|H", &cookies)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               zend_hash_clean(&obj->list->cookies);
+               if (cookies) {
+                       array_copy(cookies, &obj->list->cookies);
+               }
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, addCookies)
+{
+       HashTable *cookies = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "H", &cookies)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_join(cookies, &obj->list->cookies, 1, ARRAY_JOIN_STRONLY);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+
+PHP_METHOD(HttpCookie, getExtras)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_init(return_value);
+               array_copy(&obj->list->extras, Z_ARRVAL_P(return_value));
+               return;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, setExtras)
+{
+       HashTable *extras = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|H", &extras)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               zend_hash_clean(&obj->list->extras);
+               if (extras) {
+                       array_copy(extras, &obj->list->extras);
+               }
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, addExtras)
+{
+       HashTable *extras = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "H", &extras)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_join(extras, &obj->list->extras, 1, ARRAY_JOIN_STRONLY);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, getCookie)
+{
+       char *name_str;
+       int name_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name_str, &name_len)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               zval **zvalue;
+
+               if (SUCCESS == zend_hash_find(&obj->list->cookies, name_str, name_len + 1, (void *) &zvalue)) {
+                       RETURN_ZVAL(*zvalue, 1, 0);
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, setCookie)
+{
+       char *name_str, *value_str;
+       int name_len, value_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s!", &name_str, &name_len, &value_str, &value_len)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!value_str) {
+                       RETURN_SUCCESS(zend_hash_del(&obj->list->cookies, name_str, name_len + 1));
+               } else {
+                       zval *zvalue;
+
+                       MAKE_STD_ZVAL(zvalue);
+                       ZVAL_STRINGL(zvalue, value_str, value_len, 1);
+                       RETURN_SUCCESS(zend_hash_update(&obj->list->cookies, name_str, name_len + 1, &zvalue, sizeof(zval *), NULL));
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, addCookie)
+{
+       char *name_str, *value_str;
+       int name_len, value_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &name_str, &name_len, &value_str, &value_len)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               zval *zvalue;
+
+               MAKE_STD_ZVAL(zvalue);
+               ZVAL_STRINGL(zvalue, value_str, value_len, 1);
+               RETURN_SUCCESS(zend_hash_add(&obj->list->cookies, name_str, name_len + 1, &zvalue, sizeof(zval *), NULL));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, getExtra)
+{
+       char *name_str;
+       int name_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &name_str, &name_len)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               zval **zvalue;
+
+               if (SUCCESS == zend_hash_find(&obj->list->extras, name_str, name_len + 1, (void *) &zvalue)) {
+                       RETURN_ZVAL(*zvalue, 1, 0);
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, setExtra)
+{
+       char *name_str, *value_str;
+       int name_len, value_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s!", &name_str, &name_len, &value_str, &value_len)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!value_str) {
+                       RETURN_SUCCESS(zend_hash_del(&obj->list->extras, name_str, name_len + 1));
+               } else {
+                       zval *zvalue;
+
+                       MAKE_STD_ZVAL(zvalue);
+                       ZVAL_STRINGL(zvalue, value_str, value_len, 1);
+                       RETURN_SUCCESS(zend_hash_update(&obj->list->extras, name_str, name_len + 1, &zvalue, sizeof(zval *), NULL));
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, addExtra)
+{
+       char *name_str, *value_str;
+       int name_len, value_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|s", &name_str, &name_len, &value_str, &value_len)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               zval *zvalue;
+
+               MAKE_STD_ZVAL(zvalue);
+               ZVAL_STRINGL(zvalue, value_str, value_len, 1);
+               RETURN_SUCCESS(zend_hash_add(&obj->list->extras, name_str, name_len + 1, &zvalue, sizeof(zval *), NULL));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, getDomain)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->list->domain) {
+                       RETURN_STRING(obj->list->domain, 1);
+               }
+               RETURN_NULL();
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, setDomain)
+{
+       char *domain_str = NULL;
+       int domain_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &domain_str, &domain_len)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               STR_SET(obj->list->domain, domain_str ? estrndup(domain_str, domain_len) : NULL);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, getPath)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->list->path) {
+                       RETURN_STRING(obj->list->path, 1);
+               }
+               RETURN_NULL();
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, setPath)
+{
+       char *path_str = NULL;
+       int path_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &path_str, &path_len)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               STR_SET(obj->list->path, path_str ? estrndup(path_str, path_len) : NULL);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, getExpires)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_LONG(obj->list->expires);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, setExpires)
+{
+       long ts = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &ts)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               obj->list->expires = ts;
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, getFlags)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_LONG(obj->list->flags);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, setFlags)
+{
+       long flags = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags)) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               obj->list->flags = flags;
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpCookie, toString)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               char *str;
+               size_t len;
+
+               php_http_cookie_list_to_string(obj->list, &str, &len TSRMLS_CC);
+               RETURN_STRINGL(str, len, 0);
+       }
+       RETURN_EMPTY_STRING();
+}
+
+PHP_METHOD(HttpCookie, toArray)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_cookie_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_init(return_value);
+               php_http_cookie_list_to_struct(obj->list, return_value TSRMLS_CC);
+       }
+}
+
+PHP_MINIT_FUNCTION(http_cookie)
+{
+       PHP_HTTP_REGISTER_CLASS(http, Cookie, http_cookie, php_http_object_class_entry, 0);
+       php_http_cookie_class_entry->create_object = php_http_cookie_object_new;
+       memcpy(&php_http_cookie_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_cookie_object_handlers.clone_obj = php_http_cookie_object_clone;
+
+       zend_declare_class_constant_long(php_http_cookie_class_entry, ZEND_STRL("PARSE_RAW"), PHP_HTTP_COOKIE_PARSE_RAW TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_cookie_class_entry, ZEND_STRL("SECURE"), PHP_HTTP_COOKIE_SECURE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_cookie_class_entry, ZEND_STRL("HTTPONLY"), PHP_HTTP_COOKIE_HTTPONLY TSRMLS_CC);
+
+       return SUCCESS;
+}
+
+
+
+/*
+ * 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_cookie.h b/php_http_cookie.h
new file mode 100644 (file)
index 0000000..1e2b0f7
--- /dev/null
@@ -0,0 +1,104 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_cookie_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_COOKIE_H
+#define PHP_HTTP_COOKIE_H
+
+#define PHP_HTTP_COOKIE_SECURE         0x10L
+#define PHP_HTTP_COOKIE_HTTPONLY       0x20L
+
+#define PHP_HTTP_COOKIE_PARSE_RAW      0x01L
+
+/*
+       generally a netscape cookie compliant struct, recognizing httpOnly attribute, too;
+       cookie params like those from rfc2109 and rfc2965 are just put into extras, if
+       one specifies them in allowed extras, else they're treated like cookies themself
+*/
+typedef struct php_http_cookie_list {
+       HashTable cookies;
+       HashTable extras;
+       long flags;
+       char *path;
+       char *domain;
+       time_t expires;
+} php_http_cookie_list_t;
+
+PHP_HTTP_API php_http_cookie_list_t *php_http_cookie_list_init(php_http_cookie_list_t *list TSRMLS_DC);
+PHP_HTTP_API php_http_cookie_list_t *php_http_cookie_list_parse(php_http_cookie_list_t * list, const char *string, long flags, char **allowed_extras TSRMLS_DC);
+PHP_HTTP_API void php_http_cookie_list_dtor(php_http_cookie_list_t *list TSRMLS_DC);
+PHP_HTTP_API void php_http_cookie_list_free(php_http_cookie_list_t **list TSRMLS_DC);
+
+#define php_http_cookie_list_has_cookie(list, name, name_len) zend_hash_exists(&(list)->cookies, (name), (name_len)+1)
+PHP_HTTP_API void php_http_cookie_list_add_cookie(php_http_cookie_list_t *list, const char *name, size_t name_len, const char *value, size_t value_len TSRMLS_DC);
+PHP_HTTP_API const char *php_http_cookie_list_get_cookie(php_http_cookie_list_t *list, const char *name, size_t name_len TSRMLS_DC);
+
+#define php_http_cookie_list_has_extra(list, name, name_len) zend_hash_exists(&(list)->extras, (name), (name_len)+1)
+PHP_HTTP_API void php_http_cookie_list_add_extra(php_http_cookie_list_t *list, const char *name, size_t name_len, const char *value, size_t value_len TSRMLS_DC);
+PHP_HTTP_API const char *php_http_cookie_list_get_extra(php_http_cookie_list_t *list, const char *name, size_t name_len TSRMLS_DC);
+
+PHP_HTTP_API void php_http_cookie_list_to_string(php_http_cookie_list_t *list, char **str, size_t *len TSRMLS_DC);
+PHP_HTTP_API php_http_cookie_list_t *php_http_cookie_list_from_struct(php_http_cookie_list_t *list, zval *strct TSRMLS_DC);
+PHP_HTTP_API void php_http_cookie_list_to_struct(php_http_cookie_list_t *list, zval *strct TSRMLS_DC);
+
+
+extern zend_class_entry *php_http_cookie_class_entry;
+extern zend_function_entry php_http_cookie_method_entry[];
+
+typedef struct php_http_cookie_object {
+       zend_object o;
+       php_http_cookie_list_t *list;
+} php_http_cookie_object_t;
+
+zend_object_value php_http_cookie_object_new(zend_class_entry *ce TSRMLS_DC);
+zend_object_value php_http_cookie_object_new_ex(zend_class_entry *ce, php_http_cookie_list_t *list, php_http_cookie_object_t **obj TSRMLS_DC);
+zend_object_value php_http_cookie_object_clone(zval *this_ptr TSRMLS_DC);
+void php_http_cookie_object_free(void *object TSRMLS_DC);
+
+PHP_METHOD(HttpCookie, __construct);
+PHP_METHOD(HttpCookie, getCookies);
+PHP_METHOD(HttpCookie, setCookies);
+PHP_METHOD(HttpCookie, addCookies);
+PHP_METHOD(HttpCookie, getExtras);
+PHP_METHOD(HttpCookie, setExtras);
+PHP_METHOD(HttpCookie, addExtras);
+PHP_METHOD(HttpCookie, getCookie);
+PHP_METHOD(HttpCookie, setCookie);
+PHP_METHOD(HttpCookie, addCookie);
+PHP_METHOD(HttpCookie, getExtra);
+PHP_METHOD(HttpCookie, setExtra);
+PHP_METHOD(HttpCookie, addExtra);
+PHP_METHOD(HttpCookie, getDomain);
+PHP_METHOD(HttpCookie, setDomain);
+PHP_METHOD(HttpCookie, getPath);
+PHP_METHOD(HttpCookie, setPath);
+PHP_METHOD(HttpCookie, getExpires);
+PHP_METHOD(HttpCookie, setExpires);
+PHP_METHOD(HttpCookie, getFlags);
+PHP_METHOD(HttpCookie, setFlags);
+PHP_METHOD(HttpCookie, toString);
+PHP_METHOD(HttpCookie, toArray);
+
+extern PHP_MINIT_FUNCTION(http_cookie);
+
+
+#endif
+
+/*
+ * 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_encoding.c b/php_http_encoding.c
new file mode 100644 (file)
index 0000000..34c6922
--- /dev/null
@@ -0,0 +1,1197 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_encoding_api.c 298592 2010-04-26 11:47:29Z mike $ */
+
+#include "php_http.h"
+
+static inline int eol_match(char **line, int *eol_len)
+{
+       char *ptr = *line;
+       
+       while (' ' == *ptr) ++ptr;
+
+       if (ptr == php_http_locate_eol(*line, eol_len)) {
+               *line = ptr;
+               return 1;
+       } else {
+               return 0;
+       }
+}
+
+PHP_HTTP_API const char *php_http_encoding_dechunk(const char *encoded, size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
+{
+       int eol_len = 0;
+       char *n_ptr = NULL;
+       const char *e_ptr = encoded;
+       
+       *decoded_len = 0;
+       *decoded = ecalloc(1, encoded_len);
+
+       while ((encoded + encoded_len - e_ptr) > 0) {
+               ulong chunk_len = 0, rest;
+
+               chunk_len = strtoul(e_ptr, &n_ptr, 16);
+
+               /* we could not read in chunk size */
+               if (n_ptr == e_ptr) {
+                       /*
+                        * if this is the first turn and there doesn't seem to be a chunk
+                        * size at the begining of the body, do not fail on apparently
+                        * not encoded data and return a copy
+                        */
+                       if (e_ptr == encoded) {
+                               php_http_error(HE_NOTICE, PHP_HTTP_E_ENCODING, "Data does not seem to be chunked encoded");
+                               memcpy(*decoded, encoded, encoded_len);
+                               *decoded_len = encoded_len;
+                               return encoded + encoded_len;
+                       } else {
+                               efree(*decoded);
+                               php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Expected chunk size at pos %tu of %zu but got trash", n_ptr - encoded, encoded_len);
+                               return NULL;
+                       }
+               }
+               
+               /* reached the end */
+               if (!chunk_len) {
+                       /* move over '0' chunked encoding terminator and any new lines */
+                       do {
+                               switch (*e_ptr) {
+                                       case '0':
+                                       case '\r':
+                                       case '\n':
+                                               ++e_ptr;
+                                               continue;
+                               }
+                       } while (0);
+                       break;
+               }
+
+               /* there should be CRLF after the chunk size, but we'll ignore SP+ too */
+               if (*n_ptr && !eol_match(&n_ptr, &eol_len)) {
+                       if (eol_len == 2) {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Expected CRLF at pos %tu of %zu but got 0x%02X 0x%02X", n_ptr - encoded, encoded_len, *n_ptr, *(n_ptr + 1));
+                       } else {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Expected LF at pos %tu of %zu but got 0x%02X", n_ptr - encoded, encoded_len, *n_ptr);
+                       }
+               }
+               n_ptr += eol_len;
+               
+               /* chunk size pretends more data than we actually got, so it's probably a truncated message */
+               if (chunk_len > (rest = encoded + encoded_len - n_ptr)) {
+                       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Truncated message: chunk size %lu exceeds remaining data size %lu at pos %tu of %zu", chunk_len, rest, n_ptr - encoded, encoded_len);
+                       chunk_len = rest;
+               }
+
+               /* copy the chunk */
+               memcpy(*decoded + *decoded_len, n_ptr, chunk_len);
+               *decoded_len += chunk_len;
+               
+               if (chunk_len == rest) {
+                       e_ptr = n_ptr + chunk_len;
+                       break;
+               } else {
+                       /* advance to next chunk */
+                       e_ptr = n_ptr + chunk_len + eol_len;
+               }
+       }
+
+       return e_ptr;
+}
+
+static inline int php_http_inflate_rounds(z_stream *Z, int flush, char **buf, size_t *len)
+{
+       int status = 0, round = 0;
+       php_http_buffer buffer;
+       
+       *buf = NULL;
+       *len = 0;
+       
+       php_http_buffer_init_ex(&buffer, Z->avail_in, PHP_HTTP_BUFFER_INIT_PREALLOC);
+       
+       do {
+               if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_resize_ex(&buffer, buffer.size, 0, 1)) {
+                       status = Z_MEM_ERROR;
+               } else {
+                       Z->avail_out = buffer.free;
+                       Z->next_out = (Bytef *) buffer.data + buffer.used;
+#if 0
+                       fprintf(stderr, "\n%3d: %3d PRIOR: size=%7lu,\tfree=%7lu,\tused=%7lu,\tavail_in=%7lu,\tavail_out=%7lu\n", round, status, buffer.size, buffer.free, buffer.used, Z->avail_in, Z->avail_out);
+#endif
+                       status = inflate(Z, flush);
+                       
+                       buffer.used += buffer.free - Z->avail_out;
+                       buffer.free = Z->avail_out;
+#if 0
+                       fprintf(stderr, "%3d: %3d AFTER: size=%7lu,\tfree=%7lu,\tused=%7lu,\tavail_in=%7lu,\tavail_out=%7lu\n", round, status, buffer.size, buffer.free, buffer.used, Z->avail_in, Z->avail_out);
+#endif
+                       PHP_HTTP_INFLATE_BUFFER_SIZE_ALIGN(buffer.size);
+               }
+       } while ((Z_BUF_ERROR == status || (Z_OK == status && Z->avail_in)) && ++round < PHP_HTTP_INFLATE_ROUNDS);
+       
+       if (status == Z_OK || status == Z_STREAM_END) {
+               php_http_buffer_shrink(&buffer);
+               php_http_buffer_fix(&buffer);
+               *buf = buffer.data;
+               *len = buffer.used;
+       } else {
+               php_http_buffer_dtor(&buffer);
+       }
+       
+       return status;
+}
+
+PHP_HTTP_API STATUS php_http_encoding_deflate(int flags, const char *data, size_t data_len, char **encoded, size_t *encoded_len TSRMLS_DC)
+{
+       int status, level, wbits, strategy;
+       z_stream Z;
+       
+       PHP_HTTP_DEFLATE_LEVEL_SET(flags, level);
+       PHP_HTTP_DEFLATE_WBITS_SET(flags, wbits);
+       PHP_HTTP_DEFLATE_STRATEGY_SET(flags, strategy);
+       
+       memset(&Z, 0, sizeof(z_stream));
+       *encoded = NULL;
+       *encoded_len = 0;
+       
+       status = deflateInit2(&Z, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, strategy);
+       if (Z_OK == status) {
+               *encoded_len = PHP_HTTP_DEFLATE_BUFFER_SIZE_GUESS(data_len);
+               *encoded = emalloc(*encoded_len);
+               
+               Z.next_in = (Bytef *) data;
+               Z.next_out = (Bytef *) *encoded;
+               Z.avail_in = data_len;
+               Z.avail_out = *encoded_len;
+               
+               status = deflate(&Z, Z_FINISH);
+               deflateEnd(&Z);
+               
+               if (Z_STREAM_END == status) {
+                       /* size buffer down to actual length */
+                       *encoded = erealloc(*encoded, Z.total_out + 1);
+                       (*encoded)[*encoded_len = Z.total_out] = '\0';
+                       return SUCCESS;
+               } else {
+                       STR_SET(*encoded, NULL);
+                       *encoded_len = 0;
+               }
+       }
+       
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not deflate data: %s", zError(status));
+       return FAILURE;
+}
+
+PHP_HTTP_API STATUS php_http_encoding_inflate(const char *data, size_t data_len, char **decoded, size_t *decoded_len TSRMLS_DC)
+{
+       z_stream Z;
+       int status, wbits = PHP_HTTP_WINDOW_BITS_ANY;
+       
+       memset(&Z, 0, sizeof(z_stream));
+       
+retry_raw_inflate:
+       status = inflateInit2(&Z, wbits);
+       if (Z_OK == status) {
+               Z.next_in = (Bytef *) data;
+               Z.avail_in = data_len;
+               
+               switch (status = php_http_inflate_rounds(&Z, Z_NO_FLUSH, decoded, decoded_len)) {
+                       case Z_STREAM_END:
+                               inflateEnd(&Z);
+                               return SUCCESS;
+
+                       case Z_OK:
+                               status = Z_DATA_ERROR;
+                               break;
+                       
+                       case Z_DATA_ERROR:
+                               /* raw deflated data? */
+                               if (PHP_HTTP_WINDOW_BITS_ANY == wbits) {
+                                       inflateEnd(&Z);
+                                       wbits = PHP_HTTP_WINDOW_BITS_RAW;
+                                       goto retry_raw_inflate;
+                               }
+               }
+               inflateEnd(&Z);
+
+               if (decoded_len && *decoded) {
+                       efree(*decoded);
+               }
+       }
+       
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not inflate data: %s", zError(status));
+       return FAILURE;
+}
+
+PHP_HTTP_API php_http_encoding_stream_t *php_http_encoding_stream_init(php_http_encoding_stream_t *s, php_http_encoding_stream_ops_t *ops, unsigned flags TSRMLS_DC)
+{
+       int freeme;
+
+       if ((freeme = !s)) {
+               s = pemalloc(sizeof(*s), (flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT));
+       }
+       memset(s, 0, sizeof(*s));
+
+       s->flags = flags;
+       TSRMLS_SET_CTX(s->ts);
+
+       if ((s->ops = ops)) {
+               php_http_encoding_stream_t *ss = s->ops->init(s);
+
+               if (ss) {
+                       return ss;
+               }
+       } else {
+               return s;
+       }
+
+       if (freeme) {
+               pefree(s, (flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT));
+       }
+       return NULL;
+}
+
+PHP_HTTP_API php_http_encoding_stream_t *php_http_encoding_stream_copy(php_http_encoding_stream_t *from, php_http_encoding_stream_t *to)
+{
+       TSRMLS_FETCH_FROM_CTX(from->ts);
+
+       if (!from->ops->copy) {
+               return NULL;
+       }
+
+       return from->ops->copy(from, php_http_encoding_stream_init(to, from->ops, from->flags TSRMLS_CC));
+}
+
+PHP_HTTP_API STATUS php_http_encoding_stream_reset(php_http_encoding_stream_t **s)
+{
+       php_http_encoding_stream_t *ss;
+       if ((*s)->ops->dtor) {
+               (*s)->ops->dtor(*s);
+       }
+       if ((ss = (*s)->ops->init(*s))) {
+               *s = ss;
+               return SUCCESS;
+       }
+       return FAILURE;
+}
+
+PHP_HTTP_API STATUS php_http_encoding_stream_update(php_http_encoding_stream_t *s, const char *in_str, size_t in_len, char **out_str, size_t *out_len)
+{
+       if (!s->ops->update) {
+               return FAILURE;
+       }
+       return s->ops->update(s, in_str, in_len, out_str, out_len);
+}
+
+PHP_HTTP_API STATUS php_http_encoding_stream_flush(php_http_encoding_stream_t *s, char **out_str, size_t *out_len)
+{
+       if (!s->ops->flush) {
+               return SUCCESS;
+       }
+       return s->ops->flush(s, out_str, out_len);
+}
+
+PHP_HTTP_API zend_bool php_http_encoding_stream_done(php_http_encoding_stream_t *s)
+{
+       if (!s->ops->done) {
+               return 0;
+       }
+       return s->ops->done(s);
+}
+
+PHP_HTTP_API STATUS php_http_encoding_stream_finish(php_http_encoding_stream_t *s, char **out_str, size_t *out_len)
+{
+       if (!s->ops->finish) {
+               return SUCCESS;
+       }
+       return s->ops->finish(s, out_str, out_len);
+}
+
+PHP_HTTP_API void php_http_encoding_stream_dtor(php_http_encoding_stream_t *s)
+{
+       if (s->ops->dtor) {
+               s->ops->dtor(s);
+       }
+}
+
+PHP_HTTP_API void php_http_encoding_stream_free(php_http_encoding_stream_t **s)
+{
+       if (*s) {
+               if ((*s)->ops->dtor) {
+                       (*s)->ops->dtor(*s);
+               }
+               pefree(*s, ((*s)->flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT));
+               *s = NULL;
+       }
+}
+
+struct dechunk_ctx {
+       php_http_buffer buffer;
+       ulong hexlen;
+       unsigned zeroed:1;
+};
+
+static php_http_encoding_stream_t *deflate_init(php_http_encoding_stream_t *s)
+{
+       int status, level, wbits, strategy, p = (s->flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT);
+       z_streamp ctx = pecalloc(1, sizeof(z_stream), p);
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+       
+       PHP_HTTP_DEFLATE_LEVEL_SET(s->flags, level);
+       PHP_HTTP_DEFLATE_WBITS_SET(s->flags, wbits);
+       PHP_HTTP_DEFLATE_STRATEGY_SET(s->flags, strategy);
+       
+       if (Z_OK == (status = deflateInit2(ctx, level, Z_DEFLATED, wbits, MAX_MEM_LEVEL, strategy))) {
+               if ((ctx->opaque = php_http_buffer_init_ex(NULL, PHP_HTTP_DEFLATE_BUFFER_SIZE, p ? PHP_HTTP_BUFFER_INIT_PERSISTENT : 0))) {
+                       s->ctx = ctx;
+                       return s;
+               }
+               deflateEnd(ctx);
+               status = Z_MEM_ERROR;
+       }
+       pefree(ctx, p);
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Failed to initialize deflate encoding stream: %s", zError(status));
+       return NULL;
+}
+
+static php_http_encoding_stream_t *inflate_init(php_http_encoding_stream_t *s)
+{
+       int status, wbits, p = (s->flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT);
+       z_streamp ctx = pecalloc(1, sizeof(z_stream), p);
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+       
+       PHP_HTTP_INFLATE_WBITS_SET(s->flags, wbits);
+       
+       if (Z_OK == (status = inflateInit2(ctx, wbits))) {
+               if ((ctx->opaque = php_http_buffer_init_ex(NULL, PHP_HTTP_DEFLATE_BUFFER_SIZE, p ? PHP_HTTP_BUFFER_INIT_PERSISTENT : 0))) {
+                       s->ctx = ctx;
+                       return s;
+               }
+               inflateEnd(ctx);
+               status = Z_MEM_ERROR;
+       }
+       pefree(ctx, p);
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Failed to initialize inflate stream: %s", zError(status));
+       return NULL;
+}
+
+static php_http_encoding_stream_t *dechunk_init(php_http_encoding_stream_t *s)
+{
+       struct dechunk_ctx *ctx = pecalloc(1, sizeof(*ctx), (s->flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT));
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+
+       if (!php_http_buffer_init_ex(&ctx->buffer, PHP_HTTP_BUFFER_DEFAULT_SIZE, (s->flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT) ? PHP_HTTP_BUFFER_INIT_PERSISTENT : 0)) {
+               return NULL;
+       }
+
+       ctx->hexlen = 0;
+       ctx->zeroed = 0;
+       s->ctx = ctx;
+
+       return s;
+}
+
+static php_http_encoding_stream_t *deflate_copy(php_http_encoding_stream_t *from, php_http_encoding_stream_t *to)
+{
+       z_streamp from_ctx = from->ctx, to_ctx = to->ctx;
+
+       deflateCopy(to_ctx, from_ctx);
+       php_http_buffer_append(to_ctx->opaque, PHP_HTTP_BUFFER_VAL(from_ctx->opaque), PHP_HTTP_BUFFER_LEN(from_ctx->opaque));
+
+       return to;
+}
+
+static php_http_encoding_stream_t *inflate_copy(php_http_encoding_stream_t *from, php_http_encoding_stream_t *to)
+{
+       z_streamp from_ctx = from->ctx, to_ctx = to->ctx;
+
+       inflateCopy(to_ctx, from_ctx);
+       php_http_buffer_append(to_ctx->opaque, PHP_HTTP_BUFFER_VAL(from_ctx->opaque), PHP_HTTP_BUFFER_LEN(from_ctx->opaque));
+
+       return to;
+}
+
+static php_http_encoding_stream_t *dechunk_copy(php_http_encoding_stream_t *from, php_http_encoding_stream_t *to)
+{
+       struct dechunk_ctx *from_ctx = from->ctx, *to_ctx = to->ctx;
+
+       to_ctx->hexlen = from_ctx->hexlen;
+       to_ctx->zeroed = from_ctx->zeroed;
+       php_http_buffer_append(&to_ctx->buffer, PHP_HTTP_BUFFER_VAL(&from_ctx->buffer), PHP_HTTP_BUFFER_LEN(&from_ctx->buffer));
+
+       return to;
+}
+
+static STATUS deflate_update(php_http_encoding_stream_t *s, const char *data, size_t data_len, char **encoded, size_t *encoded_len)
+{
+       int status;
+       z_streamp ctx = s->ctx;
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+       
+       /* append input to our buffer */
+       php_http_buffer_append(PHP_HTTP_BUFFER(ctx->opaque), data, data_len);
+       
+       ctx->next_in = (Bytef *) PHP_HTTP_BUFFER_VAL(ctx->opaque);
+       ctx->avail_in = PHP_HTTP_BUFFER_LEN(ctx->opaque);
+       
+       /* deflate */
+       *encoded_len = PHP_HTTP_DEFLATE_BUFFER_SIZE_GUESS(data_len);
+       *encoded = emalloc(*encoded_len);
+       ctx->avail_out = *encoded_len;
+       ctx->next_out = (Bytef *) *encoded;
+       
+       switch (status = deflate(ctx, PHP_HTTP_ENCODING_STREAM_FLUSH_FLAG(s->flags))) {
+               case Z_OK:
+               case Z_STREAM_END:
+                       /* cut processed chunk off the buffer */
+                       if (ctx->avail_in) {
+                               php_http_buffer_cut(PHP_HTTP_BUFFER(ctx->opaque), 0, PHP_HTTP_BUFFER_LEN(ctx->opaque) - ctx->avail_in);
+                       } else {
+                               php_http_buffer_reset(PHP_HTTP_BUFFER(ctx->opaque));
+                       }
+                       
+                       /* size buffer down to actual size */
+                       *encoded_len -= ctx->avail_out;
+                       *encoded = erealloc(*encoded, *encoded_len + 1);
+                       (*encoded)[*encoded_len] = '\0';
+                       return SUCCESS;
+       }
+       
+       STR_SET(*encoded, NULL);
+       *encoded_len = 0;
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Failed to update deflate stream: %s", zError(status));
+       return FAILURE;
+}
+
+static STATUS inflate_update(php_http_encoding_stream_t *s, const char *data, size_t data_len, char **decoded, size_t *decoded_len)
+{
+       int status;
+       z_streamp ctx = s->ctx;
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+       
+       /* append input to buffer */
+       php_http_buffer_append(PHP_HTTP_BUFFER(ctx->opaque), data, data_len);
+       
+retry_raw_inflate:
+       ctx->next_in = (Bytef *) PHP_HTTP_BUFFER_VAL(ctx->opaque);
+       ctx->avail_in = PHP_HTTP_BUFFER_LEN(ctx->opaque);
+       
+       switch (status = php_http_inflate_rounds(ctx, PHP_HTTP_ENCODING_STREAM_FLUSH_FLAG(s->flags), decoded, decoded_len)) {
+               case Z_OK:
+               case Z_STREAM_END:
+                       /* cut off */
+                       if (ctx->avail_in) {
+                               php_http_buffer_cut(PHP_HTTP_BUFFER(ctx->opaque), 0, PHP_HTTP_BUFFER_LEN(ctx->opaque) - ctx->avail_in);
+                       } else {
+                               php_http_buffer_reset(PHP_HTTP_BUFFER(ctx->opaque));
+                       }
+                       return SUCCESS;
+               
+               case Z_DATA_ERROR:
+                       /* raw deflated data ? */
+                       if (!(s->flags & PHP_HTTP_INFLATE_TYPE_RAW) && !ctx->total_out) {
+                               inflateEnd(ctx);
+                               s->flags |= PHP_HTTP_INFLATE_TYPE_RAW;
+                               inflateInit2(ctx, PHP_HTTP_WINDOW_BITS_RAW);
+                               goto retry_raw_inflate;
+                       }
+       }
+       
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Failed to update inflate stream: %s", zError(status));
+       return FAILURE;
+}
+
+static STATUS dechunk_update(php_http_encoding_stream_t *s, const char *data, size_t data_len, char **decoded, size_t *decoded_len)
+{
+       php_http_buffer tmp;
+       struct dechunk_ctx *ctx = s->ctx;
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+
+       if (ctx->zeroed) {
+               return FAILURE;
+       }
+       if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(&ctx->buffer, data, data_len)) {
+               return FAILURE;
+       }
+       if (!php_http_buffer_fix(&ctx->buffer)) {
+               return FAILURE;
+       }
+
+       *decoded = NULL;
+       *decoded_len = 0;
+
+       php_http_buffer_init(&tmp);
+
+       /* we have data in our buffer */
+       while (PHP_HTTP_BUFFER_LEN(&ctx->buffer)) {
+
+               /* we already know the size of the chunk and are waiting for data */
+               if (ctx->hexlen) {
+
+                       /* not enough data buffered */
+                       if (PHP_HTTP_BUFFER_LEN(&ctx->buffer) < ctx->hexlen) {
+
+                               /* flush anyway? */
+                               if (s->flags & PHP_HTTP_ENCODING_STREAM_FLUSH_FULL) {
+                                       /* flush all data (should only be chunk data) */
+                                       php_http_buffer_append(&tmp, PHP_HTTP_BUFFER_VAL(&ctx->buffer), PHP_HTTP_BUFFER_LEN(&ctx->buffer));
+                                       /* waiting for less data now */
+                                       ctx->hexlen -= PHP_HTTP_BUFFER_LEN(&ctx->buffer);
+                                       /* no more buffered data */
+                                       php_http_buffer_reset(&ctx->buffer);
+                                       /* break */
+                               }
+
+                               /* we have too less data and don't need to flush */
+                               else {
+                                       break;
+                               }
+                       }
+
+                       /* we seem to have all data of the chunk */
+                       else {
+                               php_http_buffer_append(&tmp, PHP_HTTP_BUFFER_VAL(&ctx->buffer), ctx->hexlen);
+                               /* remove outgoing data from the buffer */
+                               php_http_buffer_cut(PHP_HTTP_BUFFER(&ctx->buffer), 0, ctx->hexlen);
+                               /* reset hexlen */
+                               ctx->hexlen = 0;
+                               /* continue */
+                       }
+               }
+
+               /* we don't know the length of the chunk yet */
+               else {
+                       size_t off = 0;
+
+                       /* ignore preceeding CRLFs (too loose?) */
+                       while (off < PHP_HTTP_BUFFER_LEN(&ctx->buffer) && (
+                                       PHP_HTTP_BUFFER_VAL(&ctx->buffer)[off] == '\n' ||
+                                       PHP_HTTP_BUFFER_VAL(&ctx->buffer)[off] == '\r')) {
+                               ++off;
+                       }
+                       if (off) {
+                               php_http_buffer_cut(PHP_HTTP_BUFFER(&ctx->buffer), 0, off);
+                       }
+
+                       /* still data there? */
+                       if (PHP_HTTP_BUFFER_LEN(&ctx->buffer)) {
+                               int eollen;
+                               const char *eolstr;
+
+                               /* we need eol, so we can be sure we have all hex digits */
+                               php_http_buffer_fix(&ctx->buffer);
+                               if ((eolstr = php_http_locate_bin_eol(PHP_HTTP_BUFFER_VAL(&ctx->buffer), PHP_HTTP_BUFFER_LEN(&ctx->buffer), &eollen))) {
+                                       char *stop = NULL;
+
+                                       /* read in chunk size */
+                                       ctx->hexlen = strtoul(PHP_HTTP_BUFFER_VAL(&ctx->buffer), &stop, 16);
+
+                                       /*      if strtoul() stops at the beginning of the buffered data
+                                               there's domething oddly wrong, i.e. bad input */
+                                       if (stop == PHP_HTTP_BUFFER_VAL(&ctx->buffer)) {
+                                               php_http_buffer_dtor(&tmp);
+                                               return FAILURE;
+                                       }
+
+                                       /* cut out <chunk size hex><chunk extension><eol> */
+                                       php_http_buffer_cut(PHP_HTTP_BUFFER(&ctx->buffer), 0, eolstr + eollen - PHP_HTTP_BUFFER_VAL(&ctx->buffer));
+                                       /* buffer->hexlen is 0 now or contains the size of the next chunk */
+                                       if (!ctx->hexlen) {
+                                               size_t off = 0;
+
+                                               /* ignore following CRLFs (too loose?) */
+                                               while (off < PHP_HTTP_BUFFER_LEN(&ctx->buffer) && (
+                                                               PHP_HTTP_BUFFER_VAL(&ctx->buffer)[off] == '\n' ||
+                                                               PHP_HTTP_BUFFER_VAL(&ctx->buffer)[off] == '\r')) {
+                                                       ++off;
+                                               }
+                                               if (off) {
+                                                       php_http_buffer_cut(PHP_HTTP_BUFFER(&ctx->buffer), 0, off);
+                                               }
+
+                                               ctx->zeroed = 1;
+                                               break;
+                                       }
+                                       /* continue */
+                               } else {
+                                       /* we have not enough data buffered to read in chunk size */
+                                       break;
+                               }
+                       }
+                       /* break */
+               }
+       }
+
+       php_http_buffer_fix(&tmp);
+       *decoded = PHP_HTTP_BUFFER_VAL(&tmp);
+       *decoded_len = PHP_HTTP_BUFFER_LEN(&tmp);
+
+       return SUCCESS;
+}
+
+static STATUS deflate_flush(php_http_encoding_stream_t *s, char **encoded, size_t *encoded_len)
+{
+       int status;
+       z_streamp ctx = s->ctx;
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+       
+       *encoded_len = PHP_HTTP_DEFLATE_BUFFER_SIZE;
+       *encoded = emalloc(*encoded_len);
+       
+       ctx->avail_in = 0;
+       ctx->next_in = NULL;
+       ctx->avail_out = *encoded_len;
+       ctx->next_out = (Bytef *) *encoded;
+       
+       switch (status = deflate(ctx, Z_FULL_FLUSH)) {
+               case Z_OK:
+               case Z_STREAM_END:
+                       *encoded_len = PHP_HTTP_DEFLATE_BUFFER_SIZE - ctx->avail_out;
+                       *encoded = erealloc(*encoded, *encoded_len + 1);
+                       (*encoded)[*encoded_len] = '\0';
+                       return SUCCESS;
+       }
+       
+       STR_SET(*encoded, NULL);
+       *encoded_len = 0;
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Failed to flush deflate stream: %s", zError(status));
+       return FAILURE;
+}
+
+static STATUS dechunk_flush(php_http_encoding_stream_t *s, char **decoded, size_t *decoded_len)
+{
+       struct dechunk_ctx *ctx = s->ctx;
+
+       if (ctx->hexlen) {
+               /* flush all data (should only be chunk data) */
+               php_http_buffer_fix(&ctx->buffer);
+               php_http_buffer_data(&ctx->buffer, decoded, decoded_len);
+               /* waiting for less data now */
+               ctx->hexlen -= PHP_HTTP_BUFFER_LEN(&ctx->buffer);
+               /* no more buffered data */
+               php_http_buffer_reset(&ctx->buffer);
+       } else {
+               *decoded = NULL;
+               *decoded_len = 0;
+       }
+
+       return SUCCESS;
+}
+
+static STATUS deflate_finish(php_http_encoding_stream_t *s, char **encoded, size_t *encoded_len)
+{
+       int status;
+       z_streamp ctx = s->ctx;
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+       
+       *encoded_len = PHP_HTTP_DEFLATE_BUFFER_SIZE;
+       *encoded = emalloc(*encoded_len);
+       
+       /* deflate remaining input */
+       ctx->next_in = (Bytef *) PHP_HTTP_BUFFER_VAL(ctx->opaque);
+       ctx->avail_in = PHP_HTTP_BUFFER_LEN(ctx->opaque);
+       
+       ctx->avail_out = *encoded_len;
+       ctx->next_out = (Bytef *) *encoded;
+       
+       do {
+               status = deflate(ctx, Z_FINISH);
+       } while (Z_OK == status);
+       
+       if (Z_STREAM_END == status) {
+               /* cut processed intp off */
+               php_http_buffer_cut(PHP_HTTP_BUFFER(ctx->opaque), 0, PHP_HTTP_BUFFER_LEN(ctx->opaque) - ctx->avail_in);
+               
+               /* size down */
+               *encoded_len -= ctx->avail_out;
+               *encoded = erealloc(*encoded, *encoded_len + 1);
+               (*encoded)[*encoded_len] = '\0';
+               return SUCCESS;
+       }
+       
+       STR_SET(*encoded, NULL);
+       *encoded_len = 0;
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Failed to finish deflate stream: %s", zError(status));
+       return FAILURE;
+}
+
+static STATUS inflate_finish(php_http_encoding_stream_t *s, char **decoded, size_t *decoded_len)
+{
+       int status;
+       z_streamp ctx = s->ctx;
+       TSRMLS_FETCH_FROM_CTX(s->ts);
+       
+       if (!PHP_HTTP_BUFFER_LEN(ctx->opaque)) {
+               *decoded = NULL;
+               *decoded_len = 0;
+               return SUCCESS;
+       }
+       
+       *decoded_len = (PHP_HTTP_BUFFER_LEN(ctx->opaque) + 1) * PHP_HTTP_INFLATE_ROUNDS;
+       *decoded = emalloc(*decoded_len);
+       
+       /* inflate remaining input */
+       ctx->next_in = (Bytef *) PHP_HTTP_BUFFER_VAL(ctx->opaque);
+       ctx->avail_in = PHP_HTTP_BUFFER_LEN(ctx->opaque);
+       
+       ctx->avail_out = *decoded_len;
+       ctx->next_out = (Bytef *) *decoded;
+       
+       if (Z_STREAM_END == (status = inflate(ctx, Z_FINISH))) {
+               /* cut processed input off */
+               php_http_buffer_cut(PHP_HTTP_BUFFER(ctx->opaque), 0, PHP_HTTP_BUFFER_LEN(ctx->opaque) - ctx->avail_in);
+               
+               /* size down */
+               *decoded_len -= ctx->avail_out;
+               *decoded = erealloc(*decoded, *decoded_len + 1);
+               (*decoded)[*decoded_len] = '\0';
+               return SUCCESS;
+       }
+       
+       STR_SET(*decoded, NULL);
+       *decoded_len = 0;
+       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Failed to finish inflate stream: %s", zError(status));
+       return FAILURE;
+}
+
+static zend_bool deflate_done(php_http_encoding_stream_t *s)
+{
+       z_streamp ctx = s->ctx;
+       return !ctx->avail_in && !PHP_HTTP_BUFFER_LEN(ctx->opaque);
+}
+
+static zend_bool inflate_done(php_http_encoding_stream_t *s)
+{
+       z_streamp ctx = s->ctx;
+       return !ctx->avail_in && !PHP_HTTP_BUFFER_LEN(ctx->opaque);
+}
+
+static zend_bool dechunk_done(php_http_encoding_stream_t *s)
+{
+       return ((struct dechunk_ctx *) s->ctx)->zeroed;
+}
+
+static void deflate_dtor(php_http_encoding_stream_t *s)
+{
+       if (s->ctx) {
+               z_streamp ctx = s->ctx;
+
+               if (ctx->opaque) {
+                       php_http_buffer_free((php_http_buffer **) &ctx->opaque);
+               }
+               deflateEnd(ctx);
+               pefree(ctx, (s->flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT));
+               s->ctx = NULL;
+       }
+}
+
+static void inflate_dtor(php_http_encoding_stream_t *s)
+{
+       if (s->ctx) {
+               z_streamp ctx = s->ctx;
+
+               if (ctx->opaque) {
+                       php_http_buffer_free((php_http_buffer **) &ctx->opaque);
+               }
+               inflateEnd(ctx);
+               pefree(ctx, (s->flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT));
+               s->ctx = NULL;
+       }
+}
+
+static void dechunk_dtor(php_http_encoding_stream_t *s)
+{
+       if (s->ctx) {
+               struct dechunk_ctx *ctx = s->ctx;
+
+               php_http_buffer_dtor(&ctx->buffer);
+               pefree(ctx, (s->flags & PHP_HTTP_ENCODING_STREAM_PERSISTENT));
+               s->ctx = NULL;
+       }
+}
+
+static php_http_encoding_stream_ops_t php_http_encoding_deflate_ops = {
+       deflate_init,
+       deflate_copy,
+       deflate_update,
+       deflate_flush,
+       deflate_done,
+       deflate_finish,
+       deflate_dtor
+};
+
+PHP_HTTP_API php_http_encoding_stream_ops_t *php_http_encoding_stream_get_deflate_ops(void)
+{
+       return &php_http_encoding_deflate_ops;
+}
+
+static php_http_encoding_stream_ops_t php_http_encoding_inflate_ops = {
+       inflate_init,
+       inflate_copy,
+       inflate_update,
+       NULL,
+       inflate_done,
+       inflate_finish,
+       inflate_dtor
+};
+
+PHP_HTTP_API php_http_encoding_stream_ops_t *php_http_encoding_stream_get_inflate_ops(void)
+{
+       return &php_http_encoding_inflate_ops;
+}
+
+static php_http_encoding_stream_ops_t php_http_encoding_dechunk_ops = {
+       dechunk_init,
+       dechunk_copy,
+       dechunk_update,
+       dechunk_flush,
+       dechunk_done,
+       NULL,
+       dechunk_dtor
+};
+
+PHP_HTTP_API php_http_encoding_stream_ops_t *php_http_encoding_stream_get_dechunk_ops(void)
+{
+       return &php_http_encoding_dechunk_ops;
+}
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpEncodingStream, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpEncodingStream, method, 0)
+#define PHP_HTTP_ENCSTREAM_ME(method, visibility)      PHP_ME(HttpEncodingStream, method, PHP_HTTP_ARGS(HttpEncodingStream, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(__construct, 0)
+       PHP_HTTP_ARG_VAL(flags, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(update, 1)
+       PHP_HTTP_ARG_VAL(data, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(flush);
+PHP_HTTP_EMPTY_ARGS(done);
+PHP_HTTP_EMPTY_ARGS(finish);
+
+zend_class_entry *php_http_encoding_stream_class_entry;
+zend_function_entry php_http_encoding_stream_method_entry[] = {
+       PHP_HTTP_ENCSTREAM_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+       PHP_HTTP_ENCSTREAM_ME(update, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENCSTREAM_ME(flush, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENCSTREAM_ME(done, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENCSTREAM_ME(finish, ZEND_ACC_PUBLIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+static zend_object_handlers php_http_encoding_stream_object_handlers;
+
+zend_object_value php_http_encoding_stream_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_encoding_stream_object_new_ex(ce, NULL, NULL TSRMLS_CC);
+}
+
+zend_object_value php_http_encoding_stream_object_new_ex(zend_class_entry *ce, php_http_encoding_stream_t *s, php_http_encoding_stream_object_t **ptr TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_encoding_stream_object_t *o;
+
+       o = ecalloc(1, sizeof(*o));
+       zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       if (ptr) {
+               *ptr = o;
+       }
+
+       if (s) {
+               o->stream = s;
+       }
+
+       ov.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_encoding_stream_object_free, NULL TSRMLS_CC);
+       ov.handlers = &php_http_encoding_stream_object_handlers;
+
+       return ov;
+}
+
+zend_object_value php_http_encoding_stream_object_clone(zval *this_ptr TSRMLS_DC)
+{
+       zend_object_value new_ov;
+       php_http_encoding_stream_object_t *new_obj = NULL, *old_obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       new_ov = php_http_encoding_stream_object_new_ex(old_obj->zo.ce, php_http_encoding_stream_copy(old_obj->stream, NULL), &new_obj TSRMLS_CC);
+       zend_objects_clone_members(&new_obj->zo, new_ov, &old_obj->zo, Z_OBJ_HANDLE_P(this_ptr) TSRMLS_CC);
+
+       return new_ov;
+}
+
+void php_http_encoding_stream_object_free(void *object TSRMLS_DC)
+{
+       php_http_encoding_stream_object_t *o = (php_http_encoding_stream_object_t *) object;
+
+       if (o->stream) {
+               php_http_encoding_stream_free(&o->stream TSRMLS_CC);
+       }
+       zend_object_std_dtor((zend_object *) o TSRMLS_CC);
+       efree(o);
+}
+
+PHP_METHOD(HttpEncodingStream, __construct)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               long flags = 0;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(encoding)) {
+                               php_http_encoding_stream_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                               if (!obj->stream) {
+                                       php_http_encoding_stream_ops_t *ops = NULL;
+
+                                       if (instanceof_function(obj->zo.ce, php_http_deflate_stream_class_entry TSRMLS_CC)) {
+                                               ops = &php_http_encoding_deflate_ops;
+                                       } else if (instanceof_function(obj->zo.ce, php_http_inflate_stream_class_entry TSRMLS_CC)) {
+                                               ops = &php_http_encoding_inflate_ops;
+                                       } else if (instanceof_function(obj->zo.ce, php_http_dechunk_stream_class_entry TSRMLS_CC)) {
+                                               ops = &php_http_encoding_dechunk_ops;
+                                       }
+
+                                       if (ops) {
+                                               obj->stream = php_http_encoding_stream_init(obj->stream, ops, flags TSRMLS_CC);
+                                       } else {
+                                               php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Unknown HttpEncodingStream class %s", obj->zo.ce->name);
+                                       }
+
+                               } else {
+                                       php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "HttpEncodingStream cannot be initialized twice");
+                               }
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpEncodingStream, update)
+{
+       int data_len;
+       char *data_str;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &data_str, &data_len)) {
+               php_http_encoding_stream_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->stream) {
+                       size_t encoded_len;
+                       char *encoded_str;
+
+                       if (SUCCESS == php_http_encoding_stream_update(obj->stream, data_str, data_len, &encoded_str, &encoded_len TSRMLS_CC)) {
+                               RETURN_STRINGL(encoded_str, encoded_len, 0);
+                       }
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEncodingStream, flush)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_encoding_stream_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->stream) {
+                       char *encoded_str;
+                       size_t encoded_len;
+
+                       if (SUCCESS == php_http_encoding_stream_flush(obj->stream, &encoded_str, &encoded_len TSRMLS_CC)) {
+                               RETURN_STRINGL(encoded_str, encoded_len, 0);
+                       }
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEncodingStream, done)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_encoding_stream_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->stream) {
+                       RETURN_BOOL(php_http_encoding_stream_done(obj->stream));
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEncodingStream, finish)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_encoding_stream_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->stream) {
+                       char *encoded_str;
+                       size_t encoded_len;
+
+                       if (SUCCESS == php_http_encoding_stream_finish(obj->stream, &encoded_str, &encoded_len TSRMLS_CC)) {
+                               if (SUCCESS == php_http_encoding_stream_reset(&obj->stream)) {
+                                       RETURN_STRINGL(encoded_str, encoded_len, 0);
+                               } else {
+                                       STR_FREE(encoded_str);
+                               }
+                       }
+               }
+       }
+       RETURN_FALSE;
+}
+
+#undef PHP_HTTP_BEGIN_ARGS
+#undef PHP_HTTP_EMPTY_ARGS
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpDeflateStream, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpDeflateStream, method, 0)
+#define PHP_HTTP_DEFLATE_ME(method, visibility)        PHP_ME(HttpDeflateStream, method, PHP_HTTP_ARGS(HttpDeflateStream, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(encode, 1)
+       PHP_HTTP_ARG_VAL(data, 0)
+       PHP_HTTP_ARG_VAL(flags, 0)
+PHP_HTTP_END_ARGS;
+
+zend_class_entry *php_http_deflate_stream_class_entry;
+zend_function_entry php_http_deflate_stream_method_entry[] = {
+       PHP_HTTP_DEFLATE_ME(encode, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_METHOD(HttpDeflateStream, encode)
+{
+       char *str;
+       int len;
+       long flags = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &len, &flags)) {
+               char *enc_str;
+               size_t enc_len;
+
+               if (SUCCESS == php_http_encoding_deflate(flags, str, len, &enc_str, &enc_len TSRMLS_CC)) {
+                       RETURN_STRINGL(enc_str, enc_len, 0);
+               }
+       }
+       RETURN_FALSE;
+}
+
+#undef PHP_HTTP_BEGIN_ARGS
+#undef PHP_HTTP_EMPTY_ARGS
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpInflateStream, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpInflateStream, method, 0)
+#define PHP_HTTP_INFLATE_ME(method, visibility)        PHP_ME(HttpInflateStream, method, PHP_HTTP_ARGS(HttpInflateStream, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(decode, 1)
+       PHP_HTTP_ARG_VAL(data, 0)
+PHP_HTTP_END_ARGS;
+
+zend_class_entry *php_http_inflate_stream_class_entry;
+zend_function_entry php_http_inflate_stream_method_entry[] = {
+       PHP_HTTP_INFLATE_ME(decode, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_METHOD(HttpInflateStream, decode)
+{
+       char *str;
+       int len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len)) {
+               char *enc_str;
+               size_t enc_len;
+
+               if (SUCCESS == php_http_encoding_inflate(str, len, &enc_str, &enc_len TSRMLS_CC)) {
+                       RETURN_STRINGL(enc_str, enc_len, 0);
+               }
+       }
+       RETURN_FALSE;
+}
+
+#undef PHP_HTTP_BEGIN_ARGS
+#undef PHP_HTTP_EMPTY_ARGS
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpDechunkStream, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpDechunkStream, method, 0)
+#define PHP_HTTP_DECHUNK_ME(method, visibility)        PHP_ME(HttpDechunkStream, method, PHP_HTTP_ARGS(HttpDechunkStream, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(decode, 1)
+       PHP_HTTP_ARG_VAL(data, 0)
+       PHP_HTTP_ARG_VAL(decoded_len, 1)
+PHP_HTTP_END_ARGS;
+
+zend_class_entry *php_http_dechunk_stream_class_entry;
+zend_function_entry php_http_dechunk_stream_method_entry[] = {
+       PHP_HTTP_DECHUNK_ME(decode, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_METHOD(HttpDechunkStream, decode)
+{
+       char *str;
+       int len;
+       zval *zlen;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z!", &str, &len, &zlen)) {
+               const char *end_ptr;
+               char *enc_str;
+               size_t enc_len;
+
+               if ((end_ptr = php_http_encoding_dechunk(str, len, &enc_str, &enc_len TSRMLS_CC))) {
+                       if (zlen) {
+                               zval_dtor(zlen);
+                               ZVAL_LONG(zlen, str + len - end_ptr);
+                       }
+                       RETURN_STRINGL(enc_str, enc_len, 0);
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_MINIT_FUNCTION(http_encoding)
+{
+       PHP_HTTP_REGISTER_CLASS(http\\encoding, Stream, http_encoding_stream, php_http_object_class_entry, ZEND_ACC_EXPLICIT_ABSTRACT_CLASS);
+       php_http_encoding_stream_class_entry->create_object = php_http_encoding_stream_object_new;
+       memcpy(&php_http_encoding_stream_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_encoding_stream_object_handlers.clone_obj = php_http_encoding_stream_object_clone;
+
+       zend_declare_class_constant_long(php_http_encoding_stream_class_entry, ZEND_STRL("FLUSH_NONE"), PHP_HTTP_ENCODING_STREAM_FLUSH_NONE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_encoding_stream_class_entry, ZEND_STRL("FLUSH_SYNC"), PHP_HTTP_ENCODING_STREAM_FLUSH_SYNC TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_encoding_stream_class_entry, ZEND_STRL("FLUSH_FULL"), PHP_HTTP_ENCODING_STREAM_FLUSH_FULL TSRMLS_CC);
+
+       PHP_HTTP_REGISTER_CLASS(http\\encoding\\stream, Deflate, http_deflate_stream, php_http_encoding_stream_class_entry, 0);
+
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("TYPE_GZIP"), PHP_HTTP_DEFLATE_TYPE_GZIP TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("TYPE_ZLIB"), PHP_HTTP_DEFLATE_TYPE_ZLIB TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("TYPE_RAW"), PHP_HTTP_DEFLATE_TYPE_RAW TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("LEVEL_DEF"), PHP_HTTP_DEFLATE_LEVEL_DEF TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("LEVEL_MIN"), PHP_HTTP_DEFLATE_LEVEL_MIN TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("LEVEL_MAX"), PHP_HTTP_DEFLATE_LEVEL_MAX TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("STRATEGY_DEF"), PHP_HTTP_DEFLATE_STRATEGY_DEF TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("STRATEGY_FILT"), PHP_HTTP_DEFLATE_STRATEGY_FILT TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("STRATEGY_HUFF"), PHP_HTTP_DEFLATE_STRATEGY_HUFF TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("STRATEGY_RLE"), PHP_HTTP_DEFLATE_STRATEGY_RLE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_deflate_stream_class_entry, ZEND_STRL("STRATEGY_FIXED"), PHP_HTTP_DEFLATE_STRATEGY_FIXED TSRMLS_CC);
+
+       PHP_HTTP_REGISTER_CLASS(http\\encoding\\stream, Inflate, http_inflate_stream, php_http_encoding_stream_class_entry, 0);
+       PHP_HTTP_REGISTER_CLASS(http\\encoding\\stream, Dechunk, http_dechunk_stream, php_http_encoding_stream_class_entry, 0);
+
+       return SUCCESS;
+}
+
+
+/*
+ * 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_encoding.h b/php_http_encoding.h
new file mode 100644 (file)
index 0000000..9e700a7
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_encoding_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_ENCODING_H
+#define PHP_HTTP_ENCODING_H
+
+PHP_HTTP_API int php_http_encoding_response_start(size_t content_length, zend_bool ignore_http_ohandler TSRMLS_DC);
+
+extern PHP_MINIT_FUNCTION(http_encoding);
+extern PHP_RINIT_FUNCTION(http_encoding);
+extern PHP_RSHUTDOWN_FUNCTION(http_encoding);
+
+typedef enum php_http_encoding_type {
+       PHP_HTTP_ENCODING_NONE,
+       PHP_HTTP_ENCODING_GZIP,
+       PHP_HTTP_ENCODING_DEFLATE,
+} php_http_encoding_type_t;
+
+#define PHP_HTTP_INFLATE_ROUNDS 100
+
+#define PHP_HTTP_DEFLATE_BUFFER_SIZE_GUESS(S) \
+       (((size_t) ((double) S * (double) 1.015)) + 10 + 8 + 4 + 1)
+#define PHP_HTTP_INFLATE_BUFFER_SIZE_GUESS(S) \
+       (((S) + 1) << 3)
+#define PHP_HTTP_INFLATE_BUFFER_SIZE_ALIGN(S) \
+       ((S) += (S) >> (3))
+
+#define PHP_HTTP_DEFLATE_BUFFER_SIZE           0x8000
+#define PHP_HTTP_INFLATE_BUFFER_SIZE           0x1000
+
+#define PHP_HTTP_DEFLATE_LEVEL_DEF                     0x00000000
+#define PHP_HTTP_DEFLATE_LEVEL_MIN                     0x00000001
+#define PHP_HTTP_DEFLATE_LEVEL_MAX                     0x00000009
+#define PHP_HTTP_DEFLATE_TYPE_ZLIB                     0x00000000
+#define PHP_HTTP_DEFLATE_TYPE_GZIP                     0x00000010
+#define PHP_HTTP_DEFLATE_TYPE_RAW                      0x00000020
+#define PHP_HTTP_DEFLATE_STRATEGY_DEF          0x00000000
+#define PHP_HTTP_DEFLATE_STRATEGY_FILT         0x00000100
+#define PHP_HTTP_DEFLATE_STRATEGY_HUFF         0x00000200
+#define PHP_HTTP_DEFLATE_STRATEGY_RLE          0x00000300
+#define PHP_HTTP_DEFLATE_STRATEGY_FIXED                0x00000400
+
+#define PHP_HTTP_DEFLATE_LEVEL_SET(flags, level) \
+       switch (flags & 0xf) \
+       { \
+               default: \
+                       if ((flags & 0xf) < 10) { \
+                               level = flags & 0xf; \
+                               break; \
+                       } \
+               case PHP_HTTP_DEFLATE_LEVEL_DEF: \
+                       level = Z_DEFAULT_COMPRESSION; \
+               break; \
+       }
+       
+#define PHP_HTTP_DEFLATE_WBITS_SET(flags, wbits) \
+       switch (flags & 0xf0) \
+       { \
+               case PHP_HTTP_DEFLATE_TYPE_GZIP: \
+                       wbits = PHP_HTTP_WINDOW_BITS_GZIP; \
+               break; \
+               case PHP_HTTP_DEFLATE_TYPE_RAW: \
+                       wbits = PHP_HTTP_WINDOW_BITS_RAW; \
+               break; \
+               default: \
+                       wbits = PHP_HTTP_WINDOW_BITS_ZLIB; \
+               break; \
+       }
+
+#define PHP_HTTP_INFLATE_WBITS_SET(flags, wbits) \
+       if (flags & PHP_HTTP_INFLATE_TYPE_RAW) { \
+               wbits = PHP_HTTP_WINDOW_BITS_RAW; \
+} else { \
+               wbits = PHP_HTTP_WINDOW_BITS_ANY; \
+}
+
+#define PHP_HTTP_DEFLATE_STRATEGY_SET(flags, strategy) \
+       switch (flags & 0xf00) \
+       { \
+               case PHP_HTTP_DEFLATE_STRATEGY_FILT: \
+                       strategy = Z_FILTERED; \
+               break; \
+               case PHP_HTTP_DEFLATE_STRATEGY_HUFF: \
+                       strategy = Z_HUFFMAN_ONLY; \
+               break; \
+               case PHP_HTTP_DEFLATE_STRATEGY_RLE: \
+                       strategy = Z_RLE; \
+               break; \
+               case PHP_HTTP_DEFLATE_STRATEGY_FIXED: \
+                       strategy = Z_FIXED; \
+               break; \
+               default: \
+                       strategy = Z_DEFAULT_STRATEGY; \
+               break; \
+       }
+
+#define PHP_HTTP_WINDOW_BITS_ZLIB      0x0000000f
+#define PHP_HTTP_WINDOW_BITS_GZIP      0x0000001f
+#define PHP_HTTP_WINDOW_BITS_ANY       0x0000002f
+#define PHP_HTTP_WINDOW_BITS_RAW       -0x000000f
+
+#ifndef Z_FIXED
+/* Z_FIXED does not exist prior 1.2.2.2 */
+#      define Z_FIXED 0
+#endif
+
+#define PHP_HTTP_INFLATE_TYPE_ZLIB                     0x00000000
+#define PHP_HTTP_INFLATE_TYPE_GZIP                     0x00000000
+#define PHP_HTTP_INFLATE_TYPE_RAW                      0x00000001
+
+#define PHP_HTTP_ENCODING_STREAM_FLUSH_NONE    0x00000000
+#define PHP_HTTP_ENCODING_STREAM_FLUSH_SYNC 0x00100000
+#define PHP_HTTP_ENCODING_STREAM_FLUSH_FULL 0x00200000
+
+#define PHP_HTTP_ENCODING_STREAM_FLUSH_FLAG(f) \
+       (((f) & PHP_HTTP_ENCODING_STREAM_FLUSH_FULL) ? Z_FULL_FLUSH : \
+       (((f) & PHP_HTTP_ENCODING_STREAM_FLUSH_SYNC) ? Z_SYNC_FLUSH : Z_NO_FLUSH))
+
+#define PHP_HTTP_ENCODING_STREAM_PERSISTENT    0x01000000
+
+typedef struct php_http_encoding_stream php_http_encoding_stream_t;
+
+typedef php_http_encoding_stream_t *(*php_http_encoding_stream_init_func_t)(php_http_encoding_stream_t *s);
+typedef php_http_encoding_stream_t *(*php_http_encoding_stream_copy_func_t)(php_http_encoding_stream_t *from, php_http_encoding_stream_t *to);
+typedef STATUS (*php_http_encoding_stream_update_func_t)(php_http_encoding_stream_t *s, const char *in_str, size_t in_len, char **out_str, size_t *out_len);
+typedef STATUS (*php_http_encoding_stream_flush_func_t)(php_http_encoding_stream_t *s, char **out_str, size_t *out_len);
+typedef zend_bool (*php_http_encoding_stream_done_func_t)(php_http_encoding_stream_t *s);
+typedef STATUS (*php_http_encoding_stream_finish_func_t)(php_http_encoding_stream_t *s, char **out_str, size_t *out_len);
+typedef void (*php_http_encoding_stream_dtor_func_t)(php_http_encoding_stream_t *s);
+
+typedef struct php_http_encoding_stream_ops {
+       php_http_encoding_stream_init_func_t init;
+       php_http_encoding_stream_copy_func_t copy;
+       php_http_encoding_stream_update_func_t update;
+       php_http_encoding_stream_flush_func_t flush;
+       php_http_encoding_stream_done_func_t done;
+       php_http_encoding_stream_finish_func_t finish;
+       php_http_encoding_stream_dtor_func_t dtor;
+} php_http_encoding_stream_ops_t;
+
+struct php_http_encoding_stream {
+       unsigned flags;
+       void *ctx;
+       php_http_encoding_stream_ops_t *ops;
+#ifdef ZTS
+       void ***ts;
+#endif
+};
+
+extern php_http_encoding_stream_ops_t *php_http_encoding_stream_get_deflate_ops(void);
+extern php_http_encoding_stream_ops_t *php_http_encoding_stream_get_inflate_ops(void);
+extern php_http_encoding_stream_ops_t *php_http_encoding_stream_get_dechunk_ops(void);
+
+PHP_HTTP_API php_http_encoding_stream_t *php_http_encoding_stream_init(php_http_encoding_stream_t *s, php_http_encoding_stream_ops_t *ops, unsigned flags TSRMLS_DC);
+PHP_HTTP_API php_http_encoding_stream_t *php_http_encoding_stream_copy(php_http_encoding_stream_t *from, php_http_encoding_stream_t *to);
+PHP_HTTP_API STATUS php_http_encoding_stream_reset(php_http_encoding_stream_t **s);
+PHP_HTTP_API STATUS php_http_encoding_stream_update(php_http_encoding_stream_t *s, const char *in_str, size_t in_len, char **out_str, size_t *out_len);
+PHP_HTTP_API STATUS php_http_encoding_stream_flush(php_http_encoding_stream_t *s, char **out_str, size_t *len);
+PHP_HTTP_API zend_bool php_http_encoding_stream_done(php_http_encoding_stream_t *s);
+PHP_HTTP_API STATUS php_http_encoding_stream_finish(php_http_encoding_stream_t *s, char **out_str, size_t *len);
+PHP_HTTP_API void php_http_encoding_stream_dtor(php_http_encoding_stream_t *s);
+PHP_HTTP_API void php_http_encoding_stream_free(php_http_encoding_stream_t **s);
+
+PHP_HTTP_API const char *php_http_encoding_dechunk(const char *encoded, size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_encoding_deflate(int flags, const char *data, size_t data_len, char **encoded, size_t *encoded_len TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_encoding_inflate(const char *data, size_t data_len, char **decoded, size_t *decoded_len TSRMLS_DC);
+
+typedef struct php_http_encoding_stream_object {
+       zend_object zo;
+       php_http_encoding_stream_t *stream;
+} php_http_encoding_stream_object_t;
+
+extern zend_class_entry *php_http_encoding_stream_class_entry;
+extern zend_function_entry php_http_encoding_stream_method_entry[];
+
+extern zend_object_value php_http_encoding_stream_object_new(zend_class_entry *ce TSRMLS_DC);
+extern zend_object_value php_http_encoding_stream_object_new_ex(zend_class_entry *ce, php_http_encoding_stream_t *s, php_http_encoding_stream_object_t **ptr TSRMLS_DC);
+extern zend_object_value php_http_encoding_stream_object_clone(zval *object TSRMLS_DC);
+extern void php_http_encoding_stream_object_free(void *object TSRMLS_DC);
+
+extern zend_class_entry *php_http_deflate_stream_class_entry;
+extern zend_function_entry php_http_deflate_stream_method_entry[];
+extern zend_class_entry *php_http_inflate_stream_class_entry;
+extern zend_function_entry php_http_inflate_stream_method_entry[];
+extern zend_class_entry *php_http_dechunk_stream_class_entry;
+extern zend_function_entry php_http_dechunk_stream_method_entry[];
+
+PHP_METHOD(HttpEncodingStream, __construct);
+PHP_METHOD(HttpEncodingStream, update);
+PHP_METHOD(HttpEncodingStream, flush);
+PHP_METHOD(HttpEncodingStream, done);
+PHP_METHOD(HttpEncodingStream, finish);
+
+PHP_METHOD(HttpDeflateStream, encode);
+PHP_METHOD(HttpInflateStream, decode);
+PHP_METHOD(HttpDechunkStream, decode);
+
+/*
+typedef struct php_http_inflatestream_object {
+       zend_object zo;
+       php_http_encoding_stream_t *stream;
+} php_http_inflatestream_object_t;
+
+extern zend_class_entry *php_http_inflatestream_class_entry;
+extern zend_function_entry php_http_inflatestream_method_entry[];
+
+extern zend_object_value php_http_inflatestream_object_new(zend_class_entry *ce TSRMLS_DC);
+extern zend_object_value php_http_inflatestream_object_new_ex(zend_class_entry *ce, php_http_encoding_stream_t *s, php_http_inflatestream_object_t **ptr TSRMLS_DC);
+extern zend_object_value php_http_inflatestream_object_clone(zval *object TSRMLS_DC);
+extern void php_http_inflatestream_object_free(void *object TSRMLS_DC);
+
+PHP_METHOD(HttpInflateStream, __construct);
+PHP_METHOD(HttpInflateStream, factory);
+PHP_METHOD(HttpInflateStream, update);
+PHP_METHOD(HttpInflateStream, flush);
+PHP_METHOD(HttpInflateStream, finish);
+*/
+#endif
+
+/*
+ * 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_env.c b/php_http_env.c
new file mode 100644 (file)
index 0000000..59442fc
--- /dev/null
@@ -0,0 +1,1704 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id $ */
+
+#include "php_http.h"
+
+PHP_RINIT_FUNCTION(http_env)
+{
+       PHP_HTTP_G->env.response.last_modified = 0;
+       PHP_HTTP_G->env.response.throttle_chunk = 0;
+       PHP_HTTP_G->env.response.throttle_delay = 0;
+       PHP_HTTP_G->env.request.time = sapi_get_request_time(TSRMLS_C);
+
+       return SUCCESS;
+}
+
+PHP_RSHUTDOWN_FUNCTION(http_env)
+{
+       if (PHP_HTTP_G->env.request.headers) {
+               zend_hash_destroy(PHP_HTTP_G->env.request.headers);
+               FREE_HASHTABLE(PHP_HTTP_G->env.request.headers);
+               PHP_HTTP_G->env.request.headers = NULL;
+       }
+       if (PHP_HTTP_G->env.request.body) {
+               php_http_message_body_free(&PHP_HTTP_G->env.request.body);
+       }
+       if (PHP_HTTP_G->env.response.body) {
+               php_http_message_body_free(&PHP_HTTP_G->env.response.body);
+       }
+       STR_SET(PHP_HTTP_G->env.response.content_type, NULL);
+       STR_SET(PHP_HTTP_G->env.response.etag, NULL);
+
+       if (PHP_HTTP_G->env.server_var) {
+               zval_ptr_dtor(&PHP_HTTP_G->env.server_var);
+               PHP_HTTP_G->env.server_var = NULL;
+       }
+
+       return SUCCESS;
+}
+
+PHP_HTTP_API void php_http_env_get_request_headers(HashTable *headers TSRMLS_DC)
+{
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       zval **hsv, **header;
+       HashPosition pos;
+
+       if (!PHP_HTTP_G->env.request.headers) {
+               ALLOC_HASHTABLE(PHP_HTTP_G->env.request.headers);
+               zend_hash_init(PHP_HTTP_G->env.request.headers, 0, NULL, ZVAL_PTR_DTOR, 0);
+
+               zend_is_auto_global("_SERVER", lenof("_SERVER") TSRMLS_CC);
+
+               if (SUCCESS == zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void *) &hsv) && Z_TYPE_PP(hsv) == IS_ARRAY) {
+                       FOREACH_KEY(pos, *hsv, key) {
+                               if (key.type == HASH_KEY_IS_STRING && key.len > 6 && !strncmp(key.str, "HTTP_", 5)) {
+                                       key.len -= 5;
+                                       key.str = php_http_pretty_key(estrndup(key.str + 5, key.len - 1), key.len - 1, 1, 1);
+
+                                       zend_hash_get_current_data_ex(Z_ARRVAL_PP(hsv), (void *) &header, &pos);
+                                       Z_ADDREF_P(*header);
+                                       zend_hash_add(PHP_HTTP_G->env.request.headers, key.str, key.len, (void *) header, sizeof(zval *), NULL);
+
+                                       efree(key.str);
+                               }
+                       }
+               }
+       }
+
+       if (headers) {
+               zend_hash_copy(headers, PHP_HTTP_G->env.request.headers, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+       }
+}
+
+PHP_HTTP_API char *php_http_env_get_request_header(const char *name_str, size_t name_len TSRMLS_DC)
+{
+       zval **zvalue;
+       char *val = NULL, *key = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1);
+
+       php_http_env_get_request_headers(NULL TSRMLS_CC);
+
+       if (SUCCESS == zend_hash_find(PHP_HTTP_G->env.request.headers, key, name_len + 1, (void *) &zvalue)) {
+               zval *zcopy = php_http_zsep(IS_STRING, *zvalue);
+
+               val = estrndup(Z_STRVAL_P(zcopy), Z_STRLEN_P(zcopy));
+               zval_ptr_dtor(&zcopy);
+       }
+
+       efree(key);
+
+       return val;
+}
+
+PHP_HTTP_API int php_http_env_got_request_header(const char *name_str, size_t name_len TSRMLS_DC)
+{
+       char *key = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1);
+       int got;
+
+       php_http_env_get_request_headers(NULL TSRMLS_CC);
+       got = zend_hash_exists(PHP_HTTP_G->env.request.headers, key, name_len + 1);
+       efree(key);
+
+       return got;
+}
+
+PHP_HTTP_API zval *php_http_env_get_server_var(const char *key, size_t key_len, zend_bool check TSRMLS_DC)
+{
+       zval **hsv, **var;
+       char *env;
+
+       /* if available, this is a lot faster than accessing $_SERVER */
+       if (sapi_module.getenv) {
+               if ((!(env = sapi_module.getenv((char *) key, key_len TSRMLS_CC))) || (check && !*env)) {
+                       return NULL;
+               }
+               if (PHP_HTTP_G->env.server_var) {
+                       zval_ptr_dtor(&PHP_HTTP_G->env.server_var);
+               }
+               MAKE_STD_ZVAL(PHP_HTTP_G->env.server_var);
+               ZVAL_STRING(PHP_HTTP_G->env.server_var, env, 1);
+               return PHP_HTTP_G->env.server_var;
+       }
+
+       zend_is_auto_global(ZEND_STRL("_SERVER") TSRMLS_CC);
+
+       if ((SUCCESS != zend_hash_find(&EG(symbol_table), ZEND_STRS("_SERVER"), (void *) &hsv)) || (Z_TYPE_PP(hsv) != IS_ARRAY)) {
+               return NULL;
+       }
+       if ((SUCCESS != zend_hash_find(Z_ARRVAL_PP(hsv), key, key_len + 1, (void *) &var))) {
+               return NULL;
+       }
+       if (check && !((Z_TYPE_PP(var) == IS_STRING) && Z_STRVAL_PP(var) && Z_STRLEN_PP(var))) {
+               return NULL;
+       }
+       return *var;
+}
+
+PHP_HTTP_API php_http_message_body_t *php_http_env_get_request_body(TSRMLS_D)
+{
+       if (!PHP_HTTP_G->env.request.body) {
+               php_stream *s = NULL;
+
+               if (SG(request_info).post_data || SG(request_info).raw_post_data) {
+                       if ((s = php_stream_temp_new())) {
+                               /* php://input does not support seek() */
+                               if (SG(request_info).raw_post_data) {
+                                       php_stream_write(s, SG(request_info).raw_post_data, SG(request_info).raw_post_data_length);
+                               } else {
+                                       php_stream_write(s, SG(request_info).post_data, SG(request_info).post_data_length);
+                               }
+                               php_stream_rewind(s);
+                       }
+               } else if (sapi_module.read_post) {
+                       if ((s = php_stream_temp_new())) {
+                               char *buf = emalloc(4096);
+                               int len;
+
+                               while (0 < (len = sapi_module.read_post(buf, 4096 TSRMLS_CC))) {
+                                       php_stream_write(s, buf, len);
+
+                                       if (len < 4096) {
+                                               break;
+                                       }
+                               }
+                               efree(buf);
+
+                               php_stream_rewind(s);
+                       }
+               }
+               PHP_HTTP_G->env.request.body = php_http_message_body_init(NULL, s TSRMLS_CC);
+       }
+
+       return PHP_HTTP_G->env.request.body;
+}
+
+PHP_HTTP_API php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_t length TSRMLS_DC)
+{
+       zval *zentry;
+       char *range, *rp, c;
+       long begin = -1, end = -1, *ptr;
+
+       if (!(range = php_http_env_get_request_header(ZEND_STRL("Range") TSRMLS_CC))) {
+               return PHP_HTTP_RANGE_NO;
+       }
+       if (strncmp(range, "bytes=", lenof("bytes="))) {
+               STR_FREE(range);
+               return PHP_HTTP_RANGE_NO;
+       }
+
+       rp  = range + lenof("bytes=");
+       ptr = &begin;
+
+       do {
+               switch (c = *(rp++)) {
+                       case '0':
+                               /* allow 000... - shall we? */
+                               if (*ptr != -10) {
+                                       *ptr *= 10;
+                               }
+                               break;
+
+                       case '1': case '2': case '3':
+                       case '4': case '5': case '6':
+                       case '7': case '8': case '9':
+                               /*
+                                * If the value of the pointer is already set (non-negative)
+                                * then multiply its value by ten and add the current value,
+                                * else initialise the pointers value with the current value
+                                * --
+                                * This let us recognize empty fields when validating the
+                                * ranges, i.e. a "-10" for begin and "12345" for the end
+                                * was the following range request: "Range: bytes=0-12345";
+                                * While a "-1" for begin and "12345" for the end would
+                                * have been: "Range: bytes=-12345".
+                                */
+                               if (*ptr > 0) {
+                                       *ptr *= 10;
+                                       *ptr += c - '0';
+                               } else {
+                                       *ptr = c - '0';
+                               }
+                               break;
+
+                       case '-':
+                               ptr = &end;
+                               break;
+
+                       case ' ':
+                               break;
+
+                       case 0:
+                       case ',':
+
+                               if (length) {
+                                       /* validate ranges */
+                                       switch (begin) {
+                                               /* "0-12345" */
+                                               case -10:
+                                                       switch (end) {
+                                                               /* "0-" */
+                                                               case -1:
+                                                                       STR_FREE(range);
+                                                                       return PHP_HTTP_RANGE_NO;
+
+                                                               /* "0-0" */
+                                                               case -10:
+                                                                       end = 0;
+                                                                       break;
+
+                                                               default:
+                                                                       if (length <= (size_t) end) {
+                                                                               end = length - 1;
+                                                                       }
+                                                                       break;
+                                                       }
+                                                       begin = 0;
+                                                       break;
+
+                                               /* "-12345" */
+                                               case -1:
+                                                       /* "-", "-0" */
+                                                       if (end == -1 || end == -10) {
+                                                               STR_FREE(range);
+                                                               return PHP_HTTP_RANGE_ERR;
+                                                       }
+                                                       begin = length - end;
+                                                       end = length - 1;
+                                                       break;
+
+                                               /* "12345-(NNN)" */
+                                               default:
+                                                       if (length <= (size_t) begin) {
+                                                               STR_FREE(range);
+                                                               return PHP_HTTP_RANGE_ERR;
+                                                       }
+                                                       switch (end) {
+                                                               /* "12345-0" */
+                                                               case -10:
+                                                                       STR_FREE(range);
+                                                                       return PHP_HTTP_RANGE_ERR;
+
+                                                               /* "12345-" */
+                                                               case -1:
+                                                                       end = length - 1;
+                                                                       break;
+
+                                                               /* "12345-67890" */
+                                                               default:
+                                                                       if (length <= (size_t) end) {
+                                                                               end = length - 1;
+                                                                       } else if (end <  begin) {
+                                                                               STR_FREE(range);
+                                                                               return PHP_HTTP_RANGE_ERR;
+                                                                       }
+                                                                       break;
+                                                       }
+                                                       break;
+                                       }
+                               }
+
+                               MAKE_STD_ZVAL(zentry);
+                               array_init(zentry);
+                               add_index_long(zentry, 0, begin);
+                               add_index_long(zentry, 1, end);
+                               zend_hash_next_index_insert(ranges, &zentry, sizeof(zval *), NULL);
+
+                               begin = -1;
+                               end = -1;
+                               ptr = &begin;
+
+                               break;
+
+                       default:
+                               STR_FREE(range);
+                               return PHP_HTTP_RANGE_NO;
+               }
+       } while (c != 0);
+
+       STR_FREE(range);
+       return PHP_HTTP_RANGE_OK;
+}
+
+static void grab_headers(void *data, void *arg TSRMLS_DC)
+{
+       php_http_buffer_appendl(PHP_HTTP_BUFFER(arg), ((sapi_header_struct *)data)->header);
+       php_http_buffer_appends(PHP_HTTP_BUFFER(arg), PHP_HTTP_CRLF);
+}
+
+PHP_HTTP_API STATUS php_http_env_get_response_headers(HashTable *headers_ht TSRMLS_DC)
+{
+       STATUS status;
+       php_http_buffer headers;
+
+       php_http_buffer_init(&headers);
+       zend_llist_apply_with_argument(&SG(sapi_headers).headers, grab_headers, &headers TSRMLS_CC);
+       php_http_buffer_fix(&headers);
+
+       status = php_http_headers_parse(PHP_HTTP_BUFFER_VAL(&headers), PHP_HTTP_BUFFER_LEN(&headers), headers_ht, NULL, NULL TSRMLS_CC);
+       php_http_buffer_dtor(&headers);
+
+       return status;
+}
+
+PHP_HTTP_API char *php_http_env_get_response_header(const char *name_str, size_t name_len TSRMLS_DC)
+{
+       char *val = NULL;
+       HashTable headers;
+
+       zend_hash_init(&headers, 0, NULL, NULL, 0);
+       if (SUCCESS == php_http_env_get_response_headers(&headers TSRMLS_CC)) {
+               zval **zvalue;
+               char *key = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1);
+
+               if (SUCCESS == zend_hash_find(&headers, key, name_len + 1, (void *) &zvalue)) {
+                       zval *zcopy = php_http_zsep(IS_STRING, *zvalue);
+
+                       val = estrndup(Z_STRVAL_P(zcopy), Z_STRLEN_P(zcopy));
+                       zval_ptr_dtor(&zcopy);
+               }
+
+               efree(key);
+       }
+       zend_hash_destroy(&headers);
+
+       return val;
+}
+
+PHP_HTTP_API long php_http_env_get_response_code(TSRMLS_D)
+{
+       long code = SG(sapi_headers).http_response_code;
+       return code ? code : 200;
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_code(long http_code TSRMLS_DC)
+{
+       return sapi_header_op(SAPI_HEADER_SET_STATUS, (void *) http_code TSRMLS_CC);
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_status_line(long code, php_http_version_t *v TSRMLS_DC)
+{
+       sapi_header_line h = {0};
+       STATUS ret;
+
+       h.line_len = spprintf(&h.line, 0, "HTTP/%u.%u %ld %s", v->major, v->minor, code, php_http_env_get_response_status_for_code(code));
+       ret = sapi_header_op(SAPI_HEADER_REPLACE, (void *) &h TSRMLS_CC);
+       efree(h.line);
+
+       return ret;
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_protocol_version(php_http_version_t *v TSRMLS_DC)
+{
+       return php_http_env_set_response_status_line(php_http_env_get_response_code(TSRMLS_C), v TSRMLS_CC);
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_header(long http_code, const char *header_str, size_t header_len, zend_bool replace TSRMLS_DC)
+{
+       sapi_header_line h = {estrndup(header_str, header_len), header_len, http_code};
+       STATUS ret = sapi_header_op(replace ? SAPI_HEADER_REPLACE : SAPI_HEADER_ADD, (void *) &h TSRMLS_CC);
+       efree(h.line);
+       return ret;
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_header_value(long http_code, const char *name_str, size_t name_len, zval *value, zend_bool replace TSRMLS_DC)
+{
+       if (!value) {
+               sapi_header_line h = {(char *) name_str, name_len, http_code};
+
+               return sapi_header_op(SAPI_HEADER_DELETE, (void *) &h TSRMLS_CC);
+       }
+
+       if(Z_TYPE_P(value) == IS_ARRAY || Z_TYPE_P(value) == IS_OBJECT) {
+               HashPosition pos;
+               int first = replace;
+               zval **data_ptr;
+
+               FOREACH_HASH_VAL(pos, HASH_OF(value), data_ptr) {
+                       if (SUCCESS != php_http_env_set_response_header_value(http_code, name_str, name_len, *data_ptr, first TSRMLS_CC)) {
+                               return FAILURE;
+                       }
+                       first = 0;
+               }
+
+               return SUCCESS;
+       } else {
+               zval *data = php_http_zsep(IS_STRING, value);
+
+               if (!Z_STRLEN_P(data)) {
+                       zval_ptr_dtor(&data);
+                       return php_http_env_set_response_header_value(http_code, name_str, name_len, NULL, replace TSRMLS_CC);
+               } else {
+                       sapi_header_line h;
+                       STATUS ret;
+
+                       if (name_len > INT_MAX) {
+                               name_len = INT_MAX;
+                       }
+                       h.response_code = http_code;
+                       h.line_len = spprintf(&h.line, 0, "%.*s: %.*s", (int) name_len, name_str, Z_STRLEN_P(data), Z_STRVAL_P(data));
+
+                       ret = sapi_header_op(replace ? SAPI_HEADER_REPLACE : SAPI_HEADER_ADD, (void *) &h TSRMLS_CC);
+
+                       zval_ptr_dtor(&data);
+                       STR_FREE(h.line);
+
+                       return ret;
+               }
+       }
+}
+
+PHP_HTTP_API void php_http_env_set_response_throttle_rate(zval *container, size_t chunk_size, double delay TSRMLS_CC)
+{
+       if (Z_TYPE_P(container) == IS_OBJECT) {
+               zend_update_property_double(Z_OBJCE_P(container), container, ZEND_STRL("throttleDelay"), delay TSRMLS_CC);
+               zend_update_property_long(Z_OBJCE_P(container), container, ZEND_STRL("throttleChunk"), chunk_size TSRMLS_CC);
+       } else {
+               convert_to_array(container);
+               add_assoc_double_ex(container, ZEND_STRS("throttleDelay"), delay);
+               add_assoc_long_ex(container, ZEND_STRS("throttleChunk"), chunk_size);
+       }
+}
+
+static void set_container_value(zval *container, const char *name_str, size_t name_len, int type, const void *value_ptr, size_t value_len TSRMLS_DC)
+{
+       if (Z_TYPE_P(container) == IS_OBJECT) {
+               /* stupid non-const api */
+               char *name = estrndup(name_str, name_len);
+               switch (type) {
+                       case IS_LONG:
+                               zend_update_property_long(Z_OBJCE_P(container), container, name, name_len, *(long *)value_ptr TSRMLS_CC);
+                               break;
+                       case IS_STRING:
+                               zend_update_property_stringl(Z_OBJCE_P(container), container, name, name_len, value_ptr, value_len TSRMLS_CC);
+                               break;
+               }
+               efree(name);
+       } else {
+               convert_to_array(container);
+               switch (type) {
+                       case IS_LONG:
+                               add_assoc_long_ex(container, name_str, name_len + 1, *(long *)value_ptr);
+                               break;
+                       case IS_STRING: {
+                               char *value = estrndup(value_ptr, value_len);
+                               add_assoc_stringl_ex(container, name_str, name_len + 1, value, value_len, 0);
+                               break;
+                       }
+               }
+       }
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_last_modified(zval *container, time_t t, char **sent_header TSRMLS_DC)
+{
+       STATUS ret;
+       char *lm_header_str, *date;
+       size_t lm_header_len;
+
+       if (t) {
+               if (!(date = php_format_date(ZEND_STRL(PHP_HTTP_DATE_FORMAT), t, 0 TSRMLS_CC))) {
+                       return FAILURE;
+               }
+
+               lm_header_len = spprintf(&lm_header_str, 0, "Last-Modified: %s", date);
+               STR_FREE(date);
+       } else {
+               lm_header_str = "Last-Modified:";
+               lm_header_len = lenof("Last-Modified:");
+       }
+
+       if (SUCCESS == (ret = php_http_env_set_response_header(0, lm_header_str, lm_header_len, 1 TSRMLS_CC))) {
+               set_container_value(container, ZEND_STRL("lastModified"), IS_LONG, &t, 0 TSRMLS_CC);
+       }
+
+       if (sent_header) {
+               *sent_header = lm_header_str;
+       } else if (t) {
+               STR_FREE(lm_header_str);
+       }
+
+       return ret;
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_etag(zval *container, const char *etag_str, size_t etag_len, char **sent_header TSRMLS_DC)
+{
+       STATUS ret;
+       char *etag = NULL, *etag_header_str;
+       size_t etag_header_len;
+
+       if (etag_len){
+               etag_header_len = spprintf(&etag_header_str, 0, "ETag: \"%s\"", etag_str);
+       } else {
+               etag_header_str = "ETag:";
+               etag_header_len = lenof("ETag:");
+       }
+
+       if (SUCCESS == (ret = php_http_env_set_response_header(0, etag_header_str, etag_header_len, 1 TSRMLS_CC))) {
+               set_container_value(container, ZEND_STRL(etag), IS_STRING, etag_str, etag_len TSRMLS_CC);
+       }
+
+       if (sent_header) {
+               *sent_header = etag_header_str;
+       } else if (etag_len) {
+               STR_FREE(etag_header_str);
+       }
+
+       return ret;
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_content_type(zval *container, const char *ct_str, size_t ct_len, char **sent_header TSRMLS_DC)
+{
+       STATUS ret;
+       char *ct_header_str;
+       size_t ct_header_len;
+
+       if (ct_len) {
+               PHP_HTTP_CHECK_CONTENT_TYPE(ct_str, return FAILURE);
+               ct_header_len = spprintf(&ct_header_str, 0, "Content-Type: %s", ct_str);
+       } else {
+               ct_header_str = "Content-Type:";
+               ct_header_len = lenof("Content-Type:");
+       }
+
+       if (SUCCESS == (ret = php_http_env_set_response_header(0, ct_header_str, ct_header_len, 1 TSRMLS_CC))) {
+               set_container_value(container, ZEND_STRL("contentType"), IS_STRING, ct_str, ct_len TSRMLS_CC);
+       }
+
+       if (sent_header) {
+               *sent_header = ct_header_str;
+       } else if (ct_len) {
+               STR_FREE(ct_header_str);
+       }
+
+       return ret;
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_content_disposition(zval *container, php_http_content_disposition_t d, const char *f_str, size_t f_len, char **sent_header TSRMLS_DC)
+{
+       STATUS ret;
+       char *tmp, *cd_header_str, *new_f_str;
+       int new_f_len;
+       size_t cd_header_len;
+
+       switch (d) {
+               case PHP_HTTP_CONTENT_DISPOSITION_NONE:
+                       break;
+               case PHP_HTTP_CONTENT_DISPOSITION_INLINE:
+                       tmp = "inline";
+                       break;
+               case PHP_HTTP_CONTENT_DISPOSITION_ATTACHMENT:
+                       tmp = "attachment";
+                       break;
+               default:
+                       php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Unknown content disposition (%d)", (int) d);
+                       return FAILURE;
+       }
+
+       if (f_len) {
+               new_f_str = php_addslashes(estrndup(f_str, f_len), f_len, &new_f_len, 0 TSRMLS_CC);
+               cd_header_len = spprintf(&cd_header_str, 0, "Content-Disposition: %s; filename=\"%.*s\"", tmp, new_f_len, new_f_str);
+               STR_FREE(new_f_str);
+       } else if (d) {
+               cd_header_len = spprintf(&cd_header_str, 0, "Content-Disposition: %s", tmp);
+       } else {
+               cd_header_str = "Content-Disposition:";
+               cd_header_len = lenof("Content-Disposition:");
+       }
+       
+       ret = php_http_env_set_response_header(0, cd_header_str, cd_header_len, 1 TSRMLS_CC);
+
+       if (sent_header) {
+               *sent_header = cd_header_str;
+       } else if (f_len || d){
+               STR_FREE(cd_header_str);
+       }
+
+       return ret;
+}
+
+PHP_HTTP_API STATUS php_http_env_set_response_cache_control(zval *container, const char *cc_str, size_t cc_len, char **sent_header TSRMLS_DC)
+{
+       STATUS ret;
+       char *cc_header_str;
+       size_t cc_header_len;
+
+       if (cc_len) {
+               cc_header_len = spprintf(&cc_header_str, 0, "Cache-Control: %s", cc_str);
+       } else {
+               cc_header_str = "Content-Disposition:";
+               cc_header_len = lenof("Content-Disposition:");
+       }
+
+       ret = php_http_env_set_response_header(0, cc_header_str, cc_header_len, 1 TSRMLS_CC);
+
+       if (sent_header) {
+               *sent_header = cc_header_str;
+       } else if (cc_len) {
+               STR_FREE(cc_header_str);
+       }
+
+       return ret;
+}
+
+static zval *get_container_value(zval *container, const char *name_str, size_t name_len TSRMLS_CC)
+{
+       zval *val, **valptr;
+
+       if (Z_TYPE_P(container) == IS_OBJECT) {
+               char *name = estrndup(name_str, name_len);
+               val = zend_read_property(Z_OBJCE_P(container), container, name, name_len, 0 TSRMLS_CC);
+               efree(name);
+       } else {
+               if (SUCCESS == zend_hash_find(Z_ARRVAL_P(container), name_str, name_len + 1, (void *) &valptr)) {
+                       val = *valptr;
+               } else {
+                       val = NULL;
+               }
+       }
+       if (val) {
+               Z_ADDREF_P(val);
+       }
+       return val;
+}
+
+PHP_HTTP_API php_http_cache_status_t php_http_env_is_response_cached_by_etag(zval *container, const char *header_str, size_t header_len TSRMLS_DC)
+{
+       int ret, free_etag = 0;
+       char *header, *etag;
+       zval *zetag, *zbody = NULL;
+
+       if (    !(header = php_http_env_get_request_header(header_str, header_len TSRMLS_CC))
+       ||              !(zbody = get_container_value(container, ZEND_STRL("body") TSRMLS_CC))
+       ||              !(Z_TYPE_P(zbody) == IS_OBJECT)
+       ||              !instanceof_function(Z_OBJCE_P(zbody), php_http_message_body_class_entry TSRMLS_CC)
+       ) {
+               STR_FREE(header);
+               if (zbody) {
+                       zval_ptr_dtor(&zbody);
+               }
+               return PHP_HTTP_CACHE_NO;
+       }
+
+       if ((zetag = get_container_value(container, ZEND_STRL("etag") TSRMLS_CC))) {
+               zval *zetag_copy = php_http_zsep(IS_STRING, zetag);
+               zval_ptr_dtor(&zetag);
+               zetag = zetag_copy;
+       }
+
+       if (zetag && Z_STRLEN_P(zetag)) {
+               etag = Z_STRVAL_P(zetag);
+       } else {
+               etag = php_http_message_body_etag(((php_http_message_body_object_t *) zend_object_store_get_object(zbody TSRMLS_CC))->body);
+               php_http_env_set_response_etag(container, etag, strlen(etag), NULL TSRMLS_CC);
+               free_etag = 1;
+       }
+
+       if (zetag) {
+               zval_ptr_dtor(&zetag);
+       }
+
+       ret = php_http_match(header, etag, PHP_HTTP_MATCH_WORD);
+
+       if (free_etag) {
+               efree(etag);
+       }
+       efree(header);
+
+       return ret ? PHP_HTTP_CACHE_HIT : PHP_HTTP_CACHE_MISS;
+}
+
+PHP_HTTP_API php_http_cache_status_t php_http_env_is_response_cached_by_last_modified(zval *container, const char *header_str, size_t header_len TSRMLS_DC)
+{
+       char *header;
+       time_t ums, lm = 0;
+       zval *zbody = NULL, *zlm;
+
+       if (    !(header = php_http_env_get_request_header(header_str, header_len TSRMLS_CC))
+       ||              !(zbody = get_container_value(container, ZEND_STRL("body") TSRMLS_CC))
+       ||              !(Z_TYPE_P(zbody) == IS_OBJECT)
+       ||              !instanceof_function(Z_OBJCE_P(zbody), php_http_message_body_class_entry TSRMLS_CC)
+       ) {
+               STR_FREE(header);
+               if (zbody) {
+                       zval_ptr_dtor(&zbody);
+               }
+               return PHP_HTTP_CACHE_NO;
+       }
+
+       if ((zlm = get_container_value(container, ZEND_STRL("lastModified") TSRMLS_CC))) {
+               zval *zlm_copy = php_http_zsep(IS_LONG, zlm);
+               zval_ptr_dtor(&zlm);
+               zlm = zlm_copy;
+       }
+
+       if (zlm && Z_LVAL_P(zlm) > 0) {
+               lm = Z_LVAL_P(zlm);
+       } else {
+               lm = php_http_message_body_mtime(((php_http_message_body_object_t *) zend_object_store_get_object(zbody TSRMLS_CC))->body);
+               php_http_env_set_response_last_modified(container, lm, NULL TSRMLS_CC);
+       }
+
+       if (zlm) {
+               zval_ptr_dtor(&zlm);
+       }
+
+       ums = php_parse_date(header, NULL TSRMLS_CC);
+       efree(header);
+
+       if (ums > 0 && ums <= lm) {
+               return PHP_HTTP_CACHE_HIT;
+       } else {
+               return PHP_HTTP_CACHE_MISS;
+       }
+}
+
+PHP_HTTP_API void php_http_env_set_response_body(zval *container, php_http_message_body_t *body)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       zend_object_value ov = php_http_message_body_object_new_ex(php_http_message_body_class_entry, php_http_message_body_copy(body, NULL, 0), NULL TSRMLS_CC);
+
+       set_container_value(container, ZEND_STRL("body"), IS_OBJECT, &ov, 0 TSRMLS_CC);
+}
+
+struct output_ctx {
+       php_http_buffer *buf;
+       zval *container;
+};
+
+static size_t output(void *context, const char *buf, size_t len TSRMLS_DC)
+{
+       struct output_ctx *ctx = context;
+
+       if (ctx->buf) {
+               zval *zcs;
+               size_t chunk_size = PHP_HTTP_SENDBUF_SIZE;
+
+               if ((zcs = get_container_value(ctx->container, ZEND_STRL("throttleChunk") TSRMLS_CC))) {
+                       zval *zcs_copy = php_http_zsep(IS_LONG, zcs);
+
+                       zval_ptr_dtor(&zcs);
+                       chunk_size = Z_LVAL_P(zcs_copy);
+                       zval_ptr_dtor(&zcs_copy);
+               }
+               php_http_buffer_chunked_output(&ctx->buf, buf, len, buf ? chunk_size : 0, output, NULL TSRMLS_CC);
+       } else {
+               zval *ztd;
+
+
+               PHPWRITE(buf, len);
+
+               /*      we really only need to flush when throttling is enabled,
+                       because we push the data as fast as possible anyway if not */
+               if ((ztd = get_container_value(ctx->container, ZEND_STRL("throttleDelay") TSRMLS_CC))) {
+                       double delay;
+                       zval *ztd_copy = php_http_zsep(IS_DOUBLE, ztd);
+
+                       zval_ptr_dtor(&ztd);
+                       delay = Z_DVAL_P(ztd_copy);
+                       zval_ptr_dtor(&ztd_copy);
+
+                       if (delay >= PHP_HTTP_DIFFSEC) {
+                               if (php_output_get_level(TSRMLS_C)) {
+                                       php_output_flush_all(TSRMLS_C);
+                               }
+                               if (!(php_output_get_status(TSRMLS_C) & PHP_OUTPUT_IMPLICITFLUSH)) {
+                                       sapi_flush(TSRMLS_C);
+                               }
+                               php_http_sleep(delay);
+                       }
+               }
+       }
+       return len;
+}
+
+PHP_HTTP_API STATUS php_http_env_send_response(zval *container TSRMLS_DC)
+{
+       struct output_ctx ctx = {NULL, container};
+       zval *zbody, *zheader, *zrcode, *zversion;
+       HashTable ranges;
+       php_http_range_status_t range_status;
+       php_http_message_body_t *body;
+       size_t body_size;
+
+       if (    !(zbody = get_container_value(container, ZEND_STRL("body") TSRMLS_CC))
+       ||              !(Z_TYPE_P(zbody) == IS_OBJECT)
+       ||              !instanceof_function(Z_OBJCE_P(zbody), php_http_message_body_class_entry TSRMLS_CC)
+       ) {
+               if (zbody) {
+                       zval_ptr_dtor(&zbody);
+               }
+               return FAILURE;
+       }
+
+       if ((zrcode = get_container_value(container, ZEND_STRL("responseCode") TSRMLS_CC))) {
+               zval *zrcode_copy = php_http_zsep(IS_LONG, zrcode);
+
+               zval_ptr_dtor(&zrcode);
+               if (Z_LVAL_P(zrcode_copy) > 0) {
+                       php_http_env_set_response_code(Z_LVAL_P(zrcode_copy) TSRMLS_CC);
+               }
+               zval_ptr_dtor(&zrcode_copy);
+       }
+
+       if ((zversion = get_container_value(container, ZEND_STRL("httpVersion") TSRMLS_CC))) {
+               php_http_version_t v;
+               zval *zversion_copy = php_http_zsep(IS_STRING, zversion);
+
+               zval_ptr_dtor(&zversion);
+               if (Z_STRLEN_P(zversion_copy) && php_http_version_parse(&v, Z_STRVAL_P(zversion_copy) TSRMLS_CC)) {
+                       php_http_env_set_response_protocol_version(&v TSRMLS_CC);
+                       php_http_version_dtor(&v);
+               }
+               zval_ptr_dtor(&zversion_copy);
+       }
+
+       if ((zheader = get_container_value(container, ZEND_STRL("headers") TSRMLS_CC))) {
+               if (Z_TYPE_P(zheader) == IS_ARRAY) {
+                       zval **val;
+                       HashPosition pos;
+                       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+
+                       FOREACH_KEYVAL(pos, zheader, key, val) {
+                               if (key.type == HASH_KEY_IS_STRING) {
+                                       php_http_env_set_response_header_value(0, key.str, key.len - 1, *val, 1 TSRMLS_CC);
+                               }
+                       }
+               }
+               zval_ptr_dtor(&zheader);
+       }
+
+       body = ((php_http_message_body_object_t *) zend_object_store_get_object(zbody TSRMLS_CC))->body;
+       body_size = php_http_message_body_size(body);
+       php_http_env_set_response_header(0, ZEND_STRL("Accept-Ranges: bytes"), 1 TSRMLS_CC);
+       zend_hash_init(&ranges, 0, NULL, ZVAL_PTR_DTOR, 0);
+       range_status = php_http_env_get_request_ranges(&ranges, body_size TSRMLS_CC);
+
+       switch (range_status) {
+               case PHP_HTTP_RANGE_ERR:
+                       zend_hash_destroy(&ranges);
+                       if (!php_http_env_got_request_header(ZEND_STRL("If-Range") TSRMLS_CC)) {
+                               char *cr_header_str;
+                               size_t cr_header_len;
+
+                               cr_header_len = spprintf(&cr_header_str, 0, "Content-Range: bytes */%zu", body_size);
+                               php_http_env_set_response_header(416, cr_header_str, cr_header_len, 1 TSRMLS_CC);
+                               efree(cr_header_str);
+                               if (zbody) {
+                                       zval_ptr_dtor(&zbody);
+                               }
+                               return SUCCESS;
+                       }
+                       break;
+
+               case PHP_HTTP_RANGE_NO:
+                       /* send full entity */
+                       zend_hash_destroy(&ranges);
+                       break;
+
+               case PHP_HTTP_RANGE_OK:
+                       /* send content-range response */
+                       if (PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_etag(container, ZEND_STRL("If-Range") TSRMLS_CC)
+                       ||      PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_last_modified(container, ZEND_STRL("If-Range") TSRMLS_CC)
+                       ) {
+                               /* send full entity */
+                               zend_hash_destroy(&ranges);
+                               break;
+                       }
+                       if (PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_etag(container, ZEND_STRL("If-Match") TSRMLS_CC)
+                       ||      PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_last_modified(container, ZEND_STRL("If-Unmodified-Since") TSRMLS_CC)
+                       ||      PHP_HTTP_CACHE_MISS == php_http_env_is_response_cached_by_last_modified(container, ZEND_STRL("Unless-Modified-Since") TSRMLS_CC)
+                       ) {
+                               zend_hash_destroy(&ranges);
+                               php_http_env_set_response_code(412 TSRMLS_CC);
+                               if (zbody) {
+                                       zval_ptr_dtor(&zbody);
+                               }
+                               return SUCCESS;
+                       }
+                       if (zend_hash_num_elements(&ranges) == 1) {
+                               /* single range */
+                               zval **range, **begin, **end;
+
+                               if (SUCCESS != zend_hash_index_find(&ranges, 0, (void *) &range)
+                               ||      SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(range), 0, (void *) &begin)
+                               ||      SUCCESS != zend_hash_index_find(Z_ARRVAL_PP(range), 1, (void *) &end)
+                               ) {
+                                       /* this should never happen */
+                                       zend_hash_destroy(&ranges);
+                                       php_http_env_set_response_code(500 TSRMLS_CC);
+                                       if (zbody) {
+                                               zval_ptr_dtor(&zbody);
+                                       }
+                                       return FAILURE;
+                               } else {
+                                       char *cr_header_str;
+                                       size_t cr_header_len;
+
+                                       cr_header_len = spprintf(&cr_header_str, 0, "Content-Range: bytes %ld-%ld/%zu", Z_LVAL_PP(begin), Z_LVAL_PP(end), body_size);
+                                       php_http_env_set_response_header(206, cr_header_str, cr_header_len, 1 TSRMLS_CC);
+                                       efree(cr_header_str);
+
+                                       /* send chunk */
+                                       php_http_message_body_to_callback(body, output, &ctx, Z_LVAL_PP(begin), Z_LVAL_PP(end) - Z_LVAL_PP(begin) + 1);
+                                       output(&ctx, NULL, 0 TSRMLS_CC);
+                                       if (zbody) {
+                                               zval_ptr_dtor(&zbody);
+                                       }
+                                       return SUCCESS;
+                               }
+                       } else {
+                               /* send multipart/byte-ranges message */
+                               HashPosition pos;
+                               zval **chunk, *zct;
+                               php_http_buffer preface;
+                               int free_ct = 0;
+                               char *content_type = "application/octet-stream";
+                               char boundary[32], *ct_header_str = "Content-Type: multipart/byteranges; boundary=                                ";
+
+                               if ((zct = get_container_value(container, ZEND_STRL("contentType") TSRMLS_CC))) {
+                                       zval *zct_copy = php_http_zsep(IS_STRING, zct);
+
+                                       zval_ptr_dtor(&zct);
+                                       if (Z_STRLEN_P(zct_copy)) {
+                                               content_type = estrndup(Z_STRVAL_P(zct_copy), Z_STRLEN_P(zct_copy));
+                                               free_ct = 1;
+                                       }
+
+                                       zval_ptr_dtor(&zct);
+                               }
+
+                               php_http_boundary(boundary, sizeof(boundary));
+                               strlcpy(&ct_header_str[45], boundary, 32);
+
+                               php_http_env_set_response_header(206, ct_header_str, strlen(ct_header_str), 1 TSRMLS_CC);
+
+                               php_http_buffer_init(&preface);
+                               FOREACH_HASH_VAL(pos, &ranges, chunk) {
+                                       zval **begin, **end;
+
+                                       if (IS_ARRAY == Z_TYPE_PP(chunk)
+                                       &&      SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(chunk), 0, (void *) &begin)
+                                       &&      IS_LONG == Z_TYPE_PP(begin)
+                                       &&      SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(chunk), 1, (void *) &end)
+                                       &&      IS_LONG == Z_TYPE_PP(end)
+                                       ) {
+                                               php_http_buffer_appendf(&preface,
+                                                               PHP_HTTP_CRLF
+                                                               "--%s" PHP_HTTP_CRLF
+                                                               "Content-Type: %s" PHP_HTTP_CRLF
+                                                               "Content-Range: bytes %ld-%ld/%zu" PHP_HTTP_CRLF,
+                                                               /* - */
+                                                               boundary,
+                                                               content_type,
+                                                               Z_LVAL_PP(begin),
+                                                               Z_LVAL_PP(end),
+                                                               body_size
+                                               );
+                                               php_http_buffer_fix(&preface);
+                                               output(&ctx, PHP_HTTP_BUFFER_VAL(&preface), PHP_HTTP_BUFFER_LEN(&preface) TSRMLS_CC);
+                                               php_http_buffer_reset(&preface);
+
+                                               php_http_message_body_to_callback(body, output, &ctx, Z_LVAL_PP(begin), Z_LVAL_PP(end) - Z_LVAL_PP(begin) + 1);
+                                       }
+                               }
+                               php_http_buffer_appendf(&preface, PHP_HTTP_CRLF "--%s--", boundary);
+                               php_http_buffer_fix(&preface);
+                               output(&ctx, PHP_HTTP_BUFFER_VAL(&preface), PHP_HTTP_BUFFER_LEN(&preface) TSRMLS_CC);
+                               php_http_buffer_dtor(&preface);
+                               output(&ctx, NULL, 0 TSRMLS_CC);
+                               if (zbody) {
+                                       zval_ptr_dtor(&zbody);
+                               }
+                               return SUCCESS;
+                       }
+                       break;
+       }
+
+       switch (php_http_env_is_response_cached_by_etag(container, ZEND_STRL("If-None-Match"))) {
+               case PHP_HTTP_CACHE_MISS:
+                       break;
+
+               case PHP_HTTP_CACHE_NO:
+                       if (PHP_HTTP_CACHE_HIT != php_http_env_is_response_cached_by_last_modified(container, ZEND_STRL("If-Modified-Since"))) {
+                               break;
+                       }
+
+               case PHP_HTTP_CACHE_HIT:
+                       php_http_env_set_response_code(304 TSRMLS_CC);
+                       if (zbody) {
+                               zval_ptr_dtor(&zbody);
+                       }
+                       return SUCCESS;
+       }
+
+       php_http_message_body_to_callback(body, output, &ctx, 0, 0);
+       output(&ctx, NULL, 0 TSRMLS_CC);
+
+       if (zbody) {
+               zval_ptr_dtor(&zbody);
+       }
+       return SUCCESS;
+}
+
+static PHP_HTTP_STRLIST(php_http_env_response_status) =
+       PHP_HTTP_STRLIST_ITEM("Continue")
+       PHP_HTTP_STRLIST_ITEM("Switching Protocols")
+       PHP_HTTP_STRLIST_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_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_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_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_STOP
+;
+
+PHP_HTTP_API const char *php_http_env_get_response_status_for_code(unsigned code)
+{
+       return php_http_strlist_find(php_http_env_response_status, 100, code);
+}
+
+zend_class_entry *php_http_env_class_entry;
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpEnv, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpEnv, method, 0)
+#define PHP_HTTP_ENV_ME(method)                                        PHP_ME(HttpEnv, method, PHP_HTTP_ARGS(HttpEnv, method), ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+
+PHP_HTTP_BEGIN_ARGS(getRequestHeader, 0)
+       PHP_HTTP_ARG_VAL(header_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(getRequestBody, 0)
+       PHP_HTTP_ARG_VAL(body_class_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(getResponseStatusForCode, 1)
+       PHP_HTTP_ARG_VAL(code, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(getResponseHeader, 0)
+       PHP_HTTP_ARG_VAL(header_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getResponseCode);
+
+PHP_HTTP_BEGIN_ARGS(setResponseHeader, 1)
+       PHP_HTTP_ARG_VAL(header_name, 0)
+       PHP_HTTP_ARG_VAL(header_value, 0)
+       PHP_HTTP_ARG_VAL(response_code, 0)
+       PHP_HTTP_ARG_VAL(replace_header, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setResponseCode, 1)
+       PHP_HTTP_ARG_VAL(code, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(negotiateLanguage, 0)
+       PHP_HTTP_ARG_VAL(supported, 0)
+       PHP_HTTP_ARG_VAL(result_array, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(negotiateContentType, 0)
+       PHP_HTTP_ARG_VAL(supported, 0)
+       PHP_HTTP_ARG_VAL(result_array, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(negotiateCharset, 0)
+       PHP_HTTP_ARG_VAL(supported, 0)
+       PHP_HTTP_ARG_VAL(result_array, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(negotiate, 0)
+       PHP_HTTP_ARG_VAL(value, 0)
+       PHP_HTTP_ARG_VAL(supported, 0)
+       PHP_HTTP_ARG_VAL(result_array, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(persistentHandlesStat);
+
+PHP_HTTP_BEGIN_ARGS(persistentHandlesClean, 0)
+       PHP_HTTP_ARG_VAL(name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(persistentHandlesIdent, 0)
+       PHP_HTTP_ARG_VAL(name, 0)
+PHP_HTTP_END_ARGS;
+
+zend_function_entry php_http_env_method_entry[] = {
+       PHP_HTTP_ENV_ME(getRequestHeader)
+       PHP_HTTP_ENV_ME(getRequestBody)
+
+       PHP_HTTP_ENV_ME(getResponseStatusForCode)
+
+       PHP_HTTP_ENV_ME(getResponseHeader)
+       PHP_HTTP_ENV_ME(getResponseCode)
+       PHP_HTTP_ENV_ME(setResponseHeader)
+       PHP_HTTP_ENV_ME(setResponseCode)
+
+       PHP_HTTP_ENV_ME(negotiateLanguage)
+       PHP_HTTP_ENV_ME(negotiateContentType)
+       PHP_HTTP_ENV_ME(negotiateCharset)
+       PHP_HTTP_ENV_ME(negotiate)
+
+       PHP_HTTP_ENV_ME(persistentHandlesStat)
+       PHP_HTTP_ENV_ME(persistentHandlesClean)
+       PHP_HTTP_ENV_ME(persistentHandlesIdent)
+
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_METHOD(HttpEnv, getRequestHeader)
+{
+       char *header_name_str;
+       int header_name_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &header_name_str, &header_name_len)) {
+               if (header_name_str && header_name_len) {
+                       char *header_value = php_http_env_get_request_header(header_name_str, header_name_len TSRMLS_CC);
+
+                       if (header_value) {
+                               RETURN_STRING(header_value, 0);
+                       }
+                       RETURN_NULL();
+               } else {
+                       array_init(return_value);
+                       php_http_env_get_request_headers(Z_ARRVAL_P(return_value) TSRMLS_CC);
+                       return;
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, getRequestBody)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               zend_class_entry *class_entry = php_http_message_body_class_entry;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|C", &class_entry)) {
+                       zend_object_value ov;
+                       php_http_message_body_t *body = php_http_env_get_request_body(TSRMLS_C);
+
+                       if (SUCCESS == php_http_new(&ov, class_entry, (php_http_new_t) php_http_message_body_object_new_ex, php_http_message_body_class_entry, body, NULL TSRMLS_CC)) {
+                               RETURN_OBJVAL(ov, 0);
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpEnv, getResponseStatusForCode)
+{
+       long code;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &code)) {
+               RETURN_STRING(php_http_env_get_response_status_for_code(code), 1);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, getResponseHeader)
+{
+       char *header_name_str;
+       int header_name_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &header_name_str, &header_name_len)) {
+               if (header_name_str && header_name_len) {
+                       char *header_value = php_http_env_get_response_header(header_name_str, header_name_len TSRMLS_CC);
+
+                       if (header_value) {
+                               RETURN_STRING(header_value, 0);
+                       }
+                       RETURN_NULL();
+               } else {
+                       array_init(return_value);
+                       php_http_env_get_response_headers(Z_ARRVAL_P(return_value) TSRMLS_CC);
+                       return;
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, getResponseCode)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_LONG(php_http_env_get_response_code(TSRMLS_C));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, setResponseHeader)
+{
+       char *header_name_str;
+       int header_name_len;
+       zval *header_value;
+       long code = 0;
+       zend_bool replace_header = 1;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z!lb", &header_name_str, &header_name_len, &header_value, &code, &replace_header)) {
+               RETURN_SUCCESS(php_http_env_set_response_header_value(code, header_name_str, header_name_len, header_value, replace_header TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, setResponseCode)
+{
+       long code;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &code)) {
+               RETURN_SUCCESS(php_http_env_set_response_code(code TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+
+#define PHP_HTTP_DO_NEGOTIATE_DEFAULT(supported) \
+       { \
+               zval **value; \
+                \
+               zend_hash_internal_pointer_reset((supported)); \
+               if (SUCCESS == zend_hash_get_current_data((supported), (void *) &value)) { \
+                       RETVAL_ZVAL(*value, 1, 0); \
+               } else { \
+                       RETVAL_NULL(); \
+               } \
+       }
+
+#define PHP_HTTP_DO_NEGOTIATE_HANDLE_DEFAULT(supported, rs_array) \
+       PHP_HTTP_DO_NEGOTIATE_DEFAULT(supported); \
+       if (rs_array) { \
+               HashPosition pos; \
+               zval **value_ptr; \
+                \
+               FOREACH_HASH_VAL(pos, supported, value_ptr) { \
+                       zval *value = php_http_zsep(IS_STRING, *value_ptr); \
+                       add_assoc_double(rs_array, Z_STRVAL_P(value), 1.0); \
+                       zval_ptr_dtor(&value); \
+               } \
+       }
+
+#define PHP_HTTP_DO_NEGOTIATE_HANDLE_RESULT(result, supported, rs_array) \
+       { \
+               char *key; \
+               uint key_len; \
+               ulong idx; \
+                \
+               if (zend_hash_num_elements(result) && HASH_KEY_IS_STRING == zend_hash_get_current_key_ex(result, &key, &key_len, &idx, 1, NULL)) { \
+                       RETVAL_STRINGL(key, key_len-1, 0); \
+               } else { \
+                       PHP_HTTP_DO_NEGOTIATE_DEFAULT(supported); \
+               } \
+               \
+               if (rs_array) { \
+                       zend_hash_copy(Z_ARRVAL_P(rs_array), result, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); \
+               } \
+               \
+               zend_hash_destroy(result); \
+               FREE_HASHTABLE(result); \
+       }
+
+#define PHP_HTTP_DO_NEGOTIATE(type, supported, rs_array) \
+       { \
+               HashTable *result; \
+               if ((result = php_http_negotiate_ ##type(supported))) { \
+                       PHP_HTTP_DO_NEGOTIATE_HANDLE_RESULT(result, supported, rs_array); \
+               } else { \
+                       PHP_HTTP_DO_NEGOTIATE_HANDLE_DEFAULT(supported, rs_array); \
+               } \
+       }
+
+PHP_METHOD(HttpEnv, negotiateLanguage)
+{
+       HashTable *supported;
+       zval *rs_array = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "H|z", &supported, &rs_array)) {
+               if (rs_array) {
+                       zval_dtor(rs_array);
+                       array_init(rs_array);
+               }
+
+               PHP_HTTP_DO_NEGOTIATE(language, supported, rs_array);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, negotiateCharset)
+{
+       HashTable *supported;
+       zval *rs_array = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "H|z", &supported, &rs_array)) {
+               if (rs_array) {
+                       zval_dtor(rs_array);
+                       array_init(rs_array);
+               }
+               PHP_HTTP_DO_NEGOTIATE(charset, supported, rs_array);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, negotiateContentType)
+{
+       HashTable *supported;
+       zval *rs_array = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "H|z", &supported, &rs_array)) {
+               if (rs_array) {
+                       zval_dtor(rs_array);
+                       array_init(rs_array);
+               }
+               PHP_HTTP_DO_NEGOTIATE(content_type, supported, rs_array);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, negotiate)
+{
+       HashTable *supported;
+       zval *rs_array = NULL;
+       char *value_str;
+       int value_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sH|z", &value_str, &value_len, &supported, &rs_array)) {
+               HashTable *rs;
+
+               if (rs_array) {
+                       zval_dtor(rs_array);
+                       array_init(rs_array);
+               }
+
+               if ((rs = php_http_negotiate(value_str, supported, php_http_negotiate_default_func))) {
+                       PHP_HTTP_DO_NEGOTIATE_HANDLE_RESULT(rs, supported, rs_array);
+               } else {
+                       PHP_HTTP_DO_NEGOTIATE_HANDLE_DEFAULT(supported, rs_array);
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, persistentHandlesStat)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               object_init(return_value);
+               if (php_http_persistent_handle_statall(HASH_OF(return_value))) {
+                       return;
+               }
+               zval_dtor(return_value);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnv, persistentHandlesClean)
+{
+       char *name_str = NULL;
+       int name_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &name_str, &name_len)) {
+               php_http_persistent_handle_cleanup(name_str, name_len, 1 TSRMLS_CC);
+       }
+}
+
+PHP_METHOD(HttpEnv, persistentHandlesIdent)
+{
+       char *ident_str = NULL;
+       int ident_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &ident_str, &ident_len)) {
+               RETVAL_STRING(zend_ini_string(ZEND_STRS("http.persistent.handles.ident"), 0), 1);
+               if (ident_str && ident_len) {
+                       zend_alter_ini_entry(ZEND_STRS("http.persistent.handles.ident"), ident_str, ident_len, ZEND_INI_USER, PHP_INI_STAGE_RUNTIME);
+               }
+               return;
+       }
+       RETURN_FALSE;
+}
+
+zend_class_entry *php_http_env_request_class_entry;
+
+#undef PHP_HTTP_BEGIN_ARGS
+#undef PHP_HTTP_EMPTY_ARGS
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)          PHP_HTTP_BEGIN_ARGS_EX(HttpEnvRequest, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                                    PHP_HTTP_EMPTY_ARGS_EX(HttpEnvRequest, method, 0)
+#define PHP_HTTP_ENV_REQUEST_ME(method, visibility)    PHP_ME(HttpEnvRequest, method, PHP_HTTP_ARGS(HttpEnvRequest, method), visibility)
+
+PHP_HTTP_EMPTY_ARGS(__construct);
+
+zend_function_entry php_http_env_request_method_entry[] = {
+       PHP_HTTP_ENV_REQUEST_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_METHOD(HttpEnvRequest, __construct)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) {
+                               obj->message = php_http_message_init_env(obj->message, PHP_HTTP_REQUEST TSRMLS_CC);
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+
+zend_class_entry *php_http_env_response_class_entry;
+
+#undef PHP_HTTP_BEGIN_ARGS
+#undef PHP_HTTP_EMPTY_ARGS
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)                  PHP_HTTP_BEGIN_ARGS_EX(HttpEnvResponse, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                                            PHP_HTTP_EMPTY_ARGS_EX(HttpEnvResponse, method, 0)
+#define PHP_HTTP_ENV_RESPONSE_ME(method, visibility)   PHP_ME(HttpEnvResponse, method, PHP_HTTP_ARGS(HttpEnvResponse, method), visibility)
+
+PHP_HTTP_EMPTY_ARGS(__construct);
+
+PHP_HTTP_BEGIN_ARGS(setContentType, 1)
+       PHP_HTTP_ARG_VAL(content_type, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setContentDisposition, 1)
+       PHP_HTTP_ARG_VAL(content_disposition, 0)
+       PHP_HTTP_ARG_VAL(filename, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setCacheControl, 1)
+       PHP_HTTP_ARG_VAL(cache_control, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setLastModified, 1)
+       PHP_HTTP_ARG_VAL(last_modified, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(isCachedByLastModified, 0)
+       PHP_HTTP_ARG_VAL(header_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setEtag, 1)
+       PHP_HTTP_ARG_VAL(etag, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(isCachedByEtag, 0)
+       PHP_HTTP_ARG_VAL(header_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setThrottleRate, 1)
+       PHP_HTTP_ARG_VAL(chunk_size, 0)
+       PHP_HTTP_ARG_VAL(delay, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(send);
+
+
+zend_function_entry php_http_env_response_method_entry[] = {
+       PHP_HTTP_ENV_RESPONSE_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+       PHP_HTTP_ENV_RESPONSE_ME(setContentType, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENV_RESPONSE_ME(setContentDisposition, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENV_RESPONSE_ME(setCacheControl, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENV_RESPONSE_ME(setLastModified, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENV_RESPONSE_ME(isCachedByLastModified, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENV_RESPONSE_ME(setEtag, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENV_RESPONSE_ME(isCachedByEtag, ZEND_ACC_PUBLIC)
+       PHP_HTTP_ENV_RESPONSE_ME(setThrottleRate, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_ENV_RESPONSE_ME(send, ZEND_ACC_PUBLIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+
+
+PHP_METHOD(HttpEnvResponse, __construct)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) {
+                               obj->message = php_http_message_init_env(obj->message, PHP_HTTP_RESPONSE TSRMLS_CC);
+                       } end_error_handling();
+               }
+       } end_error_handling();
+
+}
+
+PHP_METHOD(HttpEnvResponse, setContentType)
+{
+       char *ct_str;
+       int ct_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &ct_str, &ct_len)) {
+               RETURN_SUCCESS(php_http_env_set_response_content_type(getThis(), ct_str, ct_len, NULL TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnvResponse, setContentDisposition)
+{
+       long cd;
+       char *file_str = NULL;
+       int file_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|s!", &cd, &file_str, &file_len)) {
+               RETURN_SUCCESS(php_http_env_set_response_content_disposition(getThis(), cd, file_str, file_len, NULL TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnvResponse, setCacheControl)
+{
+       char *cc_str;
+       int cc_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &cc_str, &cc_len)) {
+               RETURN_SUCCESS(php_http_env_set_response_cache_control(getThis(), cc_str, cc_len, NULL TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnvResponse, setLastModified)
+{
+       long last_modified;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &last_modified)) {
+               RETURN_SUCCESS(php_http_env_set_response_last_modified(getThis(), last_modified, NULL TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnvResponse, isCachedByLastModified)
+{
+       char *header_name_str = NULL;
+       int header_name_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s!", &header_name_str, &header_name_len)) {
+               if (!header_name_str || !header_name_len) {
+                       header_name_str = "If-Modified-Since";
+                       header_name_len = lenof("If-Modified-Since");
+               }
+               RETURN_LONG(php_http_env_is_response_cached_by_last_modified(getThis(), header_name_str, header_name_len TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnvResponse, setEtag)
+{
+       char *etag_str;
+       int etag_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s!", &etag_str, &etag_len)) {
+               RETURN_SUCCESS(php_http_env_set_response_etag(getThis(), etag_str, etag_len, NULL TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnvResponse, isCachedByEtag)
+{
+       char *header_name_str = NULL;
+       int header_name_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &header_name_str, &header_name_len)) {
+               if (!header_name_str || !header_name_len) {
+                       header_name_str = "If-None-Match";
+                       header_name_len = lenof("If-None-Match");
+               }
+               RETURN_LONG(php_http_env_is_response_cached_by_etag(getThis(), header_name_str, header_name_len TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnvResponse, setThrottleRate)
+{
+       long chunk_size;
+       double delay = 1;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|d", &chunk_size, &delay)) {
+               php_http_env_set_response_throttle_rate(getThis(), chunk_size, delay TSRMLS_CC);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpEnvResponse, send)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_SUCCESS(php_http_env_send_response(getThis() TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+
+PHP_MINIT_FUNCTION(http_env)
+{
+       PHP_HTTP_REGISTER_CLASS(http, Env, http_env, NULL, 0);
+       PHP_HTTP_REGISTER_CLASS(http\\env, Request, http_env_request, php_http_message_class_entry, 0);
+       PHP_HTTP_REGISTER_CLASS(http\\env, Response, http_env_response, php_http_message_class_entry, 0);
+
+       zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CONTENT_DISPOSITION_INLINE"), PHP_HTTP_CONTENT_DISPOSITION_INLINE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CONTENT_DISPOSITION_ATTACHMENT"), PHP_HTTP_CONTENT_DISPOSITION_ATTACHMENT TSRMLS_CC);
+
+       zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CACHE_NO"), PHP_HTTP_CACHE_NO TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_env_response_class_entry, ZEND_STRL("CACHE_HIT"), PHP_HTTP_CACHE_HIT TSRMLS_CC);
+       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("contentType"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("etag"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("lastModified"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("throttleDelay"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       zend_declare_property_null(php_http_env_response_class_entry, ZEND_STRL("throttleChunk"), ZEND_ACC_PROTECTED TSRMLS_CC);
+
+       return SUCCESS;
+}
+
+
+/*
+ * 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_env.h b/php_http_env.h
new file mode 100644 (file)
index 0000000..0d73aca
--- /dev/null
@@ -0,0 +1,141 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#ifndef PHP_HTTP_ENV_H
+#define PHP_HTTP_ENV_H
+
+#include "php_http_message_body.h"
+#include "php_http_encoding.h"
+#include "php_http_version.h"
+
+struct php_http_env_globals {
+       zval *server_var;
+       char *etag_mode;
+
+       struct {
+               char *content_type;
+               php_http_message_body_t *body;
+               char *etag;
+               time_t last_modified;
+               double throttle_delay;
+               size_t throttle_chunk;
+               php_http_encoding_stream_t *deflate;
+       } response;
+
+       struct {
+               time_t time;
+               HashTable *headers;
+               php_http_message_body_t *body;
+       } request;
+};
+
+typedef enum php_http_range_status {
+       PHP_HTTP_RANGE_NO,
+       PHP_HTTP_RANGE_OK,
+       PHP_HTTP_RANGE_ERR
+} php_http_range_status_t;
+
+PHP_HTTP_API php_http_range_status_t php_http_env_get_request_ranges(HashTable *ranges, size_t entity_length TSRMLS_DC);
+PHP_HTTP_API void php_http_env_get_request_headers(HashTable *headers TSRMLS_DC);
+PHP_HTTP_API char *php_http_env_get_request_header(const char *name_str, size_t name_len TSRMLS_DC);
+PHP_HTTP_API int php_http_env_got_request_header(const char *name_str, size_t name_len TSRMLS_DC);
+PHP_HTTP_API php_http_message_body_t *php_http_env_get_request_body(TSRMLS_D);
+
+typedef enum php_http_content_disposition {
+       PHP_HTTP_CONTENT_DISPOSITION_NONE,
+       PHP_HTTP_CONTENT_DISPOSITION_INLINE,
+       PHP_HTTP_CONTENT_DISPOSITION_ATTACHMENT
+} php_http_content_disposition_t;
+
+typedef enum php_http_cache_status {
+       PHP_HTTP_CACHE_NO,
+       PHP_HTTP_CACHE_HIT,
+       PHP_HTTP_CACHE_MISS
+} php_http_cache_status_t;
+
+PHP_HTTP_API long php_http_env_get_response_code(TSRMLS_D);
+PHP_HTTP_API const char *php_http_env_get_response_status_for_code(unsigned code);
+PHP_HTTP_API STATUS php_http_env_get_response_headers(HashTable *headers_ht TSRMLS_DC);
+PHP_HTTP_API char *php_http_env_get_response_header(const char *name_str, size_t name_len TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_env_set_response_code(long http_code TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_env_set_response_protocol_version(php_http_version_t *v TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_env_set_response_header(long http_code, const char *header_str, size_t header_len, zend_bool replace TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_env_set_response_header_value(long http_code, const char *name_str, size_t name_len, zval *value, zend_bool replace TSRMLS_DC);
+
+PHP_HTTP_API zval *php_http_env_get_server_var(const char *key_str, size_t key_len, zend_bool check TSRMLS_DC);
+#define php_http_env_got_server_var(v) (NULL != php_http_env_get_server_var((v), strlen(v), 1 TSRMLS_CC))
+
+PHP_HTTP_API STATUS php_http_env_set_response_last_modified(zval *container, time_t lm, char **sent_header TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_env_set_response_etag(zval *container, const char *etag_str, size_t etag_len, char **sent_header TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_env_set_response_content_type(zval *container, const char *ct_str, size_t ct_len, char **sent_header TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_env_set_response_content_disposition(zval *container, php_http_content_disposition_t d, const char *f_str, size_t f_len, char **sent_header TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_env_set_response_cache_control(zval *container, const char *cc_str, size_t cc_len, char **sent_header TSRMLS_DC);
+PHP_HTTP_API void php_http_env_set_response_throttle_rate(zval *container, size_t chunk_size, double delay TSRMLS_CC);
+PHP_HTTP_API void php_http_env_set_response_body(zval *container, php_http_message_body_t *body);
+PHP_HTTP_API STATUS php_http_env_send_response(zval *container TSRMLS_DC);
+PHP_HTTP_API php_http_cache_status_t php_http_env_is_response_cached_by_etag(zval *container, const char *header_str, size_t header_len TSRMLS_DC);
+PHP_HTTP_API php_http_cache_status_t php_http_env_is_response_cached_by_last_modified(zval *container, const char *header_str, size_t header_len TSRMLS_DC);
+
+extern zend_class_entry *php_http_env_class_entry;
+extern zend_function_entry php_http_env_method_entry[];
+
+PHP_METHOD(HttpEnv, getRequestHeader);
+PHP_METHOD(HttpEnv, getRequestBody);
+PHP_METHOD(HttpEnv, getResponseStatusForCode);
+PHP_METHOD(HttpEnv, getResponseHeader);
+PHP_METHOD(HttpEnv, getResponseCode);
+PHP_METHOD(HttpEnv, setResponseHeader);
+PHP_METHOD(HttpEnv, setResponseCode);
+PHP_METHOD(HttpEnv, negotiateLanguage);
+PHP_METHOD(HttpEnv, negotiateCharset);
+PHP_METHOD(HttpEnv, negotiateContentType);
+PHP_METHOD(HttpEnv, negotiate);
+PHP_METHOD(HttpEnv, persistentHandlesStat);
+PHP_METHOD(HttpEnv, persistentHandlesClean);
+PHP_METHOD(HttpEnv, persistentHandlesIdent);
+
+extern zend_class_entry *php_http_env_request_class_entry;
+extern zend_function_entry php_http_env_request_method_entry[];
+
+PHP_METHOD(HttpEnvRequest, __construct);
+
+extern zend_class_entry *php_http_env_response_class_entry;
+extern zend_function_entry php_http_env_response_method_entry[];
+
+PHP_METHOD(HttpEnvResponse, __construct);
+PHP_METHOD(HttpEnvResponse, setContentType);
+PHP_METHOD(HttpEnvResponse, setContentDisposition);
+PHP_METHOD(HttpEnvResponse, setCacheControl);
+PHP_METHOD(HttpEnvResponse, setLastModified);
+PHP_METHOD(HttpEnvResponse, isCachedByLastModified);
+PHP_METHOD(HttpEnvResponse, setEtag);
+PHP_METHOD(HttpEnvResponse, isCachedByEtag);
+PHP_METHOD(HttpEnvResponse, setThrottleRate);
+PHP_METHOD(HttpEnvResponse, send);
+
+PHP_MINIT_FUNCTION(http_env);
+PHP_RINIT_FUNCTION(http_env);
+PHP_RSHUTDOWN_FUNCTION(http_env);
+
+#endif
+
+/*
+ * 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_etag.c b/php_http_etag.c
new file mode 100644 (file)
index 0000000..1f11785
--- /dev/null
@@ -0,0 +1,84 @@
+#include "php_http.h"
+
+#include <ext/standard/crc32.h>
+#include <ext/standard/sha1.h>
+#include <ext/standard/md5.h>
+
+PHP_HTTP_API void *php_http_etag_init(TSRMLS_D)
+{
+       void *ctx = NULL;
+       char *mode = PHP_HTTP_G->env.etag_mode;
+
+#ifdef PHP_HTTP_HAVE_HASH
+       const php_hash_ops *eho = NULL;
+
+       if (mode && (eho = php_hash_fetch_ops(mode, strlen(mode)))) {
+               ctx = emalloc(eho->context_size);
+               eho->hash_init(ctx);
+       } else
+#endif
+       if (mode && ((!strcasecmp(mode, "crc32")) || (!strcasecmp(mode, "crc32b")))) {
+               ctx = emalloc(sizeof(uint));
+               *((uint *) ctx) = ~0;
+       } else if (mode && !strcasecmp(mode, "sha1")) {
+               PHP_SHA1Init(ctx = emalloc(sizeof(PHP_SHA1_CTX)));
+       } else {
+               PHP_MD5Init(ctx = emalloc(sizeof(PHP_MD5_CTX)));
+       }
+
+       return ctx;
+}
+
+PHP_HTTP_API char *php_http_etag_finish(void *ctx TSRMLS_DC)
+{
+       unsigned char digest[128] = {0};
+       char *etag = NULL, *mode = PHP_HTTP_G->env.etag_mode;
+
+#ifdef PHP_HTTP_HAVE_HASH
+       const php_hash_ops *eho = NULL;
+
+       if (mode && (eho = php_hash_fetch_ops(mode, strlen(mode)))) {
+               eho->hash_final(digest, ctx);
+               etag = php_http_etag_digest(digest, eho->digest_size);
+       } else
+#endif
+       if (mode && ((!strcasecmp(mode, "crc32")) || (!strcasecmp(mode, "crc32b")))) {
+               *((uint *) ctx) = ~*((uint *) ctx);
+               etag = php_http_etag_digest((const unsigned char *) ctx, sizeof(uint));
+       } else if (mode && (!strcasecmp(mode, "sha1"))) {
+               PHP_SHA1Final(digest, ctx);
+               etag = php_http_etag_digest(digest, 20);
+       } else {
+               PHP_MD5Final(digest, ctx);
+               etag = php_http_etag_digest(digest, 16);
+       }
+       efree(ctx);
+
+       return etag;
+}
+
+PHP_HTTP_API size_t php_http_etag_update(void *ctx, const char *data_ptr, size_t data_len TSRMLS_DC)
+{
+       char *mode = PHP_HTTP_G->env.etag_mode;
+#ifdef PHP_HTTP_HAVE_HASH
+       const php_hash_ops *eho = NULL;
+
+       if (mode && (eho = php_hash_fetch_ops(mode, strlen(mode)))) {
+               eho->hash_update(ctx, (const unsigned char *) data_ptr, data_len);
+       } else
+#endif
+       if (mode && ((!strcasecmp(mode, "crc32")) || (!strcasecmp(mode, "crc32b")))) {
+               uint i, c = *((uint *) ctx);
+               for (i = 0; i < data_len; ++i) {
+                       CRC32(c, data_ptr[i]);
+               }
+               *((uint *)ctx) = c;
+       } else if (mode && (!strcasecmp(mode, "sha1"))) {
+               PHP_SHA1Update(ctx, (const unsigned char *) data_ptr, data_len);
+       } else {
+               PHP_MD5Update(ctx, (const unsigned char *) data_ptr, data_len);
+       }
+
+       return data_len;
+}
+
diff --git a/php_http_etag.h b/php_http_etag.h
new file mode 100644 (file)
index 0000000..25b1426
--- /dev/null
@@ -0,0 +1,24 @@
+#ifndef PHP_HTTP_ETAG_H
+#define PHP_HTTP_ETAG_H
+
+PHP_HTTP_API void *php_http_etag_init(TSRMLS_D);
+PHP_HTTP_API size_t php_http_etag_update(void *ctx, const char *data_ptr, size_t data_len TSRMLS_DC);
+PHP_HTTP_API char *php_http_etag_finish(void *ctx TSRMLS_DC);
+
+static inline char *php_http_etag_digest(const unsigned char *digest, int len)
+{
+       static const char hexdigits[17] = "0123456789abcdef";
+       int i;
+       char *hex = emalloc(len * 2 + 1);
+       char *ptr = hex;
+
+       for (i = 0; i < len; ++i) {
+               *ptr++ = hexdigits[digest[i] >> 4];
+               *ptr++ = hexdigits[digest[i] & 0xF];
+       }
+       *ptr = '\0';
+
+       return hex;
+}
+
+#endif /* PHP_HTTP_ETAG_H */
diff --git a/php_http_exception.c b/php_http_exception.c
new file mode 100644 (file)
index 0000000..9c55263
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_exception_object.c 292841 2009-12-31 08:48:57Z mike $ */
+
+#include "php_http.h"
+
+
+#ifndef PHP_HTTP_DBG_EXCEPTIONS
+#      define PHP_HTTP_DBG_EXCEPTIONS 0
+#endif
+
+zend_class_entry *PHP_HTTP_EX_DEF_CE;
+zend_class_entry *PHP_HTTP_EX_CE(runtime);
+zend_class_entry *PHP_HTTP_EX_CE(header);
+zend_class_entry *PHP_HTTP_EX_CE(malformed_headers);
+zend_class_entry *PHP_HTTP_EX_CE(request_method);
+zend_class_entry *PHP_HTTP_EX_CE(message);
+zend_class_entry *PHP_HTTP_EX_CE(message_type);
+zend_class_entry *PHP_HTTP_EX_CE(invalid_param);
+zend_class_entry *PHP_HTTP_EX_CE(encoding);
+zend_class_entry *PHP_HTTP_EX_CE(request);
+zend_class_entry *PHP_HTTP_EX_CE(request_pool);
+zend_class_entry *PHP_HTTP_EX_CE(socket);
+zend_class_entry *PHP_HTTP_EX_CE(response);
+zend_class_entry *PHP_HTTP_EX_CE(url);
+zend_class_entry *PHP_HTTP_EX_CE(querystring);
+zend_class_entry *PHP_HTTP_EX_CE(cookie);
+
+zend_function_entry php_http_exception_method_entry[] = {
+       EMPTY_FUNCTION_ENTRY
+};
+
+#if PHP_HTTP_DBG_EXCEPTIONS
+static void php_http_exception_hook(zval *ex TSRMLS_DC)
+{
+       if (ex) {
+               zval *m = zend_read_property(Z_OBJCE_P(ex), ex, "message", lenof("message"), 0 TSRMLS_CC);
+               fprintf(stderr, "*** Threw exception '%s'\n", Z_STRVAL_P(m));
+       } else {
+               fprintf(stderr, "*** Threw NULL exception\n");
+       }
+}
+#endif
+
+PHP_MINIT_FUNCTION(http_exception)
+{
+       PHP_HTTP_REGISTER_EXCEPTION(Exception, PHP_HTTP_EX_DEF_CE, zend_exception_get_default(TSRMLS_C));
+       
+       PHP_HTTP_REGISTER_EXCEPTION(RuntimeException, PHP_HTTP_EX_CE(runtime), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(InvalidParamException, PHP_HTTP_EX_CE(invalid_param), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(HeaderException, PHP_HTTP_EX_CE(header), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(MalformedHeadersException, PHP_HTTP_EX_CE(malformed_headers), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(RequestMethodException, PHP_HTTP_EX_CE(request_method), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(MessageException, PHP_HTTP_EX_CE(message), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(MessageTypeException, PHP_HTTP_EX_CE(message_type), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(EncodingException, PHP_HTTP_EX_CE(encoding), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(RequestException, PHP_HTTP_EX_CE(request), PHP_HTTP_EX_DEF_CE);
+
+       zend_declare_property_long(PHP_HTTP_EX_CE(request), "curlCode", lenof("curlCode"), 0, ZEND_ACC_PUBLIC TSRMLS_CC);
+
+       PHP_HTTP_REGISTER_EXCEPTION(RequestPoolException, PHP_HTTP_EX_CE(request_pool), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(SocketException, PHP_HTTP_EX_CE(socket), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(ResponseException, PHP_HTTP_EX_CE(response), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(UrlException, PHP_HTTP_EX_CE(url), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(QueryStringException, PHP_HTTP_EX_CE(querystring), PHP_HTTP_EX_DEF_CE);
+       PHP_HTTP_REGISTER_EXCEPTION(CookieException, PHP_HTTP_EX_CE(cookie), PHP_HTTP_EX_DEF_CE);
+       
+#if PHP_HTTP_DBG_EXCEPTIONS
+       zend_throw_exception_hook = php_http_exception_hook;
+#endif
+       
+       return SUCCESS;
+}
+
+zend_class_entry *php_http_exception_get_default(void)
+{
+       return PHP_HTTP_EX_DEF_CE;
+}
+
+zend_class_entry *php_http_exception_get_for_code(long code)
+{
+       zend_class_entry *ex = PHP_HTTP_EX_DEF_CE;
+
+       switch (code) {
+               case PHP_HTTP_E_RUNTIME:                                        ex = PHP_HTTP_EX_CE(runtime);                                   break;
+               case PHP_HTTP_E_INVALID_PARAM:                          ex = PHP_HTTP_EX_CE(invalid_param);                             break;
+               case PHP_HTTP_E_HEADER:                                         ex = PHP_HTTP_EX_CE(header);                                    break;
+               case PHP_HTTP_E_MALFORMED_HEADERS:                      ex = PHP_HTTP_EX_CE(malformed_headers);                 break;
+               case PHP_HTTP_E_REQUEST_METHOD:                         ex = PHP_HTTP_EX_CE(request_method);                    break;
+               case PHP_HTTP_E_MESSAGE:                                        ex = PHP_HTTP_EX_CE(message);                                   break;
+               case PHP_HTTP_E_MESSAGE_TYPE:                           ex = PHP_HTTP_EX_CE(message_type);                              break;
+               case PHP_HTTP_E_ENCODING:                                       ex = PHP_HTTP_EX_CE(encoding);                                  break;
+               case PHP_HTTP_E_REQUEST:                                        ex = PHP_HTTP_EX_CE(request);                                   break;
+               case PHP_HTTP_E_REQUEST_POOL:                           ex = PHP_HTTP_EX_CE(request_pool);                              break;
+               case PHP_HTTP_E_SOCKET:                                         ex = PHP_HTTP_EX_CE(socket);                                    break;
+               case PHP_HTTP_E_RESPONSE:                                       ex = PHP_HTTP_EX_CE(response);                                  break;
+               case PHP_HTTP_E_URL:                                            ex = PHP_HTTP_EX_CE(url);                                               break;
+               case PHP_HTTP_E_QUERYSTRING:                            ex = PHP_HTTP_EX_CE(querystring);                               break;
+               case PHP_HTTP_E_COOKIE:                                         ex = PHP_HTTP_EX_CE(cookie);                                    break;
+       }
+
+       return ex;
+}
+
+/*
+ * 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_exception.h b/php_http_exception.h
new file mode 100644 (file)
index 0000000..1881190
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_exception_object.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_EXCEPTION_H
+#define PHP_HTTP_EXCEPTION_H
+
+PHP_MINIT_FUNCTION(http_exception_object);
+
+#define PHP_HTTP_EX_DEF_CE php_http_exception_class_entry
+#define PHP_HTTP_EX_CE(name) php_http_ ##name## _exception_class_entry
+
+extern zend_class_entry *PHP_HTTP_EX_DEF_CE;
+extern zend_class_entry *PHP_HTTP_EX_CE(runtime);
+extern zend_class_entry *PHP_HTTP_EX_CE(header);
+extern zend_class_entry *PHP_HTTP_EX_CE(malformed_headers);
+extern zend_class_entry *PHP_HTTP_EX_CE(request_method);
+extern zend_class_entry *PHP_HTTP_EX_CE(message);
+extern zend_class_entry *PHP_HTTP_EX_CE(message_type);
+extern zend_class_entry *PHP_HTTP_EX_CE(invalid_param);
+extern zend_class_entry *PHP_HTTP_EX_CE(encoding);
+extern zend_class_entry *PHP_HTTP_EX_CE(request);
+extern zend_class_entry *PHP_HTTP_EX_CE(request_pool);
+extern zend_class_entry *PHP_HTTP_EX_CE(socket);
+extern zend_class_entry *PHP_HTTP_EX_CE(response);
+extern zend_class_entry *PHP_HTTP_EX_CE(url);
+extern zend_class_entry *PHP_HTTP_EX_CE(querystring);
+extern zend_class_entry *PHP_HTTP_EX_CE(cookie);
+extern zend_function_entry php_http_exception_method_entry[];
+
+PHP_HTTP_API zend_class_entry *php_http_exception_get_default(void);
+PHP_HTTP_API zend_class_entry *php_http_exception_get_for_code(long code);
+
+PHP_MINIT_FUNCTION(http_exception);
+
+#endif
+
+/*
+ * 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_filter.c b/php_http_filter.c
new file mode 100644 (file)
index 0000000..95b3cbf
--- /dev/null
@@ -0,0 +1,437 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_filter_api.c 292841 2009-12-31 08:48:57Z mike $ */
+
+#include "php_http.h"
+
+PHP_MINIT_FUNCTION(http_filter)
+{
+       php_stream_filter_register_factory("http.*", &php_http_filter_factory TSRMLS_CC);
+       return SUCCESS;
+}
+
+#define PHP_HTTP_FILTER_PARAMS \
+       php_stream *stream, \
+       php_stream_filter *this, \
+       php_stream_bucket_brigade *buckets_in, \
+       php_stream_bucket_brigade *buckets_out, \
+       size_t *bytes_consumed, int flags \
+       TSRMLS_DC
+#define PHP_HTTP_FILTER_OP(filter) \
+       http_filter_op_ ##filter
+#define PHP_HTTP_FILTER_OPS(filter) \
+       php_stream_filter_ops PHP_HTTP_FILTER_OP(filter)
+#define PHP_HTTP_FILTER_DTOR(filter) \
+       http_filter_ ##filter## _dtor
+#define PHP_HTTP_FILTER_DESTRUCTOR(filter) \
+       void PHP_HTTP_FILTER_DTOR(filter)(php_stream_filter *this TSRMLS_DC)
+#define PHP_HTTP_FILTER_FUNC(filter) \
+       http_filter_ ##filter
+#define PHP_HTTP_FILTER_FUNCTION(filter) \
+       php_stream_filter_status_t PHP_HTTP_FILTER_FUNC(filter)(PHP_HTTP_FILTER_PARAMS)
+#define PHP_HTTP_FILTER_BUFFER(filter) \
+       http_filter_ ##filter## _buffer
+
+#define NEW_BUCKET(data, length) \
+       { \
+               char *__data; \
+               php_stream_bucket *__buck; \
+               \
+               __data = pemalloc(length, this->is_persistent); \
+               if (!__data) { \
+                       return PSFS_ERR_FATAL; \
+               } \
+               memcpy(__data, data, length); \
+               \
+               __buck = php_stream_bucket_new(stream, __data, length, 1, this->is_persistent TSRMLS_CC); \
+               if (!__buck) { \
+                       pefree(__data, this->is_persistent); \
+                       return PSFS_ERR_FATAL; \
+               } \
+               \
+               php_stream_bucket_append(buckets_out, __buck TSRMLS_CC); \
+       }
+
+typedef struct _http_chunked_decode_filter_buffer_t {
+       php_http_buffer buffer;
+       ulong   hexlen;
+} PHP_HTTP_FILTER_BUFFER(chunked_decode);
+
+typedef php_http_encoding_stream_t PHP_HTTP_FILTER_BUFFER(zlib);
+
+static PHP_HTTP_FILTER_FUNCTION(chunked_decode)
+{
+       int out_avail = 0;
+       php_stream_bucket *ptr, *nxt;
+       PHP_HTTP_FILTER_BUFFER(chunked_decode) *buffer = (PHP_HTTP_FILTER_BUFFER(chunked_decode) *) (this->abstract);
+       
+       if (bytes_consumed) {
+               *bytes_consumed = 0;
+       }
+       
+       /* new data available? */
+       if (buckets_in->head) {
+               
+               /* fetch available bucket data */
+               for (ptr = buckets_in->head; ptr; ptr = nxt) {
+                       nxt = ptr->next;
+                       if (bytes_consumed) {
+                               *bytes_consumed += ptr->buflen;
+                       }
+               
+                       if (PHP_HTTP_BUFFER_NOMEM == php_http_buffer_append(PHP_HTTP_BUFFER(buffer), ptr->buf, ptr->buflen)) {
+                               return PSFS_ERR_FATAL;
+                       }
+                       
+                       php_stream_bucket_unlink(ptr TSRMLS_CC);
+                       php_stream_bucket_delref(ptr TSRMLS_CC);
+               }
+       }
+       if (!php_http_buffer_fix(PHP_HTTP_BUFFER(buffer))) {
+               return PSFS_ERR_FATAL;
+       }
+
+       /* we have data in our buffer */
+       while (PHP_HTTP_BUFFER_LEN(buffer)) {
+       
+               /* we already know the size of the chunk and are waiting for data */
+               if (buffer->hexlen) {
+               
+                       /* not enough data buffered */
+                       if (PHP_HTTP_BUFFER_LEN(buffer) < buffer->hexlen) {
+                       
+                               /* flush anyway? */
+                               if (flags & PSFS_FLAG_FLUSH_INC) {
+                               
+                                       /* flush all data (should only be chunk data) */
+                                       out_avail = 1;
+                                       NEW_BUCKET(PHP_HTTP_BUFFER_VAL(buffer), PHP_HTTP_BUFFER_LEN(buffer));
+                                       
+                                       /* waiting for less data now */
+                                       buffer->hexlen -= PHP_HTTP_BUFFER_LEN(buffer);
+                                       /* no more buffered data */
+                                       php_http_buffer_reset(PHP_HTTP_BUFFER(buffer));
+                                       /* break */
+                               } 
+                               
+                               /* we have too less data and don't need to flush */
+                               else {
+                                       break;
+                               }
+                       } 
+                       
+                       /* we seem to have all data of the chunk */
+                       else {
+                               out_avail = 1;
+                               NEW_BUCKET(PHP_HTTP_BUFFER_VAL(buffer), buffer->hexlen);
+                               
+                               /* remove outgoing data from the buffer */
+                               php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, buffer->hexlen);
+                               /* reset hexlen */
+                               buffer->hexlen = 0;
+                               /* continue */
+                       }
+               } 
+               
+               /* we don't know the length of the chunk yet */
+               else {
+                       size_t off = 0;
+                       
+                       /* ignore preceeding CRLFs (too loose?) */
+                       while (off < PHP_HTTP_BUFFER_LEN(buffer) && (
+                                       PHP_HTTP_BUFFER_VAL(buffer)[off] == '\n' || 
+                                       PHP_HTTP_BUFFER_VAL(buffer)[off] == '\r')) {
+                               ++off;
+                       }
+                       if (off) {
+                               php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, off);
+                       }
+                       
+                       /* still data there? */
+                       if (PHP_HTTP_BUFFER_LEN(buffer)) {
+                               int eollen;
+                               const char *eolstr;
+                               
+                               /* we need eol, so we can be sure we have all hex digits */
+                               php_http_buffer_fix(PHP_HTTP_BUFFER(buffer));
+                               if ((eolstr = php_http_locate_bin_eol(PHP_HTTP_BUFFER_VAL(buffer), PHP_HTTP_BUFFER_LEN(buffer), &eollen))) {
+                                       char *stop = NULL;
+                                       
+                                       /* read in chunk size */
+                                       buffer->hexlen = strtoul(PHP_HTTP_BUFFER_VAL(buffer), &stop, 16);
+                                       
+                                       /*      if strtoul() stops at the beginning of the buffered data
+                                               there's domething oddly wrong, i.e. bad input */
+                                       if (stop == PHP_HTTP_BUFFER_VAL(buffer)) {
+                                               return PSFS_ERR_FATAL;
+                                       }
+                                       
+                                       /* cut out <chunk size hex><chunk extension><eol> */
+                                       php_http_buffer_cut(PHP_HTTP_BUFFER(buffer), 0, eolstr + eollen - PHP_HTTP_BUFFER_VAL(buffer));
+                                       /* buffer->hexlen is 0 now or contains the size of the next chunk */
+                                       if (!buffer->hexlen) {
+                                               php_stream_notify_info(stream->context, PHP_STREAM_NOTIFY_COMPLETED, NULL, 0);
+                                               break;
+                                       }
+                                       /* continue */
+                               } else {
+                                       /* we have not enough data buffered to read in chunk size */
+                                       break;
+                               }
+                       }
+                       /* break */
+               }
+       }
+       
+       /* flush before close, but only if we are already waiting for more data */
+       if ((flags & PSFS_FLAG_FLUSH_CLOSE) && buffer->hexlen && PHP_HTTP_BUFFER_LEN(buffer)) {
+               out_avail = 1;
+               NEW_BUCKET(PHP_HTTP_BUFFER_VAL(buffer), PHP_HTTP_BUFFER_LEN(buffer));
+               php_http_buffer_reset(PHP_HTTP_BUFFER(buffer));
+               buffer->hexlen = 0;
+       }
+       
+       return out_avail ? PSFS_PASS_ON : PSFS_FEED_ME;
+}
+
+static PHP_HTTP_FILTER_DESTRUCTOR(chunked_decode)
+{
+       PHP_HTTP_FILTER_BUFFER(chunked_decode) *b = (PHP_HTTP_FILTER_BUFFER(chunked_decode) *) (this->abstract);
+       
+       php_http_buffer_dtor(PHP_HTTP_BUFFER(b));
+       pefree(b, this->is_persistent);
+}
+
+static PHP_HTTP_FILTER_FUNCTION(chunked_encode)
+{
+       int out_avail = 0;
+       php_stream_bucket *ptr, *nxt;
+       
+       if (bytes_consumed) {
+               *bytes_consumed = 0;
+       }
+       
+       /* new data available? */
+       if (buckets_in->head) {
+               php_http_buffer buf;
+               out_avail = 1;
+               
+               php_http_buffer_init(&buf);
+               
+               /* fetch available bucket data */
+               for (ptr = buckets_in->head; ptr; ptr = nxt) {
+                       nxt = ptr->next;
+                       if (bytes_consumed) {
+                               *bytes_consumed += ptr->buflen;
+                       }
+                       
+                       php_http_buffer_appendf(&buf, "%lx" PHP_HTTP_CRLF, ptr->buflen);
+                       php_http_buffer_append(&buf, ptr->buf, ptr->buflen);
+                       php_http_buffer_appends(&buf, PHP_HTTP_CRLF);
+                       
+                       /* pass through */
+                       NEW_BUCKET(PHP_HTTP_BUFFER_VAL(&buf), PHP_HTTP_BUFFER_LEN(&buf));
+                       /* reset */
+                       php_http_buffer_reset(&buf);
+                       
+                       php_stream_bucket_unlink(ptr TSRMLS_CC);
+                       php_stream_bucket_delref(ptr TSRMLS_CC);
+               }
+               
+               /* free buffer */
+               php_http_buffer_dtor(&buf);
+       }
+       
+       /* terminate with "0" */
+       if (flags & PSFS_FLAG_FLUSH_CLOSE) {
+               out_avail = 1;
+               NEW_BUCKET("0" PHP_HTTP_CRLF, lenof("0" PHP_HTTP_CRLF));
+       }
+       
+       return out_avail ? PSFS_PASS_ON : PSFS_FEED_ME;
+}
+
+static PHP_HTTP_FILTER_OPS(chunked_decode) = {
+       PHP_HTTP_FILTER_FUNC(chunked_decode),
+       PHP_HTTP_FILTER_DTOR(chunked_decode),
+       "http.chunked_decode"
+};
+
+static PHP_HTTP_FILTER_OPS(chunked_encode) = {
+       PHP_HTTP_FILTER_FUNC(chunked_encode),
+       NULL,
+       "http.chunked_encode"
+};
+
+static PHP_HTTP_FILTER_FUNCTION(zlib)
+{
+       int out_avail = 0;
+       php_stream_bucket *ptr, *nxt;
+       PHP_HTTP_FILTER_BUFFER(zlib) *buffer = (PHP_HTTP_FILTER_BUFFER(zlib) *) this->abstract;
+       
+       if (bytes_consumed) {
+               *bytes_consumed = 0;
+       }
+       
+       /* new data available? */
+       if (buckets_in->head) {
+               
+               /* fetch available bucket data */
+               for (ptr = buckets_in->head; ptr; ptr = nxt) {
+                       char *encoded = NULL;
+                       size_t encoded_len = 0;
+                       
+                       nxt = ptr->next;
+                       if (bytes_consumed) {
+                               *bytes_consumed += ptr->buflen;
+                       }
+                       
+                       if (ptr->buflen) {
+                               php_http_encoding_stream_update(buffer, ptr->buf, ptr->buflen, &encoded, &encoded_len TSRMLS_CC);
+                               if (encoded) {
+                                       if (encoded_len) {
+                                               out_avail = 1;
+                                               NEW_BUCKET(encoded, encoded_len);
+                                       }
+                                       efree(encoded);
+                               }
+                       }
+                       
+                       php_stream_bucket_unlink(ptr TSRMLS_CC);
+                       php_stream_bucket_delref(ptr TSRMLS_CC);
+               }
+       }
+       
+       /* flush & close */
+       if (flags & PSFS_FLAG_FLUSH_INC) {
+               char *encoded = NULL;
+               size_t encoded_len = 0;
+               
+               php_http_encoding_stream_flush(buffer, &encoded, &encoded_len TSRMLS_CC);
+               if (encoded) {
+                       if (encoded_len) {
+                               out_avail = 1;
+                               NEW_BUCKET(encoded, encoded_len);
+                       }
+                       efree(encoded);
+               }
+       }
+       
+       if (flags & PSFS_FLAG_FLUSH_CLOSE) {
+               char *encoded = NULL;
+               size_t encoded_len = 0;
+               
+               php_http_encoding_stream_finish(buffer, &encoded, &encoded_len TSRMLS_CC);
+               if (encoded) {
+                       if (encoded_len) {
+                               out_avail = 1;
+                               NEW_BUCKET(encoded, encoded_len);
+                       }
+                       efree(encoded);
+               }
+       }
+       
+       return out_avail ? PSFS_PASS_ON : PSFS_FEED_ME;
+}
+static PHP_HTTP_FILTER_DESTRUCTOR(zlib)
+{
+       PHP_HTTP_FILTER_BUFFER(zlib) *buffer = (PHP_HTTP_FILTER_BUFFER(zlib) *) this->abstract;
+       php_http_encoding_stream_free(&buffer TSRMLS_CC);
+}
+
+static PHP_HTTP_FILTER_OPS(deflate) = {
+       PHP_HTTP_FILTER_FUNC(zlib),
+       PHP_HTTP_FILTER_DTOR(zlib),
+       "http.deflate"
+};
+
+static PHP_HTTP_FILTER_OPS(inflate) = {
+       PHP_HTTP_FILTER_FUNC(zlib),
+       PHP_HTTP_FILTER_DTOR(zlib),
+       "http.inflate"
+};
+
+static php_stream_filter *http_filter_create(const char *name, zval *params, int p TSRMLS_DC)
+{
+       zval **tmp = &params;
+       php_stream_filter *f = NULL;
+       
+       if (!strcasecmp(name, "http.chunked_decode")) {
+               PHP_HTTP_FILTER_BUFFER(chunked_decode) *b = NULL;
+               
+               if ((b = pecalloc(1, sizeof(PHP_HTTP_FILTER_BUFFER(chunked_decode)), p))) {
+                       php_http_buffer_init_ex(PHP_HTTP_BUFFER(b), 4096, p ? PHP_HTTP_BUFFER_INIT_PERSISTENT : 0);
+                       if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(chunked_decode), b, p))) {
+                               pefree(b, p);
+                       }
+               }
+       } else
+       
+       if (!strcasecmp(name, "http.chunked_encode")) {
+               f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(chunked_encode), NULL, p);
+       } else
+       
+       if (!strcasecmp(name, "http.inflate")) {
+               int flags = p ? PHP_HTTP_ENCODING_STREAM_PERSISTENT : 0;
+               PHP_HTTP_FILTER_BUFFER(zlib) *b = NULL;
+               
+               if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_inflate_ops(), flags TSRMLS_CC))) {
+                       if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(inflate), b, p))) {
+                               php_http_encoding_stream_free(&b TSRMLS_CC);
+                       }
+               }
+       } else
+       
+       if (!strcasecmp(name, "http.deflate")) {
+               int flags = p ? PHP_HTTP_ENCODING_STREAM_PERSISTENT : 0;
+               PHP_HTTP_FILTER_BUFFER(zlib) *b = NULL;
+               
+               if (params) {
+                       switch (Z_TYPE_P(params)) {
+                               case IS_ARRAY:
+                               case IS_OBJECT:
+                                       if (SUCCESS != zend_hash_find(HASH_OF(params), "flags", sizeof("flags"), (void *) &tmp)) {
+                                               break;
+                                       }
+                               default:
+                               {
+                                       zval *num = php_http_zsep(IS_LONG, *tmp);
+                                       
+                                       flags |= (Z_LVAL_P(num) & 0x0fffffff);
+                                       zval_ptr_dtor(&num);
+                               }
+                       }
+               }
+               if ((b = php_http_encoding_stream_init(NULL, php_http_encoding_stream_get_deflate_ops(), flags TSRMLS_CC))) {
+                       if (!(f = php_stream_filter_alloc(&PHP_HTTP_FILTER_OP(deflate), b, p))) {
+                               php_http_encoding_stream_free(&b TSRMLS_CC);
+                       }
+               }
+       }
+       
+       return f;
+}
+
+php_stream_filter_factory php_http_filter_factory = {
+       http_filter_create
+};
+
+
+/*
+ * 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_filter.h b/php_http_filter.h
new file mode 100644 (file)
index 0000000..c8cb37d
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_filter_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_FILTER_H
+#define PHP_HTTP_FILTER_H
+
+extern php_stream_filter_factory php_http_filter_factory;
+PHP_MINIT_FUNCTION(http_filter);
+
+#endif
+
+/*
+ * 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_header_parser.c b/php_http_header_parser.c
new file mode 100644 (file)
index 0000000..a70d853
--- /dev/null
@@ -0,0 +1,225 @@
+
+#include "php_http.h"
+
+typedef struct php_http_header_parser_state_spec {
+       php_http_header_parser_state_t state;
+       unsigned need_data:1;
+} php_http_header_parser_state_spec_t;
+
+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_HEADER_DONE,      0},
+               {PHP_HTTP_HEADER_PARSER_STATE_DONE,                     0}
+};
+
+
+PHP_HTTP_API php_http_header_parser_t *php_http_header_parser_init(php_http_header_parser_t *parser TSRMLS_CC)
+{
+       if (!parser) {
+               parser = emalloc(sizeof(*parser));
+       }
+       memset(parser, 0, sizeof(*parser));
+
+       TSRMLS_SET_CTX(parser->ts);
+
+       return parser;
+}
+
+
+PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_push(php_http_header_parser_t *parser, unsigned argc, ...)
+{
+       va_list va_args;
+       unsigned i;
+       php_http_header_parser_state_t state = 0;
+
+       va_start(va_args, argc);
+       for (i = 0; i < argc; ++i) {
+               state = va_arg(va_args, php_http_header_parser_state_t);
+               zend_stack_push(&parser->stack, &state, sizeof(state));
+       }
+       va_end(va_args);
+
+       return state;
+}
+
+PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_is(php_http_header_parser_t *parser)
+{
+       php_http_header_parser_state_t *state;
+
+       if (SUCCESS == zend_stack_top(&parser->stack, (void *) &state)) {
+               return *state;
+       }
+       return PHP_HTTP_HEADER_PARSER_STATE_START;
+}
+
+PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_pop(php_http_header_parser_t *parser)
+{
+       php_http_header_parser_state_t state, *state_ptr;
+       if (SUCCESS == zend_stack_top(&parser->stack, (void *) &state_ptr)) {
+               state = *state_ptr;
+               zend_stack_del_top(&parser->stack);
+               return state;
+       }
+       return PHP_HTTP_HEADER_PARSER_STATE_START;
+}
+
+PHP_HTTP_API void php_http_header_parser_dtor(php_http_header_parser_t *parser)
+{
+       zend_stack_destroy(&parser->stack);
+       php_http_info_dtor(&parser->info);
+       STR_FREE(parser->_key.str);
+       STR_FREE(parser->_val.str);
+}
+
+PHP_HTTP_API void php_http_header_parser_free(php_http_header_parser_t **parser)
+{
+       if (*parser) {
+               php_http_header_parser_dtor(*parser);
+               efree(*parser);
+               *parser = NULL;
+       }
+}
+
+PHP_HTTP_API STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer *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 (%d) %zu\n",
+                               state[php_http_header_parser_state_is(parser)], zend_hash_num_elements(headers), buffer->used);
+#endif
+               switch (php_http_header_parser_state_pop(parser)) {
+                       case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
+                               return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+
+                       case PHP_HTTP_HEADER_PARSER_STATE_START: {
+                               char *ptr = buffer->data;
+
+                               while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
+                                       ++ptr;
+                               }
+
+                               php_http_buffer_cut(buffer, 0, ptr - buffer->data);
+                               php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY);
+                               break;
+                       }
+
+                       case PHP_HTTP_HEADER_PARSER_STATE_KEY: {
+                               const char *colon, *eol_str;
+                               int eol_len;
+
+                               if (buffer->data == (eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) {
+                                       /* end of headers */
+                                       php_http_buffer_cut(buffer, 0, eol_len);
+                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_DONE);
+                               } else if (php_http_info_parse(&parser->info, php_http_buffer_fix(buffer)->data TSRMLS_CC)) {
+                                       /* new message starting with request/response line */
+                                       if (callback_func) {
+                                               callback_func(callback_arg, &headers, &parser->info TSRMLS_CC);
+                                       }
+                                       php_http_info_dtor(&parser->info);
+                                       php_http_buffer_cut(buffer, 0, eol_str + eol_len - buffer->data);
+                                       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);
+                                       while (PHP_HTTP_IS_CTYPE(space, *++colon));
+                                       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 */
+                                       return php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_FAILURE);
+                               }
+                               break;
+                               /*
+                               if (colon && (!eol_str || colon < eol_str)) {
+                                       parser->_key.str = estrndup(buffer->data, parser->_key.len = colon - buffer->data);
+                                       while (PHP_HTTP_IS_CTYPE(space, *++colon));
+                                       php_http_buffer_cut(buffer, 0, colon - buffer->data);
+                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_VALUE);
+                               } else if (eol_str) {
+                                       if (eol_str == buffer->data) {
+                                               php_http_buffer_cut(buffer, 0, eol_len);
+                                               php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_DONE);
+                                       } else if (php_http_info_parse(&parser->info, php_http_buffer_fix(buffer)->data TSRMLS_CC)) {
+                                               if (callback_func) {
+                                                       callback_func(callback_arg, &headers, &parser->info TSRMLS_CC);
+                                               }
+                                               php_http_info_dtor(&parser->info);
+                                               php_http_buffer_cut(buffer, 0, eol_str + eol_len - buffer->data);
+                                               php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
+                                       } else {
+                                               return PHP_HTTP_HEADER_PARSER_STATE_FAILURE;
+                                       }
+                               } else {
+                                       php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY);
+                                       return PHP_HTTP_HEADER_PARSER_STATE_KEY;
+                               }
+                               break;
+                               */
+                       }
+
+                       case PHP_HTTP_HEADER_PARSER_STATE_VALUE: {
+                               const char *eol_str;
+                               int eol_len;
+
+                               do {
+                                       if ((eol_str = php_http_locate_bin_eol(buffer->data, buffer->used, &eol_len))) {
+                                               if (eol_str + eol_len - buffer->data < buffer->used) {
+                                                       char nextline = *(eol_str + eol_len);
+
+                                                       if (nextline == '\t' || nextline == ' ') {
+                                                               php_http_buffer_cut(buffer, eol_str - buffer->data, eol_len);
+                                                               continue;
+                                                       }
+                                               }
+
+                                               parser->_val.str = estrndup(buffer->data, parser->_val.len = eol_str - buffer->data);
+                                               php_http_buffer_cut(buffer, 0, eol_str + eol_len - buffer->data);
+                                               php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE);
+                                       } else if (flags & PHP_HTTP_HEADER_PARSER_CLEANUP) {
+                                               if (buffer->used) {
+                                                       parser->_val.str = estrndup(buffer->data, parser->_val.len = buffer->used);
+                                                       php_http_buffer_reset(buffer);
+                                               }
+                                               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);
+                                       }
+                               } while (0);
+
+                               break;
+                       }
+
+                       case PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE:
+                               if (parser->_key.str && parser->_val.str) {
+                                       zval array, **exist;
+
+                                       INIT_PZVAL_ARRAY(&array, headers);
+                                       php_http_pretty_key(parser->_key.str, parser->_key.len, 1, 1);
+                                       if (SUCCESS == zend_hash_find(headers, parser->_key.str, parser->_key.len + 1, (void *) &exist)) {
+                                               convert_to_array(*exist);
+                                               add_next_index_stringl(&array, parser->_val.str, parser->_val.len, 0);
+                                       } else {
+                                               add_assoc_stringl_ex(&array, parser->_key.str, parser->_key.len + 1, parser->_val.str, parser->_val.len, 0);
+                                       }
+                                       parser->_val.str = NULL;
+                               }
+
+                               STR_SET(parser->_key.str, NULL);
+                               STR_SET(parser->_val.str, NULL);
+
+                               php_http_header_parser_state_push(parser, 1, PHP_HTTP_HEADER_PARSER_STATE_KEY);
+                               break;
+
+                       case PHP_HTTP_HEADER_PARSER_STATE_DONE:
+                               return PHP_HTTP_HEADER_PARSER_STATE_DONE;
+               }
+       }
+
+       return php_http_header_parser_state_is(parser);
+}
diff --git a/php_http_header_parser.h b/php_http_header_parser.h
new file mode 100644 (file)
index 0000000..9d24be1
--- /dev/null
@@ -0,0 +1,39 @@
+#ifndef PHP_HTTP_HEADER_PARSER_H
+#define PHP_HTTP_HEADER_PARSER_H
+
+typedef enum php_http_header_parser_state {
+       PHP_HTTP_HEADER_PARSER_STATE_FAILURE = FAILURE,
+       PHP_HTTP_HEADER_PARSER_STATE_START = 0,
+       PHP_HTTP_HEADER_PARSER_STATE_KEY,
+       PHP_HTTP_HEADER_PARSER_STATE_VALUE,
+       PHP_HTTP_HEADER_PARSER_STATE_HEADER_DONE,
+       PHP_HTTP_HEADER_PARSER_STATE_DONE
+} php_http_header_parser_state_t;
+
+#define PHP_HTTP_HEADER_PARSER_CLEANUP 0x1
+
+typedef struct php_http_header_parser {
+       zend_stack stack;
+       php_http_info_t info;
+       struct {
+               char *str;
+               size_t len;
+       } _key;
+       struct {
+               char *str;
+               size_t len;
+       } _val;
+#ifdef ZTS
+       void ***ts;
+#endif
+} php_http_header_parser_t;
+
+PHP_HTTP_API php_http_header_parser_t *php_http_header_parser_init(php_http_header_parser_t *parser TSRMLS_DC);
+PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_push(php_http_header_parser_t *parser, unsigned argc, ...);
+PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_is(php_http_header_parser_t *parser);
+PHP_HTTP_API php_http_header_parser_state_t php_http_header_parser_state_pop(php_http_header_parser_t *parser);
+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 STATUS php_http_header_parser_parse(php_http_header_parser_t *parser, php_http_buffer *buffer, unsigned flags, HashTable *headers, php_http_info_callback_t callback_func, void *callback_arg);
+
+#endif /* PHP_HTTP_HEADER_PARSER_H */
diff --git a/php_http_headers.c b/php_http_headers.c
new file mode 100644 (file)
index 0000000..b5a700e
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_headers_api.c 300300 2010-06-09 07:29:35Z mike $ */
+
+#include "php_http.h"
+
+PHP_HTTP_API STATUS php_http_headers_parse(const char *header, size_t length, HashTable *headers, php_http_info_callback_t callback_func, void **callback_data TSRMLS_DC)
+{
+       php_http_header_parser_t ctx;
+       php_http_buffer buf;
+       
+       php_http_buffer_from_string_ex(&buf, header, length);
+       php_http_header_parser_init(&ctx TSRMLS_CC);
+       php_http_header_parser_parse(&ctx, &buf, PHP_HTTP_HEADER_PARSER_CLEANUP, headers, callback_func, callback_data);
+       php_http_header_parser_dtor(&ctx);
+       php_http_buffer_dtor(&buf);
+       /* FIXME */
+       return SUCCESS;
+}
+
+/*
+ * 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_headers.h b/php_http_headers.h
new file mode 100644 (file)
index 0000000..8cc0b8a
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_headers_api.h 300300 2010-06-09 07:29:35Z mike $ */
+
+#ifndef PHP_HTTP_HEADERS_H
+#define PHP_HTTP_HEADERS_H
+
+#include "php_http_info.h"
+
+PHP_HTTP_API STATUS php_http_headers_parse(const char *header, size_t length, HashTable *headers, php_http_info_callback_t callback_func, void **callback_data TSRMLS_DC);
+
+#endif
+
+/*
+ * 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_info.c b/php_http_info.c
new file mode 100644 (file)
index 0000000..534eb2c
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_info_api.c 292841 2009-12-31 08:48:57Z mike $ */
+
+#include "php_http.h"
+
+PHP_HTTP_API void php_http_info_default_callback(void **nothing, HashTable **headers, php_http_info_t *info TSRMLS_DC)
+{
+       zval array;
+       (void) nothing;
+       
+       INIT_PZVAL_ARRAY(&array, *headers);
+       
+       switch (info->type) {
+               case PHP_HTTP_REQUEST:
+                       add_assoc_string(&array, "Request Method", PHP_HTTP_INFO(info).request.method, 1);
+                       add_assoc_string(&array, "Request Url", PHP_HTTP_INFO(info).request.url, 1);
+                       break;
+               
+               case PHP_HTTP_RESPONSE:
+                       add_assoc_long(&array, "Response Code", (long) PHP_HTTP_INFO(info).response.code);
+                       add_assoc_string(&array, "Response Status", PHP_HTTP_INFO(info).response.status, 1);
+                       break;
+
+               case PHP_HTTP_NONE:
+                       break;
+       }
+}
+
+PHP_HTTP_API php_http_info_t *php_http_info_init(php_http_info_t *i TSRMLS_DC)
+{
+       if (!i) {
+               i = emalloc(sizeof(*i));
+       }
+
+       memset(i, 0, sizeof(*i));
+
+       return i;
+}
+
+PHP_HTTP_API 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);
+                       break;
+               
+               case PHP_HTTP_RESPONSE:
+                       STR_SET(PHP_HTTP_INFO(i).response.status, NULL);
+                       break;
+               
+               default:
+                       break;
+       }
+}
+
+PHP_HTTP_API void php_http_info_free(php_http_info_t **i)
+{
+       if (*i) {
+               php_http_info_dtor(*i);
+               efree(*i);
+               *i = NULL;
+       }
+}
+
+PHP_HTTP_API php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_header TSRMLS_DC)
+{
+       const char *end, *http;
+       zend_bool free_info = !info;
+       
+       /* sane parameter */
+       if ((!pre_header) || (!*pre_header)) {
+               return NULL;
+       }
+       
+       /* where's the end of the line */
+       if (!(end = php_http_locate_eol(pre_header, NULL))) {
+               end = pre_header + strlen(pre_header);
+       }
+       
+       /* 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.")))) {
+               return NULL;
+       }
+       
+       info = php_http_info_init(info TSRMLS_CC);
+
+       /* and nothing than SPACE or NUL after HTTP/1.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")])))) {
+               if (free_info) {
+                       php_http_info_free(&info);
+               }
+               return NULL;
+       }
+
+#if 0
+       {
+               char *line = estrndup(pre_header, end - pre_header);
+               fprintf(stderr, "http_parse_info('%s')\n", line);
+               efree(line);
+       }
+#endif
+
+       /* is response */
+       if (pre_header == http) {
+               char *status = NULL;
+               const char *code = http + sizeof("HTTP/1.1");
+               
+               info->type = PHP_HTTP_RESPONSE;
+               while (' ' == *code) ++code;
+               if (code && end > code) {
+                       PHP_HTTP_INFO(info).response.code = strtol(code, &status, 10);
+               } else {
+                       PHP_HTTP_INFO(info).response.code = 0;
+               }
+               if (status && end > status) {
+                       while (' ' == *status) ++status;
+                       PHP_HTTP_INFO(info).response.status = estrndup(status, end - status);
+               } else {
+                       PHP_HTTP_INFO(info).response.status = NULL;
+               }
+               
+               return info;
+       }
+       
+       /* is request */
+       else if (!http[lenof("HTTP/1.x")] || http[lenof("HTTP/1.x")] == '\r' || http[lenof("HTTP/1.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);
+                       while (' ' == *url) ++url;
+                       while (' ' == *(http-1)) --http;
+                       if (http > url) {
+                               PHP_HTTP_INFO(info).request.url = estrndup(url, http - url);
+                       } else {
+                               efree(PHP_HTTP_INFO(info).request.method);
+                               return NULL;
+                       }
+               } else {
+                       PHP_HTTP_INFO(info).request.method = NULL;
+                       PHP_HTTP_INFO(info).request.url = NULL;
+               }
+               
+               return info;
+       }
+
+       /* some darn header containing HTTP/1.x */
+       else {
+               if (free_info) {
+                       php_http_info_free(&info);
+               }
+               return NULL;
+       }
+}
+
+/*
+ * 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_info.h b/php_http_info.h
new file mode 100644 (file)
index 0000000..2e47d19
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_info_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_INFO_H
+#define PHP_HTTP_INFO_H
+
+#include "php_http_version.h"
+
+#define PHP_HTTP_INFO_REQUEST_FMT_ARGS(_http_ptr, 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)->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, \
+                               (_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, \
+                               (_http_ptr)->info.response.status&&*(_http_ptr)->info.response.status ? " ":"", \
+                               STR_PTR((_http_ptr)->info.response.status)
+
+typedef struct php_http_info_data {
+       union {
+               /* GET /foo/bar */
+               struct { char *method; char *url; } request;
+               /* 200 Ok */
+               struct { unsigned code; char *status; } response;
+       } info;
+       php_http_version_t version;
+} php_http_info_data_t;
+
+typedef enum php_http_info_type {
+       PHP_HTTP_NONE = 0,
+       PHP_HTTP_REQUEST,
+       PHP_HTTP_RESPONSE
+} php_http_info_type_t;
+
+#define PHP_HTTP_INFO(ptr) (ptr)->http.info
+#define PHP_HTTP_INFO_IMPL(_http, _type) \
+       php_http_info_data_t _http; \
+       php_http_info_type_t _type;
+
+typedef struct php_http_info {
+       PHP_HTTP_INFO_IMPL(http, type)
+} php_http_info_t;
+
+typedef zend_bool (*php_http_info_callback_t)(void **callback_data, HashTable **headers, php_http_info_t *info TSRMLS_DC);
+
+PHP_HTTP_API void php_http_info_default_callback(void **nothing, HashTable **headers, php_http_info_t *info TSRMLS_DC);
+
+PHP_HTTP_API php_http_info_t *php_http_info_init(php_http_info_t *info TSRMLS_DC);
+PHP_HTTP_API php_http_info_t *php_http_info_parse(php_http_info_t *info, const char *pre_header TSRMLS_DC);
+PHP_HTTP_API void php_http_info_dtor(php_http_info_t *info);
+PHP_HTTP_API void php_http_info_free(php_http_info_t **info);
+
+#endif
+
+/*
+ * 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_message.c b/php_http_message.c
new file mode 100644 (file)
index 0000000..5f9220e
--- /dev/null
@@ -0,0 +1,1954 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_message_api.c 298689 2010-04-28 06:50:06Z mike $ */
+
+#include "php_http.h"
+
+/* API */
+
+PHP_HTTP_API zend_bool php_http_message_info_callback(php_http_message_t **message, HashTable **headers, php_http_info_t *info TSRMLS_DC)
+{
+       php_http_message_t *old = *message;
+
+       /* advance message */
+       if (old->type || zend_hash_num_elements(&old->hdrs) || PHP_HTTP_BUFFER_LEN(old)) {
+               (*message) = php_http_message_init(NULL, 0);
+               (*message)->parent = old;
+               (*headers) = &((*message)->hdrs);
+       }
+
+       php_http_message_set_info(*message, info);
+
+       return old != *message;
+}
+
+PHP_HTTP_API php_http_message_t *php_http_message_init(php_http_message_t *message, php_http_message_type_t type TSRMLS_DC)
+{
+       if (!message) {
+               message = emalloc(sizeof(*message));
+       }
+       memset(message, 0, sizeof(*message));
+       TSRMLS_SET_CTX(message->ts);
+
+       php_http_message_set_type(message, type);
+       zend_hash_init(&message->hdrs, 0, NULL, ZVAL_PTR_DTOR, 0);
+       php_http_message_body_init(&message->body, NULL TSRMLS_CC);
+
+       return message;
+}
+
+PHP_HTTP_API php_http_message_t *php_http_message_init_env(php_http_message_t *message, php_http_message_type_t type TSRMLS_DC)
+{
+       int free_msg = !message;
+       zval *sval, tval;
+       php_http_message_body_t *mbody;
+       
+       message = php_http_message_init(message, type TSRMLS_CC);
+       
+       switch (type) {
+               case PHP_HTTP_REQUEST:
+                       if ((sval = php_http_env_get_server_var(ZEND_STRL("SERVER_PROTOCOL"), 1 TSRMLS_CC)) && !strncmp(Z_STRVAL_P(sval), "HTTP/", lenof("HTTP/"))) {
+                               php_http_version_parse(&message->http.version, Z_STRVAL_P(sval));
+                       } else {
+                               message->http.version.major = 1;
+                               message->http.version.minor = 1;
+                       }
+                       if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_METHOD"), 1 TSRMLS_CC))) {
+                               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));
+                       }
+                       
+                       php_http_env_get_request_headers(&message->hdrs TSRMLS_CC);
+
+                       if ((mbody = php_http_env_get_request_body(TSRMLS_C))) {
+                               php_http_message_body_dtor(&message->body);
+                               php_http_message_body_copy(mbody, &message->body, 0 TSRMLS_CC);
+                       }
+                       break;
+                       
+               case PHP_HTTP_RESPONSE:
+                       if (!SG(sapi_headers).http_status_line || !php_http_info_parse((php_http_info_t *) &message->http, SG(sapi_headers).http_status_line TSRMLS_CC)) {
+                               message->http.version.major = 1;
+                               message->http.version.minor = 1;
+                               switch ((message->http.info.response.code = SG(sapi_headers).http_response_code)) {
+                                       case 0:
+                                               message->http.info.response.code = 200;
+                                       case 200:
+                                               message->http.info.response.status = estrdup("Ok");
+                                               break;
+                                       default:
+                                               message->http.info.response.status = estrdup("");
+                                               break;
+                               }
+
+                       }
+                       
+                       php_http_env_get_response_headers(&message->hdrs TSRMLS_CC);
+
+                       if (php_output_get_level()) {
+                               if (php_output_get_status(TSRMLS_C) & PHP_OUTPUT_SENT) {
+                                       php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "Could not fetch response body, output has already been sent at %s:%d", php_output_get_start_filename(TSRMLS_C), php_output_get_start_lineno(TSRMLS_C));
+                                       goto error;
+                               } else if (SUCCESS != php_output_get_contents(&tval TSRMLS_CC)) {
+                                       php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "Could not fetch response body");
+                                       goto error;
+                               } else {
+                                       php_http_message_body_append(&message->body, Z_STRVAL(tval), Z_STRLEN(tval));
+                                       zval_dtor(&tval);
+                               }
+                       }
+                       break;
+                       
+               default:
+               error:
+                       if (free_msg) {
+                               php_http_message_free(&message);
+                       } else {
+                               message = NULL;
+                       }
+                       break;
+       }
+       
+       return message;
+}
+
+PHP_HTTP_API php_http_message_t *php_http_message_parse(php_http_message_t *msg, const char *str, size_t len TSRMLS_DC)
+{
+       php_http_message_parser_t p;
+       php_http_buffer buf;
+
+       if (!msg) {
+               msg = php_http_message_init(NULL, 0 TSRMLS_CC);
+       }
+       php_http_buffer_from_string_ex(&buf, str, len);
+       php_http_message_parser_init(&p TSRMLS_CC);
+       php_http_message_parser_parse(&p, &buf, PHP_HTTP_MESSAGE_PARSER_CLEANUP, &msg);
+       php_http_message_parser_dtor(&p);
+       php_http_buffer_dtor(&buf);
+
+       /* FIXME */
+       return msg;
+}
+
+PHP_HTTP_API zval *php_http_message_header(php_http_message_t *msg, char *key_str, size_t key_len, int join)
+{
+       zval *ret = NULL, **header;
+       char *key = php_http_pretty_key(estrndup(key_str, key_len), key_len, 1, 1);
+
+       if (SUCCESS == zend_hash_find(&msg->hdrs, key, key_len + 1, (void *) &header)) {
+               if (join && Z_TYPE_PP(header) == IS_ARRAY) {
+                       zval *header_str, **val;
+                       HashPosition pos;
+                       php_http_buffer str;
+
+                       php_http_buffer_init(&str);
+                       MAKE_STD_ZVAL(header_str);
+                       FOREACH_VAL(pos, *header, val) {
+                               php_http_buffer_appendf(&str, PHP_HTTP_BUFFER_LEN(&str) ? ", %s":"%s", Z_STRVAL_PP(val));
+                       }
+                       php_http_buffer_fix(&str);
+                       ZVAL_STRINGL(header_str, PHP_HTTP_BUFFER_VAL(&str), PHP_HTTP_BUFFER_LEN(&str), 0);
+                       ret = header_str;
+               } else {
+                       ret = php_http_zsep(IS_STRING, *header);
+               }
+       }
+
+       efree(key);
+
+       return ret;
+}
+
+
+/* */
+PHP_HTTP_API void php_http_message_set_type(php_http_message_t *message, php_http_message_type_t type)
+{
+       /* just act if different */
+       if (type != message->type) {
+
+               /* free request info */
+               switch (message->type = type) {
+                       case PHP_HTTP_REQUEST:
+                               STR_FREE(message->http.info.request.method);
+                               STR_FREE(message->http.info.request.url);
+                               break;
+                       
+                       case PHP_HTTP_RESPONSE:
+                               STR_FREE(message->http.info.response.status);
+                               break;
+                       
+                       default:
+                               break;
+               }
+
+               memset(&message->http, 0, sizeof(message->http));
+       }
+}
+
+PHP_HTTP_API void php_http_message_set_info(php_http_message_t *message, php_http_info_t *info)
+{
+       php_http_message_set_type(message, info->type);
+       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);
+                       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);
+                       break;
+               
+               default:
+                       break;
+       }
+}
+
+static inline void message_headers(php_http_message_t *msg, php_http_buffer *str)
+{
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       HashPosition pos1;
+       zval **header;
+
+       switch (msg->type) {
+               case PHP_HTTP_REQUEST:
+                       php_http_buffer_appendf(str, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&msg->http, PHP_HTTP_CRLF));
+                       break;
+
+               case PHP_HTTP_RESPONSE:
+                       php_http_buffer_appendf(str, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&msg->http, PHP_HTTP_CRLF));
+                       break;
+
+               default:
+                       break;
+       }
+
+       FOREACH_HASH_KEYVAL(pos1, &msg->hdrs, key, header) {
+               if (key.type == HASH_KEY_IS_STRING) {
+                       HashPosition pos2;
+                       zval **single_header;
+
+                       switch (Z_TYPE_PP(header)) {
+                               case IS_BOOL:
+                                       php_http_buffer_appendf(str, "%s: %s" PHP_HTTP_CRLF, key.str, Z_BVAL_PP(header)?"true":"false");
+                                       break;
+                                       
+                               case IS_LONG:
+                                       php_http_buffer_appendf(str, "%s: %ld" PHP_HTTP_CRLF, key.str, Z_LVAL_PP(header));
+                                       break;
+                                       
+                               case IS_DOUBLE:
+                                       php_http_buffer_appendf(str, "%s: %F" PHP_HTTP_CRLF, key.str, Z_DVAL_PP(header));
+                                       break;
+                                       
+                               case IS_STRING:
+                                       if (Z_STRVAL_PP(header)[Z_STRLEN_PP(header)-1] == '\r') fprintf(stderr, "DOH!\n");
+                                       php_http_buffer_appendf(str, "%s: %s" PHP_HTTP_CRLF, key.str, Z_STRVAL_PP(header));
+                                       break;
+
+                               case IS_ARRAY:
+                                       FOREACH_VAL(pos2, *header, single_header) {
+                                               switch (Z_TYPE_PP(single_header)) {
+                                                       case IS_BOOL:
+                                                               php_http_buffer_appendf(str, "%s: %s" PHP_HTTP_CRLF, key.str, Z_BVAL_PP(single_header)?"true":"false");
+                                                               break;
+                                                               
+                                                       case IS_LONG:
+                                                               php_http_buffer_appendf(str, "%s: %ld" PHP_HTTP_CRLF, key.str, Z_LVAL_PP(single_header));
+                                                               break;
+                                                               
+                                                       case IS_DOUBLE:
+                                                               php_http_buffer_appendf(str, "%s: %F" PHP_HTTP_CRLF, key.str, Z_DVAL_PP(single_header));
+                                                               break;
+                                                               
+                                                       case IS_STRING:
+                                                               php_http_buffer_appendf(str, "%s: %s" PHP_HTTP_CRLF, key.str, Z_STRVAL_PP(single_header));
+                                                               break;
+                                               }
+                                       }
+                                       break;
+                       }
+               }
+       }
+}
+
+PHP_HTTP_API void php_http_message_to_callback(php_http_message_t *msg, php_http_pass_callback_t cb, void *cb_arg)
+{
+       php_http_buffer str;
+       TSRMLS_FETCH_FROM_CTX(msg->ts);
+
+       php_http_buffer_init_ex(&str, 0x1000, 0);
+       message_headers(msg, &str);
+       cb(cb_arg, PHP_HTTP_BUFFER_VAL(&str), PHP_HTTP_BUFFER_LEN(&str));
+       php_http_buffer_dtor(&str);
+
+       if (php_http_message_body_size(&msg->body)) {
+               cb(cb_arg, ZEND_STRL(PHP_HTTP_CRLF) TSRMLS_CC);
+               php_http_message_body_to_callback(&msg->body, cb, cb_arg, 0, 0);
+               cb(cb_arg, ZEND_STRL(PHP_HTTP_CRLF) TSRMLS_CC);
+       }
+}
+
+PHP_HTTP_API void php_http_message_to_string(php_http_message_t *msg, char **string, size_t *length)
+{
+       php_http_buffer str;
+       char *data;
+
+       php_http_buffer_init_ex(&str, 0x1000, 0);
+       message_headers(msg, &str);
+       if (php_http_message_body_size(&msg->body)) {
+               php_http_buffer_appends(&str, PHP_HTTP_CRLF);
+               php_http_message_body_to_callback(&msg->body, (php_http_pass_callback_t) php_http_buffer_append, &str, 0, 0);
+               php_http_buffer_appends(&str, PHP_HTTP_CRLF);
+       }
+
+       data = php_http_buffer_data(&str, string, length);
+       if (!string) {
+               efree(data);
+       }
+
+       php_http_buffer_dtor(&str);
+}
+
+PHP_HTTP_API void php_http_message_serialize(php_http_message_t *message, char **string, size_t *length)
+{
+       char *buf;
+       size_t len;
+       php_http_buffer str;
+
+       php_http_buffer_init(&str);
+
+       do {
+               php_http_message_to_string(message, &buf, &len);
+               php_http_buffer_prepend(&str, buf, len);
+               efree(buf);
+       } while ((message = message->parent));
+
+       buf = php_http_buffer_data(&str, string, length);
+       if (!string) {
+               efree(buf);
+       }
+
+       php_http_buffer_dtor(&str);
+}
+
+PHP_HTTP_API php_http_message_t *php_http_message_reverse(php_http_message_t *msg)
+{
+       int i, c = 0;
+       
+       php_http_message_count(c, msg);
+       
+       if (c > 1) {
+               php_http_message_t *tmp = msg, **arr = ecalloc(c, sizeof(**arr));
+               
+               for (i = 0; i < c; ++i) {
+                       arr[i] = tmp;
+                       tmp = tmp->parent;
+               }
+               arr[0]->parent = NULL;
+               for (i = 0; i < c-1; ++i) {
+                       arr[i+1]->parent = arr[i];
+               }
+               
+               msg = arr[c-1];
+               efree(arr);
+       }
+       
+       return msg;
+}
+
+PHP_HTTP_API php_http_message_t *php_http_message_interconnect(php_http_message_t *m1, php_http_message_t *m2)
+{
+       if (m1 && m2) {
+               int i = 0, c1 = 0, c2 = 0;
+               php_http_message_t *t1 = m1, *t2 = m2, *p1, *p2;
+               
+               php_http_message_count(c1, m1);
+               php_http_message_count(c2, m2);
+               
+               while (i++ < (c1 - c2)) {
+                       t1 = t1->parent;
+               }
+               while (i++ <= c1) {
+                       p1 = t1->parent;
+                       p2 = t2->parent;
+                       t1->parent = t2;
+                       t2->parent = p1;
+                       t1 = p1;
+                       t2 = p2;
+               }
+       } else if (!m1 && m2) {
+               m1 = m2;
+       }
+       return m1;
+}
+
+PHP_HTTP_API void php_http_message_to_struct(php_http_message_t *msg, zval *obj TSRMLS_DC)
+{
+       zval strct;
+       zval *headers;
+       char *version;
+       
+       INIT_PZVAL_ARRAY(&strct, HASH_OF(obj));
+       
+       add_assoc_long(&strct, "type", msg->type);
+       spprintf(&version, 0 ,"%u.%u", msg->http.version.major, msg->http.version.minor);
+       add_assoc_string_ex(&strct, ZEND_STRL("httpVersion"), version, 0);
+       switch (msg->type)
+       {
+               case PHP_HTTP_RESPONSE:
+                       add_assoc_long(&strct, "responseCode", msg->http.info.response.code);
+                       add_assoc_string(&strct, "responseStatus", STR_PTR(msg->http.info.response.status), 1);
+               break;
+               
+               case PHP_HTTP_REQUEST:
+                       add_assoc_string(&strct, "requestMethod", STR_PTR(msg->http.info.request.method), 1);
+                       add_assoc_string(&strct, "requestUrl", STR_PTR(msg->http.info.request.url), 1);
+               break;
+               
+               case PHP_HTTP_NONE:
+                       /* avoid compiler warning */
+               break;
+       }
+       
+       MAKE_STD_ZVAL(headers);
+       array_init(headers);
+       zend_hash_copy(Z_ARRVAL_P(headers), &msg->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+       add_assoc_zval(&strct, "headers", headers);
+       
+       add_assoc_stringl(&strct, "body", PHP_HTTP_BUFFER_VAL(msg), PHP_HTTP_BUFFER_LEN(msg), 1);
+       
+       if (msg->parent) {
+               zval *parent;
+               
+               MAKE_STD_ZVAL(parent);
+               if (Z_TYPE_P(obj) == IS_ARRAY) {
+                       array_init(parent);
+               } else {
+                       object_init(parent);
+               }
+               add_assoc_zval(&strct, "parentMessage", parent);
+               php_http_message_to_struct(msg->parent, parent);
+       } else {
+               add_assoc_null(&strct, "parentMessage");
+       }
+}
+/*
+PHP_HTTP_API STATUS _http_message_send(http_message *message TSRMLS_DC)
+{
+       STATUS rs = FAILURE;
+
+       switch (message->type) {
+               case PHP_HTTP_RESPONSE:
+               {
+                       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+                       zval **val;
+                       HashPosition pos;
+
+                       FOREACH_HASH_KEYVAL(pos, &message->hdrs, key, val) {
+                               if (key.type == HASH_KEY_IS_STRING) {
+                                       http_send_header_zval_ex(key.str, key.len-1, val, 1);
+                               }
+                       }
+                       rs =    SUCCESS == http_send_status(message->http.info.response.code) &&
+                                       SUCCESS == http_send_data(PHP_HTTP_BUFFER_VAL(message), PHP_HTTP_BUFFER_LEN(message)) ?
+                                       SUCCESS : FAILURE;
+                       break;
+               }
+
+               case PHP_HTTP_REQUEST:
+               {
+#ifdef PHP_HTTP_HAVE_CURL
+                       char *uri = NULL;
+                       http_request request;
+                       zval **zhost, *options, *headers;
+                       
+                       MAKE_STD_ZVAL(options);
+                       MAKE_STD_ZVAL(headers);
+                       array_init(options);
+                       array_init(headers);
+                       zend_hash_copy(Z_ARRVAL_P(headers), &message->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+                       add_assoc_zval(options, "headers", headers);
+
+                       if (SUCCESS == zend_hash_find(&message->hdrs, "Host", sizeof("Host"), (void *) &zhost) && Z_TYPE_PP(zhost) == IS_STRING) {
+                               char *colon = NULL;
+                               php_url parts, *url = php_url_parse(message->http.info.request.url);
+                               
+                               memset(&parts, 0, sizeof(php_url));
+
+                               if ((colon = strchr(Z_STRVAL_PP(zhost), ':'))) {
+                                       parts.port = atoi(colon + 1);
+                                       parts.host = estrndup(Z_STRVAL_PP(zhost), (Z_STRVAL_PP(zhost) - colon - 1));
+                               } else {
+                                       parts.host = estrndup(Z_STRVAL_PP(zhost), Z_STRLEN_PP(zhost));
+                               }
+                               
+                               http_build_url(PHP_HTTP_URL_REPLACE, url, &parts, NULL, &uri, NULL);
+                               php_url_free(url);
+                               efree(parts.host);
+                       } else {
+                               uri = http_absolute_url(message->http.info.request.url);
+                       }
+
+                       if ((request.meth = http_request_method_exists(1, 0, message->http.info.request.method))) {
+                               http_request_body body;
+                               
+                               http_request_init_ex(&request, NULL, request.meth, uri);
+                               request.body = http_request_body_init_ex(&body, PHP_HTTP_REQUEST_BODY_CSTRING, PHP_HTTP_BUFFER_VAL(message), PHP_HTTP_BUFFER_LEN(message), 0);
+                               if (SUCCESS == (rs = http_request_prepare(&request, Z_ARRVAL_P(options)))) {
+                                       http_request_exec(&request);
+                               }
+                               http_request_dtor(&request);
+                       } else {
+                               http_error_ex(HE_WARNING, PHP_HTTP_E_REQUEST_METHOD,
+                                       "Cannot send HttpMessage. Request method %s not supported",
+                                       message->http.info.request.method);
+                       }
+                       efree(uri);
+                       zval_ptr_dtor(&options);
+#else
+                       http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "HTTP requests not supported - ext/http was not linked against libcurl.");
+#endif
+               break;
+               }
+
+               case PHP_HTTP_NONE:
+               default:
+                       php_http_error(HE_WARNING, PHP_HTTP_E_MESSAGE_TYPE, "HttpMessage is neither of type PHP_HTTP_REQUEST nor PHP_HTTP_RESPONSE");
+                       break;
+       }
+
+       return rs;
+}
+*/
+
+PHP_HTTP_API php_http_message_t *php_http_message_copy(php_http_message_t *from, php_http_message_t *to)
+{
+       php_http_message_t *temp, *copy = NULL;
+       php_http_info_t info;
+       TSRMLS_FETCH_FROM_CTX(from->ts);
+       
+       if (from) {
+               info.type = from->type;
+               info.http = from->http;
+               
+               copy = temp = php_http_message_init(to, 0);
+               php_http_message_set_info(temp, &info);
+               zend_hash_copy(&temp->hdrs, &from->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+               php_http_message_body_copy(&from->body, &temp->body, 1);
+       
+               while (from->parent) {
+                       info.type = from->parent->type;
+                       info.http = from->parent->http;
+               
+                       temp->parent = php_http_message_init(NULL, 0);
+                       php_http_message_set_info(temp->parent, &info);
+                       zend_hash_copy(&temp->parent->hdrs, &from->parent->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+                       php_http_message_body_copy(&from->body, &temp->body, 1);
+               
+                       temp = temp->parent;
+                       from = from->parent;
+               }
+       }
+       
+       return copy;
+}
+
+PHP_HTTP_API void php_http_message_dtor(php_http_message_t *message)
+{
+       if (message) {
+               zend_hash_destroy(&message->hdrs);
+               php_http_message_body_dtor(&message->body);
+               
+               switch (message->type) {
+                       case PHP_HTTP_REQUEST:
+                               STR_SET(message->http.info.request.method, NULL);
+                               STR_SET(message->http.info.request.url, NULL);
+                               break;
+                       
+                       case PHP_HTTP_RESPONSE:
+                               STR_SET(message->http.info.response.status, NULL);
+                               break;
+                       
+                       default:
+                               break;
+               }
+       }
+}
+
+PHP_HTTP_API void php_http_message_free(php_http_message_t **message)
+{
+       if (*message) {
+               if ((*message)->parent) {
+                       php_http_message_free(&(*message)->parent);
+               }
+               php_http_message_dtor(*message);
+               efree(*message);
+               *message = NULL;
+       }
+}
+
+
+/* PHP */
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpMessage, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpMessage, method, 0)
+#define PHP_HTTP_MESSAGE_ME(method, visibility)        PHP_ME(HttpMessage, method, PHP_HTTP_ARGS(HttpMessage, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(__construct, 0)
+       PHP_HTTP_ARG_VAL(message, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getBody);
+PHP_HTTP_BEGIN_ARGS(setBody, 1)
+       PHP_HTTP_ARG_VAL(body, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(getHeader, 1)
+       PHP_HTTP_ARG_VAL(header, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setHeader, 1)
+       PHP_HTTP_ARG_VAL(header, 0)
+       PHP_HTTP_ARG_VAL(value, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(addHeader, 2)
+       PHP_HTTP_ARG_VAL(header, 0)
+       PHP_HTTP_ARG_VAL(value, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getHeaders);
+PHP_HTTP_BEGIN_ARGS(setHeaders, 1)
+       PHP_HTTP_ARG_VAL(headers, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(addHeaders, 1)
+       PHP_HTTP_ARG_VAL(headers, 0)
+       PHP_HTTP_ARG_VAL(append, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getType);
+PHP_HTTP_BEGIN_ARGS(setType, 1)
+       PHP_HTTP_ARG_VAL(type, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getInfo);
+PHP_HTTP_BEGIN_ARGS(setInfo, 1)
+       PHP_HTTP_ARG_VAL(http_info, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getResponseCode);
+PHP_HTTP_BEGIN_ARGS(setResponseCode, 1)
+       PHP_HTTP_ARG_VAL(response_code, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getResponseStatus);
+PHP_HTTP_BEGIN_ARGS(setResponseStatus, 1)
+       PHP_HTTP_ARG_VAL(response_status, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getRequestMethod);
+PHP_HTTP_BEGIN_ARGS(setRequestMethod, 1)
+       PHP_HTTP_ARG_VAL(request_method, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getRequestUrl);
+PHP_HTTP_BEGIN_ARGS(setRequestUrl, 1)
+       PHP_HTTP_ARG_VAL(url, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getHttpVersion);
+PHP_HTTP_BEGIN_ARGS(setHttpVersion, 1)
+       PHP_HTTP_ARG_VAL(http_version, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(guessContentType, 1)
+       PHP_HTTP_ARG_VAL(magic_file, 0)
+       PHP_HTTP_ARG_VAL(magic_mode, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getParentMessage);
+PHP_HTTP_EMPTY_ARGS(send);
+PHP_HTTP_EMPTY_ARGS(__toString);
+PHP_HTTP_BEGIN_ARGS(toString, 0)
+       PHP_HTTP_ARG_VAL(include_parent, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(toMessageTypeObject);
+
+PHP_HTTP_EMPTY_ARGS(count);
+
+PHP_HTTP_EMPTY_ARGS(serialize);
+PHP_HTTP_BEGIN_ARGS(unserialize, 1)
+       PHP_HTTP_ARG_VAL(serialized, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(rewind);
+PHP_HTTP_EMPTY_ARGS(valid);
+PHP_HTTP_EMPTY_ARGS(key);
+PHP_HTTP_EMPTY_ARGS(current);
+PHP_HTTP_EMPTY_ARGS(next);
+
+PHP_HTTP_EMPTY_ARGS(detach);
+PHP_HTTP_BEGIN_ARGS(prepend, 1)
+       PHP_HTTP_ARG_OBJ(http\\Message, message, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_EMPTY_ARGS(reverse);
+
+static zval *php_http_message_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC);
+static void php_http_message_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC);
+static zval **php_http_message_object_get_prop_ptr(zval *object, zval *member, const zend_literal *literal_key TSRMLS_DC);
+static HashTable *php_http_message_object_get_props(zval *object TSRMLS_DC);
+
+zend_class_entry *php_http_message_class_entry;
+zend_function_entry php_http_message_method_entry[] = {
+       PHP_HTTP_MESSAGE_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+       PHP_HTTP_MESSAGE_ME(getBody, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setBody, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getHeader, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setHeader, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(addHeader, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getHeaders, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setHeaders, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(addHeaders, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getType, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setType, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getInfo, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setInfo, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getResponseCode, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setResponseCode, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getResponseStatus, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setResponseStatus, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getRequestMethod, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setRequestMethod, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getRequestUrl, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setRequestUrl, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getHttpVersion, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(setHttpVersion, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(getParentMessage, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(toString, ZEND_ACC_PUBLIC)
+
+       /* implements Countable */
+       PHP_HTTP_MESSAGE_ME(count, ZEND_ACC_PUBLIC)
+
+       /* implements Serializable */
+       PHP_HTTP_MESSAGE_ME(serialize, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(unserialize, ZEND_ACC_PUBLIC)
+
+       /* implements Iterator */
+       PHP_HTTP_MESSAGE_ME(rewind, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(valid, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(current, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(key, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(next, ZEND_ACC_PUBLIC)
+
+       ZEND_MALIAS(HttpMessage, __toString, toString, PHP_HTTP_ARGS(HttpMessage, __toString), ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_MESSAGE_ME(detach, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(prepend, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_ME(reverse, ZEND_ACC_PUBLIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+static zend_object_handlers php_http_message_object_handlers;
+static HashTable php_http_message_object_prophandlers;
+
+typedef void (*php_http_message_object_prophandler_func_t)(php_http_message_object_t *o, zval *v TSRMLS_DC);
+
+typedef struct php_http_message_object_prophandler {
+       php_http_message_object_prophandler_func_t read;
+       php_http_message_object_prophandler_func_t write;
+} php_http_message_object_prophandler_t;
+
+static STATUS php_http_message_object_add_prophandler(const char *prop_str, size_t prop_len, php_http_message_object_prophandler_func_t read, php_http_message_object_prophandler_func_t write) {
+       php_http_message_object_prophandler_t h = { read, write };
+       return zend_hash_add(&php_http_message_object_prophandlers, prop_str, prop_len + 1, (void *) &h, sizeof(h), NULL);
+}
+static int php_http_message_object_has_prophandler(const char *prop_str, size_t prop_len) {
+       return zend_hash_exists(&php_http_message_object_prophandlers, prop_str, prop_len + 1);
+}
+static STATUS php_http_message_object_get_prophandler(const char *prop_str, size_t prop_len, php_http_message_object_prophandler_t **handler) {
+       return zend_hash_find(&php_http_message_object_prophandlers, prop_str, prop_len + 1, (void *) handler);
+}
+static void php_http_message_object_prophandler_get_type(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
+       RETVAL_LONG(obj->message->type);
+}
+static void php_http_message_object_prophandler_set_type(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
+       zval *cpy = php_http_zsep(IS_LONG, value);
+       php_http_message_set_type(obj->message, Z_LVAL_P(cpy));
+       zval_ptr_dtor(&cpy);
+}
+static void php_http_message_object_prophandler_get_request_method(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
+       if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message) && obj->message->http.info.request.method) {
+               RETVAL_STRING(obj->message->http.info.request.method, 1);
+       } else {
+               RETVAL_NULL();
+       }
+}
+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_zsep(IS_STRING, value);
+               STR_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);
+       } 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_zsep(IS_STRING, value);
+               STR_SET(obj->message->http.info.request.url, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)));
+               zval_ptr_dtor(&cpy);
+       }
+}
+static void php_http_message_object_prophandler_get_response_status(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
+       if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message) && obj->message->http.info.response.status) {
+               RETVAL_STRING(obj->message->http.info.response.status, 1);
+       } else {
+               RETVAL_NULL();
+       }
+}
+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_zsep(IS_STRING, value);
+               STR_SET(obj->message->http.info.response.status, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)));
+               zval_ptr_dtor(&cpy);
+       }
+}
+static void php_http_message_object_prophandler_get_response_code(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
+       if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) {
+               RETVAL_LONG(obj->message->http.info.response.code);
+       } else {
+               RETVAL_NULL();
+       }
+}
+static void php_http_message_object_prophandler_set_response_code(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
+       if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) {
+               zval *cpy = php_http_zsep(IS_LONG, value);
+               obj->message->http.info.response.code = Z_LVAL_P(cpy);
+               zval_ptr_dtor(&cpy);
+       }
+}
+static void php_http_message_object_prophandler_get_http_version(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
+       char *version_str;
+       size_t version_len;
+
+       php_http_version_to_string(&obj->message->http.version, &version_str, &version_len, NULL, NULL TSRMLS_CC);
+       RETVAL_STRINGL(version_str, version_len, 0);
+}
+static void php_http_message_object_prophandler_set_http_version(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
+       zval *cpy = php_http_zsep(IS_STRING, value);
+       php_http_version_parse(&obj->message->http.version, Z_STRVAL_P(cpy) TSRMLS_CC);
+       zval_ptr_dtor(&cpy);
+}
+static void php_http_message_object_prophandler_get_headers(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
+       array_init(return_value);
+       zend_hash_copy(Z_ARRVAL_P(return_value), &obj->message->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+}
+static void php_http_message_object_prophandler_set_headers(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
+       zval *cpy = php_http_zsep(IS_ARRAY, value);
+
+       zend_hash_clean(&obj->message->hdrs);
+       zend_hash_copy(&obj->message->hdrs, Z_ARRVAL_P(cpy), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+       zval_ptr_dtor(&cpy);
+}
+static void php_http_message_object_prophandler_get_body(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
+       if (obj->body.handle) {
+               RETVAL_OBJVAL(obj->body, 1);
+       } else {
+               RETVAL_NULL();
+       }
+}
+static void php_http_message_object_prophandler_set_body(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
+       if (Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), php_http_message_body_class_entry TSRMLS_CC)) {
+               if (obj->body.handle) {
+                       zend_objects_store_del_ref_by_handle(obj->body.handle TSRMLS_CC);
+               }
+               Z_OBJ_ADDREF_P(value);
+               obj->body = Z_OBJVAL_P(value);
+       }
+}
+static void php_http_message_object_prophandler_get_parent_message(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) {
+       if (obj->message->parent) {
+               RETVAL_OBJVAL(obj->parent, 1);
+       } else {
+               RETVAL_NULL();
+       }
+}
+static void php_http_message_object_prophandler_set_parent_message(php_http_message_object_t *obj, zval *value TSRMLS_DC) {
+       if (Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), php_http_message_class_entry TSRMLS_CC)) {
+               if (obj->message->parent) {
+                       zend_objects_store_del_ref_by_handle(obj->parent.handle TSRMLS_CC);
+               }
+               Z_OBJ_ADDREF_P(value);
+               obj->parent = Z_OBJVAL_P(value);
+       }
+}
+
+PHP_MINIT_FUNCTION(http_message)
+{
+       PHP_HTTP_REGISTER_CLASS(http, Message, http_message, php_http_object_class_entry, 0);
+       php_http_message_class_entry->create_object = php_http_message_object_new;
+       memcpy(&php_http_message_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_message_object_handlers.clone_obj = php_http_message_object_clone;
+       php_http_message_object_handlers.read_property = php_http_message_object_read_prop;
+       php_http_message_object_handlers.write_property = php_http_message_object_write_prop;
+       php_http_message_object_handlers.get_properties = php_http_message_object_get_props;
+       php_http_message_object_handlers.get_property_ptr_ptr = php_http_message_object_get_prop_ptr;
+
+       zend_class_implements(php_http_message_class_entry TSRMLS_CC, 3, spl_ce_Countable, zend_ce_serializable, zend_ce_iterator);
+
+       zend_hash_init(&php_http_message_object_prophandlers, 9, NULL, NULL, 1);
+       zend_declare_property_long(php_http_message_class_entry, ZEND_STRL("type"), PHP_HTTP_NONE, ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("type"), php_http_message_object_prophandler_get_type, php_http_message_object_prophandler_set_type);
+       zend_declare_property_string(php_http_message_class_entry, ZEND_STRL("body"), "", ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("body"), php_http_message_object_prophandler_get_body, php_http_message_object_prophandler_set_body);
+       zend_declare_property_string(php_http_message_class_entry, ZEND_STRL("requestMethod"), "", ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("requestMethod"), php_http_message_object_prophandler_get_request_method, php_http_message_object_prophandler_set_request_method);
+       zend_declare_property_string(php_http_message_class_entry, ZEND_STRL("requestUrl"), "", ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("requestUrl"), php_http_message_object_prophandler_get_request_url, php_http_message_object_prophandler_set_request_url);
+       zend_declare_property_string(php_http_message_class_entry, ZEND_STRL("responseStatus"), "", ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("responseStatus"), php_http_message_object_prophandler_get_response_status, php_http_message_object_prophandler_set_response_status);
+       zend_declare_property_long(php_http_message_class_entry, ZEND_STRL("responseCode"), 0, ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("responseCode"), php_http_message_object_prophandler_get_response_code, php_http_message_object_prophandler_set_response_code);
+       zend_declare_property_null(php_http_message_class_entry, ZEND_STRL("httpVersion"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("httpVersion"), php_http_message_object_prophandler_get_http_version, php_http_message_object_prophandler_set_http_version);
+       zend_declare_property_null(php_http_message_class_entry, ZEND_STRL("headers"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("headers"), php_http_message_object_prophandler_get_headers, php_http_message_object_prophandler_set_headers);
+       zend_declare_property_null(php_http_message_class_entry, ZEND_STRL("parentMessage"), ZEND_ACC_PROTECTED TSRMLS_CC);
+       php_http_message_object_add_prophandler(ZEND_STRL("parentMessage"), php_http_message_object_prophandler_get_parent_message, php_http_message_object_prophandler_set_parent_message);
+
+       zend_declare_class_constant_long(php_http_message_class_entry, ZEND_STRL("TYPE_NONE"), PHP_HTTP_NONE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_message_class_entry, ZEND_STRL("TYPE_REQUEST"), PHP_HTTP_REQUEST TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_message_class_entry, ZEND_STRL("TYPE_RESPONSE"), PHP_HTTP_RESPONSE TSRMLS_CC);
+
+       return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(http_message)
+{
+       zend_hash_destroy(&php_http_message_object_prophandlers);
+
+       return SUCCESS;
+}
+
+void php_http_message_object_reverse(zval *this_ptr, zval *return_value TSRMLS_DC)
+{
+       int i = 0;
+       php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       /* count */
+       php_http_message_count(i, obj->message);
+
+       if (i > 1) {
+               zval o;
+               zend_object_value *ovalues = NULL;
+               php_http_message_object_t **objects = NULL;
+               int last = i - 1;
+
+               objects = ecalloc(i, sizeof(**objects));
+               ovalues = ecalloc(i, sizeof(*ovalues));
+
+               /* we are the first message */
+               objects[0] = obj;
+               ovalues[0] = getThis()->value.obj;
+
+               /* fetch parents */
+               INIT_PZVAL(&o);
+               o.type = IS_OBJECT;
+               for (i = 1; obj->parent.handle; ++i) {
+                       o.value.obj = obj->parent;
+                       ovalues[i] = o.value.obj;
+                       objects[i] = obj = zend_object_store_get_object(&o TSRMLS_CC);
+               }
+
+               /* reorder parents */
+               for (last = --i; i; --i) {
+                       objects[i]->message->parent = objects[i-1]->message;
+                       objects[i]->parent = ovalues[i-1];
+               }
+               objects[0]->message->parent = NULL;
+               objects[0]->parent.handle = 0;
+               objects[0]->parent.handlers = NULL;
+
+               /* add ref (why?) */
+               Z_OBJ_ADDREF_P(getThis());
+               RETVAL_OBJVAL(ovalues[last], 1);
+
+               efree(objects);
+               efree(ovalues);
+       } else {
+               RETURN_ZVAL(getThis(), 1, 0);
+       }
+}
+
+void php_http_message_object_prepend(zval *this_ptr, zval *prepend, zend_bool top TSRMLS_DC)
+{
+       zval m;
+       php_http_message_t *save_parent_msg = NULL;
+       zend_object_value save_parent_obj = {0, NULL};
+       php_http_message_object_t *obj = zend_object_store_get_object(this_ptr TSRMLS_CC);
+       php_http_message_object_t *prepend_obj = zend_object_store_get_object(prepend TSRMLS_CC);
+
+       INIT_PZVAL(&m);
+       m.type = IS_OBJECT;
+
+       if (!top) {
+               save_parent_obj = obj->parent;
+               save_parent_msg = obj->message->parent;
+       } else {
+               /* iterate to the most parent object */
+               while (obj->parent.handle) {
+                       m.value.obj = obj->parent;
+                       obj = zend_object_store_get_object(&m TSRMLS_CC);
+               }
+       }
+
+       /* prepend */
+       obj->parent = prepend->value.obj;
+       obj->message->parent = prepend_obj->message;
+
+       /* add ref */
+       zend_objects_store_add_ref(prepend TSRMLS_CC);
+       while (prepend_obj->parent.handle) {
+               m.value.obj = prepend_obj->parent;
+               zend_objects_store_add_ref(&m TSRMLS_CC);
+               prepend_obj = zend_object_store_get_object(&m TSRMLS_CC);
+       }
+
+       if (!top) {
+               prepend_obj->parent = save_parent_obj;
+               prepend_obj->message->parent = save_parent_msg;
+       }
+}
+
+zend_object_value php_http_message_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_message_object_new_ex(ce, NULL, NULL TSRMLS_CC);
+}
+
+zend_object_value php_http_message_object_new_ex(zend_class_entry *ce, php_http_message_t *msg, php_http_message_object_t **ptr TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_message_object_t *o;
+
+       o = ecalloc(1, sizeof(php_http_message_object_t));
+       zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       if (ptr) {
+               *ptr = o;
+       }
+
+       if (msg) {
+               o->message = msg;
+               if (msg->parent) {
+                       o->parent = php_http_message_object_new_ex(ce, msg->parent, NULL TSRMLS_CC);
+               }
+               o->body = php_http_message_body_object_new_ex(php_http_message_body_class_entry, php_http_message_body_copy(&msg->body, NULL, 0), NULL TSRMLS_CC);
+       }
+
+       ov.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_message_object_free, NULL TSRMLS_CC);
+       ov.handlers = &php_http_message_object_handlers;
+
+       return ov;
+}
+
+zend_object_value php_http_message_object_clone(zval *this_ptr TSRMLS_DC)
+{
+       zend_object_value new_ov;
+       php_http_message_object_t *new_obj = NULL;
+       php_http_message_object_t *old_obj = zend_object_store_get_object(this_ptr TSRMLS_CC);
+
+       new_ov = php_http_message_object_new_ex(old_obj->zo.ce, php_http_message_copy(old_obj->message, NULL), &new_obj);
+       zend_objects_clone_members(&new_obj->zo, new_ov, &old_obj->zo, Z_OBJ_HANDLE_P(this_ptr) TSRMLS_CC);
+
+       return new_ov;
+}
+
+void php_http_message_object_free(void *object TSRMLS_DC)
+{
+       php_http_message_object_t *o = (php_http_message_object_t *) object;
+
+       if (o->iterator) {
+               zval_ptr_dtor(&o->iterator);
+               o->iterator = NULL;
+       }
+       if (o->message) {
+               /* do NOT free recursivly */
+               php_http_message_dtor(o->message);
+               efree(o->message);
+               o->message = NULL;
+       }
+       if (o->parent.handle) {
+               zend_objects_store_del_ref_by_handle(o->parent.handle TSRMLS_CC);
+       }
+       if (o->body.handle) {
+               zend_objects_store_del_ref_by_handle(o->body.handle TSRMLS_CC);
+       }
+       zend_object_std_dtor((zend_object *) o TSRMLS_CC);
+       efree(o);
+}
+
+
+static zval **php_http_message_object_get_prop_ptr(zval *object, zval *member, const zend_literal *literal_key TSRMLS_DC) {
+       php_http_message_object_prophandler_t *handler;
+       zval *copy = php_http_zsep(IS_STRING, member);
+
+       if (SUCCESS == php_http_message_object_get_prophandler(Z_STRVAL_P(copy), Z_STRLEN_P(copy), &handler)) {
+               zval_ptr_dtor(&copy);
+               return &php_http_property_proxy_init(NULL, object, member TSRMLS_CC)->myself;
+       }
+       zval_ptr_dtor(&copy);
+
+       return zend_get_std_object_handlers()->get_property_ptr_ptr(object, member, literal_key TSRMLS_CC);
+}
+
+static zval *php_http_message_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC)
+{
+       php_http_message_object_t *obj = zend_object_store_get_object(object TSRMLS_CC);
+       php_http_message_object_prophandler_t *handler;
+       zval *return_value, *copy = php_http_zsep(IS_STRING, member);
+
+       if (SUCCESS == php_http_message_object_get_prophandler(Z_STRVAL_P(copy), Z_STRLEN_P(copy), &handler)) {
+               if (type == BP_VAR_W) {
+                       zval_ptr_dtor(&copy);
+                       zend_error(E_ERROR, "Cannot access HttpMessage properties by reference or array key/index");
+                       return NULL;
+               }
+
+               ALLOC_ZVAL(return_value);
+               Z_SET_REFCOUNT_P(return_value, 0);
+               Z_UNSET_ISREF_P(return_value);
+
+               handler->read(obj, return_value TSRMLS_CC);
+
+       } else {
+               return_value = zend_get_std_object_handlers()->read_property(object, member, type, literal_key TSRMLS_CC);
+       }
+
+       zval_ptr_dtor(&copy);
+
+       return return_value;
+}
+
+static void php_http_message_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC)
+{
+       php_http_message_object_t *obj = zend_object_store_get_object(object TSRMLS_CC);
+       php_http_message_object_prophandler_t *handler;
+       zval *copy = php_http_zsep(IS_STRING, member);
+
+       if (SUCCESS == php_http_message_object_get_prophandler(Z_STRVAL_P(copy), Z_STRLEN_P(copy), &handler)) {
+               handler->write(obj, value TSRMLS_CC);
+       } else {
+               zend_get_std_object_handlers()->write_property(object, member, value, literal_key TSRMLS_CC);
+       }
+
+       zval_ptr_dtor(&copy);
+}
+
+
+static HashTable *php_http_message_object_get_props(zval *object TSRMLS_DC)
+{
+       zval *headers;
+       php_http_message_object_t *obj = zend_object_store_get_object(object TSRMLS_CC);
+       php_http_message_t *msg = obj->message;
+       HashTable *props = zend_get_std_object_handlers()->get_properties(object TSRMLS_CC);
+       zval array, *parent, *body;
+       char *version;
+
+       INIT_PZVAL_ARRAY(&array, props);
+
+#define ASSOC_PROP(array, ptype, name, val) \
+       { \
+               char *m_prop_name; \
+               int m_prop_len; \
+               zend_mangle_property_name(&m_prop_name, &m_prop_len, "*", 1, name, lenof(name), 0); \
+               add_assoc_ ##ptype## _ex(&array, m_prop_name, sizeof(name)+3, val); \
+               efree(m_prop_name); \
+       }
+#define ASSOC_STRING(array, name, val) ASSOC_STRINGL(array, name, val, strlen(val))
+#define ASSOC_STRINGL(array, name, val, len) ASSOC_STRINGL_EX(array, name, val, len, 1)
+#define ASSOC_STRINGL_EX(array, name, val, len, cpy) \
+       { \
+               char *m_prop_name; \
+               int m_prop_len; \
+               zend_mangle_property_name(&m_prop_name, &m_prop_len, "*", 1, name, lenof(name), 0); \
+               add_assoc_stringl_ex(&array, m_prop_name, sizeof(name)+3, val, len, cpy); \
+               efree(m_prop_name); \
+       }
+
+       ASSOC_PROP(array, long, "type", msg->type);
+       ASSOC_STRINGL_EX(array, "httpVersion", version, spprintf(&version, 0, "%u.%u", msg->http.version.major, msg->http.version.minor), 0);
+
+       switch (msg->type) {
+               case PHP_HTTP_REQUEST:
+                       ASSOC_PROP(array, long, "responseCode", 0);
+                       ASSOC_STRINGL(array, "responseStatus", "", 0);
+                       ASSOC_STRING(array, "requestMethod", STR_PTR(msg->http.info.request.method));
+                       ASSOC_STRING(array, "requestUrl", STR_PTR(msg->http.info.request.url));
+                       break;
+
+               case PHP_HTTP_RESPONSE:
+                       ASSOC_PROP(array, long, "responseCode", msg->http.info.response.code);
+                       ASSOC_STRING(array, "responseStatus", STR_PTR(msg->http.info.response.status));
+                       ASSOC_STRINGL(array, "requestMethod", "", 0);
+                       ASSOC_STRINGL(array, "requestUrl", "", 0);
+                       break;
+
+               case PHP_HTTP_NONE:
+               default:
+                       ASSOC_PROP(array, long, "responseCode", 0);
+                       ASSOC_STRINGL(array, "responseStatus", "", 0);
+                       ASSOC_STRINGL(array, "requestMethod", "", 0);
+                       ASSOC_STRINGL(array, "requestUrl", "", 0);
+                       break;
+       }
+
+       MAKE_STD_ZVAL(headers);
+       array_init(headers);
+       zend_hash_copy(Z_ARRVAL_P(headers), &msg->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+       ASSOC_PROP(array, zval, "headers", headers);
+
+       MAKE_STD_ZVAL(body);
+       if (!obj->body.handle) {
+               php_http_new(&obj->body, php_http_message_body_class_entry, (php_http_new_t) php_http_message_body_object_new_ex, NULL, (void *) php_http_message_body_copy(&obj->message->body, NULL, 0), NULL TSRMLS_CC);
+       }
+       ZVAL_OBJVAL(body, obj->body, 1);
+       ASSOC_PROP(array, zval, "body", body);
+
+       MAKE_STD_ZVAL(parent);
+       if (msg->parent) {
+               ZVAL_OBJVAL(parent, obj->parent, 1);
+       } else {
+               ZVAL_NULL(parent);
+       }
+       ASSOC_PROP(array, zval, "parentMessage", parent);
+
+       return props;
+}
+
+/* PHP */
+
+PHP_METHOD(HttpMessage, __construct)
+{
+       int length = 0;
+       char *message = NULL;
+       php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &message, &length) && message && length) {
+                       php_http_message_t *msg = obj->message;
+
+                       php_http_message_dtor(msg);
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) {
+                               if ((obj->message = php_http_message_parse(msg, message, length))) {
+                                       if (obj->message->parent) {
+                                               obj->parent = php_http_message_object_new_ex(Z_OBJCE_P(getThis()), obj->message->parent, NULL TSRMLS_CC);
+                                       }
+                               } else {
+                                       obj->message = php_http_message_init(msg, 0 TSRMLS_CC);
+                               }
+                       } end_error_handling();
+               }
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+       } end_error_handling();
+
+}
+
+PHP_METHOD(HttpMessage, getBody)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                       if (!obj->message) {
+                               obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+                       }
+
+                       if (obj->body.handle || SUCCESS == php_http_new(&obj->body, php_http_message_body_class_entry, (php_http_new_t) php_http_message_body_object_new_ex, NULL, (void *) php_http_message_body_copy(&obj->message->body, NULL, 0), NULL TSRMLS_CC)) {
+                               RETVAL_OBJVAL(obj->body, 1);
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpMessage, setBody)
+{
+       zval *zbody;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &zbody, php_http_message_body_class_entry)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               php_http_message_body_object_t *body_obj = zend_object_store_get_object(zbody TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               php_http_message_body_dtor(&obj->message->body);
+               php_http_message_body_init(&obj->message->body, php_http_message_body_stream(body_obj->body));
+               Z_OBJ_ADDREF_P(zbody);
+               obj->body = Z_OBJVAL_P(zbody);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getHeader)
+{
+       char *header_str;
+       int header_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &header_str, &header_len)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               zval *header;
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               if ((header = php_http_message_header(obj->message, header_str, header_len, 0))) {
+                       RETURN_ZVAL(header, 1, 1);
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getHeaders)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               array_init(return_value);
+               array_copy(&obj->message->hdrs, Z_ARRVAL_P(return_value));
+       }
+}
+
+PHP_METHOD(HttpMessage, setHeader)
+{
+       zval *zvalue = NULL;
+       char *name_str;
+       int name_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z!", &name_str, &name_len, &zvalue)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               char *name = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               if (!zvalue) {
+                       RETVAL_SUCCESS(zend_hash_del(&obj->message->hdrs, name, name_len + 1));
+               } else {
+                       Z_ADDREF_P(zvalue);
+                       RETVAL_SUCCESS(zend_hash_update(&obj->message->hdrs, name, name_len + 1, &zvalue, sizeof(void *), NULL));
+               }
+               efree(name);
+               return;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, setHeaders)
+{
+       zval *new_headers = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/!", &new_headers)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               zend_hash_clean(&obj->message->hdrs);
+               if (new_headers) {
+                       array_join(Z_ARRVAL_P(new_headers), &obj->message->hdrs, 0, ARRAY_JOIN_PRETTIFY|ARRAY_JOIN_STRONLY);
+               }
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, addHeader)
+{
+       zval *zvalue;
+       char *name_str;
+       int name_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name_str, &name_len, &zvalue)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               char *name = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1);
+               zval *header;
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               Z_ADDREF_P(zvalue);
+               if ((header = php_http_message_header(obj->message, name, name_len, 0))) {
+                       convert_to_array(header);
+                       RETVAL_SUCCESS(zend_hash_next_index_insert(Z_ARRVAL_P(header), &zvalue, sizeof(void *), NULL));
+               } else {
+                       RETVAL_SUCCESS(zend_hash_update(&obj->message->hdrs, name, name_len + 1, &zvalue, sizeof(void *), NULL));
+               }
+               efree(name);
+               return;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, addHeaders)
+{
+       zval *new_headers;
+       zend_bool append = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|b", &new_headers, &append)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               array_join(Z_ARRVAL_P(new_headers), &obj->message->hdrs, append, ARRAY_JOIN_STRONLY|ARRAY_JOIN_PRETTIFY);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getType)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               RETURN_LONG(obj->message->type);
+       }
+}
+
+PHP_METHOD(HttpMessage, setType)
+{
+       long type;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &type)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               php_http_message_set_type(obj->message, type);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getInfo)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               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, ""));
+                               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, ""));
+                               break;
+                       default:
+                               RETURN_NULL();
+                               break;
+               }
+               Z_TYPE_P(return_value) = IS_STRING;
+               return;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, setInfo)
+{
+       char *str;
+       int len;
+       php_http_info_t inf;
+
+       if (    SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len)
+       &&              php_http_info_parse(&inf, str TSRMLS_CC)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               php_http_message_set_info(obj->message, &inf);
+               php_http_info_dtor(&inf);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getHttpVersion)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               char *str;
+               size_t len;
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               php_http_version_to_string(&obj->message->http.version, &str, &len, NULL, NULL TSRMLS_CC);
+               RETURN_STRINGL(str, len, 0);
+       }
+
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, setHttpVersion)
+{
+       char *v_str;
+       int v_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &v_str, &v_len)) {
+               php_http_version_t version;
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               if (php_http_version_parse(&version, v_str TSRMLS_CC)) {
+                       obj->message->http.version = version;
+                       RETURN_TRUE;
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getResponseCode)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               PHP_HTTP_MESSAGE_TYPE_CHECK(RESPONSE, obj->message, RETURN_FALSE);
+               RETURN_LONG(obj->message->http.info.response.code);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, setResponseCode)
+{
+       long code;
+       zend_bool strict = 1;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|b", &code, &strict)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               PHP_HTTP_MESSAGE_TYPE_CHECK(RESPONSE, obj->message, RETURN_FALSE);
+               if (strict && (code < 100 || code > 599)) {
+                       php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Invalid response code (100-599): %ld", code);
+                       RETURN_FALSE;
+               }
+
+               obj->message->http.info.response.code = code;
+               RETURN_TRUE;
+       }
+
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getResponseStatus)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               PHP_HTTP_MESSAGE_TYPE_CHECK(RESPONSE, obj->message, RETURN_FALSE);
+               if (obj->message->http.info.response.status) {
+                       RETURN_STRING(obj->message->http.info.response.status, 1);
+               } else {
+                       RETURN_EMPTY_STRING();
+               }
+       }
+
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, setResponseStatus)
+{
+       char *status;
+       int status_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &status, &status_len)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               PHP_HTTP_MESSAGE_TYPE_CHECK(RESPONSE, obj->message, RETURN_FALSE);
+               STR_SET(obj->message->http.info.response.status, estrndup(status, status_len));
+               RETURN_TRUE;
+       }
+
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getRequestMethod)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               PHP_HTTP_MESSAGE_TYPE_CHECK(REQUEST, obj->message, RETURN_FALSE);
+               if (obj->message->http.info.request.method) {
+                       RETURN_STRING(obj->message->http.info.request.method, 1);
+               } else {
+                       RETURN_EMPTY_STRING();
+               }
+       }
+
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, setRequestMethod)
+{
+       char *method;
+       int method_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &method, &method_len)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               PHP_HTTP_MESSAGE_TYPE_CHECK(REQUEST, obj->message, RETURN_FALSE);
+               if (method_len < 1) {
+                       php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Cannot set HttpMessage::requestMethod to an empty string");
+                       RETURN_FALSE;
+               }
+
+               STR_SET(obj->message->http.info.request.method, estrndup(method, method_len));
+               RETURN_TRUE;
+       }
+
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, getRequestUrl)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               PHP_HTTP_MESSAGE_TYPE_CHECK(REQUEST, obj->message, RETURN_FALSE);
+               if (obj->message->http.info.request.url) {
+                       RETURN_STRING(obj->message->http.info.request.url, 1);
+               } else {
+                       RETURN_EMPTY_STRING();
+               }
+       }
+
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, setRequestUrl)
+{
+       char *url_str;
+       int url_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &url_str, &url_len)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               PHP_HTTP_MESSAGE_TYPE_CHECK(REQUEST, obj->message, RETURN_FALSE);
+               if (url_len < 1) {
+                       php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Cannot set HttpMessage::requestUrl to an empty string");
+                       RETURN_FALSE;
+               }
+               STR_SET(obj->message->http.info.request.url, estrndup(url_str, url_len));
+               RETURN_TRUE;
+       }
+
+       RETURN_FALSE;
+}
+
+
+PHP_METHOD(HttpMessage, getParentMessage)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                       if (!obj->message) {
+                               obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+                       }
+
+                       if (obj->message->parent) {
+                               RETVAL_OBJVAL(obj->parent, 1);
+                       } else {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "HttpMessage does not have a parent message");
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpMessage, toString)
+{
+       zend_bool include_parent = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &include_parent)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               char *string;
+               size_t length;
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               if (include_parent) {
+                       php_http_message_serialize(obj->message, &string, &length);
+               } else {
+                       php_http_message_to_string(obj->message, &string, &length);
+               }
+               if (string) {
+                       RETURN_STRINGL(string, length, 0);
+               }
+       }
+       RETURN_EMPTY_STRING();
+}
+
+PHP_METHOD(HttpMessage, serialize)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               char *string;
+               size_t length;
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               php_http_message_serialize(obj->message, &string, &length);
+               RETURN_STRINGL(string, length, 0);
+       }
+       RETURN_EMPTY_STRING();
+}
+
+PHP_METHOD(HttpMessage, unserialize)
+{
+       int length;
+       char *serialized;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &serialized, &length)) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               php_http_message_t *msg;
+
+               if (obj->message) {
+                       php_http_message_dtor(obj->message);
+                       efree(obj->message);
+               }
+               if ((msg = php_http_message_parse(NULL, serialized, (size_t) length TSRMLS_CC))) {
+                       obj->message = msg;
+               } else {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+                       php_http_error(HE_ERROR, PHP_HTTP_E_RUNTIME, "Could not unserialize HttpMessage");
+               }
+       }
+}
+
+PHP_METHOD(HttpMessage, detach)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                       if (!obj->message) {
+                               obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+                       }
+
+                       RETVAL_OBJVAL(php_http_message_object_new_ex(obj->zo.ce, php_http_message_copy(obj->message, NULL), NULL TSRMLS_CC), 0);
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpMessage, prepend)
+{
+       zval *prepend;
+       zend_bool top = 1;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|b", &prepend, php_http_message_class_entry, &top)) {
+               php_http_message_t *msg[2];
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               php_http_message_object_t *prepend_obj = zend_object_store_get_object(prepend TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+               if (!prepend_obj->message) {
+                       prepend_obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               /* safety check */
+               for (msg[0] = obj->message; msg[0]; msg[0] = msg[0]->parent) {
+                       for (msg[1] = prepend_obj->message; msg[1]; msg[1] = msg[1]->parent) {
+                               if (msg[0] == msg[1]) {
+                                       php_http_error(HE_THROW, PHP_HTTP_E_INVALID_PARAM, "Cannot prepend a message located within the same message chain");
+                                       return;
+                               }
+                       }
+               }
+
+               php_http_message_object_prepend(getThis(), prepend, top TSRMLS_CC);
+       }
+}
+
+PHP_METHOD(HttpMessage, reverse)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_reverse(getThis(), return_value TSRMLS_CC);
+       }
+}
+
+PHP_METHOD(HttpMessage, count)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               long i = 0;
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (!obj->message) {
+                       obj->message = php_http_message_init(NULL, 0 TSRMLS_CC);
+               }
+
+               php_http_message_count(i, obj->message);
+               RETURN_LONG(i);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessage, rewind)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               zval *zobj = getThis();
+               php_http_message_object_t *obj = zend_object_store_get_object(zobj TSRMLS_CC);
+
+               if (obj->iterator) {
+                       zval_ptr_dtor(&obj->iterator);
+               }
+               Z_ADDREF_P(zobj);
+               obj->iterator = zobj;
+       }
+}
+
+PHP_METHOD(HttpMessage, valid)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_BOOL(obj->iterator != NULL);
+       }
+}
+
+PHP_METHOD(HttpMessage, next)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->iterator) {
+                       php_http_message_object_t *itr = zend_object_store_get_object(obj->iterator TSRMLS_CC);
+
+                       if (itr && itr->parent.handle) {
+                               zval *old = obj->iterator;
+                               MAKE_STD_ZVAL(obj->iterator);
+                               ZVAL_OBJVAL(obj->iterator, itr->parent, 1);
+                               zval_ptr_dtor(&old);
+                       } else {
+                               zval_ptr_dtor(&obj->iterator);
+                               obj->iterator = NULL;
+                       }
+               }
+       }
+}
+
+PHP_METHOD(HttpMessage, key)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_LONG(obj->iterator ? obj->iterator->value.obj.handle:0);
+       }
+}
+
+PHP_METHOD(HttpMessage, current)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->iterator) {
+                       RETURN_ZVAL(obj->iterator, 1, 0);
+               }
+       }
+}
+
+/*
+ * 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_message.h b/php_http_message.h
new file mode 100644 (file)
index 0000000..47db451
--- /dev/null
@@ -0,0 +1,147 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_message.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_MESSAGE_H
+#define PHP_HTTP_MESSAGE_H
+
+/* required minimum length of an HTTP message "HTTP/1.1" */
+#define PHP_HTTP_MESSAGE_MIN_SIZE 8
+#define PHP_HTTP_MESSAGE_TYPE(TYPE, msg) ((msg) && ((msg)->type == PHP_HTTP_ ##TYPE))
+#define PHP_HTTP_MESSAGE_TYPE_CHECK(type, msg, action) \
+               if (!PHP_HTTP_MESSAGE_TYPE(type, (msg))) { \
+                       php_http_error(HE_NOTICE, PHP_HTTP_E_MESSAGE_TYPE, "HttpMessage is not of type "#type); \
+                       action; \
+               }
+
+typedef php_http_info_type_t php_http_message_type_t;
+typedef struct php_http_message php_http_message_t;
+
+struct php_http_message {
+       PHP_HTTP_INFO_IMPL(http, type)
+       HashTable hdrs;
+       php_http_message_body_t body;
+       php_http_message_t *parent;
+       void *opaque;
+#ifdef ZTS
+       void ***ts;
+#endif
+};
+
+PHP_HTTP_API zend_bool php_http_message_info_callback(php_http_message_t **message, HashTable **headers, php_http_info_t *info TSRMLS_DC);
+
+PHP_HTTP_API php_http_message_t *php_http_message_init(php_http_message_t *m, php_http_message_type_t t TSRMLS_DC);
+PHP_HTTP_API php_http_message_t *php_http_message_init_env(php_http_message_t *m, php_http_message_type_t t TSRMLS_DC);
+PHP_HTTP_API php_http_message_t *php_http_message_copy(php_http_message_t *from, php_http_message_t *to);
+PHP_HTTP_API void php_http_message_dtor(php_http_message_t *message);
+PHP_HTTP_API void php_http_message_free(php_http_message_t **message);
+
+PHP_HTTP_API void php_http_message_set_type(php_http_message_t *m, php_http_message_type_t t);
+PHP_HTTP_API void php_http_message_set_info(php_http_message_t *message, php_http_info_t *info);
+
+PHP_HTTP_API zval *php_http_message_header(php_http_message_t *msg, char *key_str, size_t key_len, int join);
+
+PHP_HTTP_API void php_http_message_to_string(php_http_message_t *msg, char **string, size_t *length);
+PHP_HTTP_API void php_http_message_to_struct(php_http_message_t *msg, zval *strct);
+PHP_HTTP_API void php_http_message_to_callback(php_http_message_t *msg, php_http_pass_callback_t cb, void *cb_arg);
+
+PHP_HTTP_API void php_http_message_serialize(php_http_message_t *message, char **string, size_t *length);
+PHP_HTTP_API php_http_message_t *php_http_message_reverse(php_http_message_t *msg);
+PHP_HTTP_API php_http_message_t *php_http_message_interconnect(php_http_message_t *m1, php_http_message_t *m2);
+
+#define php_http_message_count(c, m) \
+{ \
+       php_http_message_t *__tmp_msg = (m); \
+       for (c = 0; __tmp_msg; __tmp_msg = __tmp_msg->parent, ++(c)); \
+}
+
+PHP_HTTP_API php_http_message_t *php_http_message_parse(php_http_message_t *msg, const char *str, size_t len TSRMLS_DC);
+
+/* PHP */
+
+typedef struct php_http_message_object {
+       zend_object zo;
+       php_http_message_t *message;
+       zend_object_value parent, body;
+       zval *iterator;
+} php_http_message_object_t;
+
+extern zend_class_entry *php_http_message_class_entry;
+extern zend_function_entry http_message_method_entry[];
+
+extern PHP_MINIT_FUNCTION(http_message);
+extern PHP_MSHUTDOWN_FUNCTION(http_message);
+
+extern void php_http_message_object_prepend(zval *this_ptr, zval *prepend, zend_bool top /* = 1 */ TSRMLS_DC);
+extern void php_http_message_object_reverse(zval *this_ptr, zval *return_value TSRMLS_DC);
+
+extern zend_object_value php_http_message_object_new(zend_class_entry *ce TSRMLS_DC);
+extern zend_object_value php_http_message_object_new_ex(zend_class_entry *ce, php_http_message_t *msg, php_http_message_object_t **ptr TSRMLS_DC);
+extern zend_object_value php_http_message_object_clone(zval *object TSRMLS_DC);
+extern void php_http_message_object_free(void *object TSRMLS_DC);
+
+PHP_METHOD(HttpMessage, __construct);
+PHP_METHOD(HttpMessage, getBody);
+PHP_METHOD(HttpMessage, setBody);
+PHP_METHOD(HttpMessage, getHeader);
+PHP_METHOD(HttpMessage, setHeader);
+PHP_METHOD(HttpMessage, addHeader);
+PHP_METHOD(HttpMessage, getHeaders);
+PHP_METHOD(HttpMessage, setHeaders);
+PHP_METHOD(HttpMessage, addHeaders);
+PHP_METHOD(HttpMessage, getType);
+PHP_METHOD(HttpMessage, setType);
+PHP_METHOD(HttpMessage, getInfo);
+PHP_METHOD(HttpMessage, setInfo);
+PHP_METHOD(HttpMessage, getResponseCode);
+PHP_METHOD(HttpMessage, setResponseCode);
+PHP_METHOD(HttpMessage, getResponseStatus);
+PHP_METHOD(HttpMessage, setResponseStatus);
+PHP_METHOD(HttpMessage, getRequestMethod);
+PHP_METHOD(HttpMessage, setRequestMethod);
+PHP_METHOD(HttpMessage, getRequestUrl);
+PHP_METHOD(HttpMessage, setRequestUrl);
+PHP_METHOD(HttpMessage, getHttpVersion);
+PHP_METHOD(HttpMessage, setHttpVersion);
+PHP_METHOD(HttpMessage, guessContentType);
+PHP_METHOD(HttpMessage, getParentMessage);
+PHP_METHOD(HttpMessage, send);
+PHP_METHOD(HttpMessage, toString);
+PHP_METHOD(HttpMessage, toMessageTypeObject);
+
+PHP_METHOD(HttpMessage, count);
+PHP_METHOD(HttpMessage, serialize);
+PHP_METHOD(HttpMessage, unserialize);
+PHP_METHOD(HttpMessage, rewind);
+PHP_METHOD(HttpMessage, valid);
+PHP_METHOD(HttpMessage, current);
+PHP_METHOD(HttpMessage, key);
+PHP_METHOD(HttpMessage, next);
+
+PHP_METHOD(HttpMessage, factory);
+
+PHP_METHOD(HttpMessage, detach);
+PHP_METHOD(HttpMessage, prepend);
+PHP_METHOD(HttpMessage, reverse);
+
+#endif
+
+/*
+ * 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_message_body.c b/php_http_message_body.c
new file mode 100644 (file)
index 0000000..969f860
--- /dev/null
@@ -0,0 +1,687 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_message_body.c 292841 2009-12-31 08:48:57Z mike $ */
+
+#include "php_http.h"
+
+typedef struct curl_httppost *post_data[2];
+
+static inline STATUS add_field(php_http_message_body_t *body, const char *name, const char *value_str, size_t value_len);
+static inline STATUS add_file(php_http_message_body_t *body, const char *name, const char *path, const char *ctype);
+static STATUS recursive_fields(post_data http_post_data, HashTable *fields, const char *prefix TSRMLS_DC);
+static STATUS recursive_files(post_data http_post_data, HashTable *files, const char *prefix TSRMLS_DC);
+
+PHP_HTTP_API php_http_message_body_t *php_http_message_body_init(php_http_message_body_t *body, php_stream *stream TSRMLS_DC)
+{
+       if (!body) {
+               body = emalloc(sizeof(php_http_message_body_t));
+       }
+       
+       if (stream) {
+               php_stream_auto_cleanup(stream);
+               body->stream_id = php_stream_get_resource_id(stream);
+               zend_list_addref(body->stream_id);
+       } else {
+               stream = php_stream_temp_new();
+               php_stream_auto_cleanup(stream);
+               body->stream_id = php_stream_get_resource_id(stream);
+       }
+       TSRMLS_SET_CTX(body->ts);
+
+       return body;
+}
+
+PHP_HTTP_API php_http_message_body_t *php_http_message_body_copy(php_http_message_body_t *from, php_http_message_body_t *to, zend_bool dup_internal_stream_and_contents)
+{
+       if (!from) {
+               return NULL;
+       } else {
+               TSRMLS_FETCH_FROM_CTX(from->ts);
+               
+               if (dup_internal_stream_and_contents) {
+                       to = php_http_message_body_init(to, NULL TSRMLS_CC);
+                       php_http_message_body_to_stream(from, php_http_message_body_stream(to), 0, 0);
+               } else {
+                       to = php_http_message_body_init(to, php_http_message_body_stream(from) TSRMLS_CC);
+               }
+
+               return to;
+       }
+}
+
+PHP_HTTP_API void php_http_message_body_dtor(php_http_message_body_t *body)
+{
+       if (body) {
+               /* NO FIXME: shows leakinfo in DEBUG mode */
+               zend_list_delete(body->stream_id);
+       }
+}
+
+PHP_HTTP_API void php_http_message_body_free(php_http_message_body_t **body)
+{
+       if (*body) {
+               php_http_message_body_dtor(*body);
+               efree(*body);
+               *body = NULL;
+       }
+}
+
+PHP_HTTP_API php_stream_statbuf *php_http_message_body_stat(php_http_message_body_t *body)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       php_stream_stat(php_http_message_body_stream(body), &body->ssb);
+       return &body->ssb;
+}
+
+PHP_HTTP_API char *php_http_message_body_etag(php_http_message_body_t *body)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       php_stream_statbuf *ssb = php_http_message_body_stat(body);
+
+       /* real file or temp buffer ? */
+       if (body->ssb.sb.st_mtime) {
+               char *etag;
+
+               spprintf(&etag, 0, "%lx-%lx-%lx", ssb->sb.st_ino, ssb->sb.st_mtime, ssb->sb.st_size);
+               return etag;
+       } else {
+               void *ctx = php_http_etag_init(TSRMLS_C);
+
+               php_http_message_body_to_callback(body, php_http_etag_update, ctx, 0, 0);
+               return php_http_etag_finish(ctx TSRMLS_CC);
+       }
+}
+
+PHP_HTTP_API void php_http_message_body_to_string(php_http_message_body_t *body, char **buf, size_t *len, off_t offset, size_t forlen)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       php_stream *s = php_http_message_body_stream(body);
+
+       php_stream_seek(s, offset, SEEK_SET);
+       if (!forlen) {
+               forlen = -1;
+       }
+       *len = php_stream_copy_to_mem(s, buf, forlen, 0);
+}
+
+PHP_HTTP_API void php_http_message_body_to_stream(php_http_message_body_t *body, php_stream *dst, off_t offset, size_t forlen)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       php_stream *s = php_http_message_body_stream(body);
+
+       php_stream_seek(s, offset, SEEK_SET);
+       if (!forlen) {
+               forlen = -1;
+       }
+       php_stream_copy_to_stream_ex(s, dst, forlen, NULL);
+}
+
+PHP_HTTP_API void php_http_message_body_to_callback(php_http_message_body_t *body, php_http_pass_callback_t cb, void *cb_arg, off_t offset, size_t forlen)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       php_stream *s = php_http_message_body_stream(body);
+
+       php_stream_seek(s, offset, SEEK_SET);
+
+       if (!forlen) {
+               forlen = -1;
+       }
+       while (!php_stream_eof(s)) {
+               char buf[0x1000];
+               size_t read = php_stream_read(s, buf, MIN(forlen, sizeof(buf)));
+
+               if (read) {
+                       cb(cb_arg, buf, read);
+               }
+
+               if (read < MIN(forlen, sizeof(buf))) {
+                       break;
+               }
+
+               if (forlen && !(forlen -= read)) {
+                       break;
+               }
+       }
+}
+
+PHP_HTTP_API STATUS php_http_message_body_to_callback_in_chunks(php_http_message_body_t *body, php_http_pass_callback_t cb, void *cb_arg, HashTable *chunks)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       php_stream *s = php_http_message_body_stream(body);
+       HashPosition pos;
+       zval **chunk;
+
+       FOREACH_HASH_VAL(pos, chunks, chunk) {
+               zval **begin, **end;
+
+               if (IS_ARRAY == Z_TYPE_PP(chunk)
+               &&      SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(chunk), 0, (void *) &begin)
+               &&      IS_LONG == Z_TYPE_PP(begin)
+               &&      SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(chunk), 1, (void *) &end)
+               &&      IS_LONG == Z_TYPE_PP(end)
+               ) {
+                       if (SUCCESS != php_stream_seek(s, Z_LVAL_PP(begin), SEEK_SET)) {
+                               return FAILURE;
+                       } else {
+                               long length = Z_LVAL_PP(end) - Z_LVAL_PP(begin) + 1;
+
+                               while (length > 0 && !php_stream_eof(s)) {
+                                       char buf[0x1000];
+                                       size_t read = php_stream_read(s, buf, MIN(length, sizeof(buf)));
+
+                                       if (read) {
+                                               cb(cb_arg, buf, read);
+                                       }
+
+                                       if (read < MIN(length, sizeof(buf))) {
+                                               break;
+                                       }
+
+                                       length -= read;
+                               }
+                       }
+               }
+       }
+
+       return SUCCESS;
+}
+
+PHP_HTTP_API size_t php_http_message_body_append(php_http_message_body_t *body, const char *buf, size_t len)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       php_stream *s = php_http_message_body_stream(body);
+
+       php_stream_seek(s, 0, SEEK_END);
+       return php_stream_write(s, buf, len);
+}
+
+PHP_HTTP_API STATUS php_http_message_body_add(php_http_message_body_t *body, HashTable *fields, HashTable *files)
+{
+       post_data http_post_data = {NULL, NULL};
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+
+       if (fields && SUCCESS != recursive_fields(http_post_data, fields, NULL TSRMLS_CC)) {
+               return FAILURE;
+       }
+       if (files && (zend_hash_num_elements(files) > 0) && (SUCCESS != recursive_files(http_post_data, files, NULL TSRMLS_CC))) {
+               return FAILURE;
+       }
+       if (CURLE_OK != curl_formget(http_post_data[0], body, (curl_formget_callback) php_http_message_body_append)) {
+               return FAILURE;
+       }
+       return SUCCESS;
+}
+
+PHP_HTTP_API STATUS php_http_message_body_add_field(php_http_message_body_t *body, const char *name, const char *value_str, size_t value_len)
+{
+       return add_field(body, name, value_str, value_len);
+}
+
+PHP_HTTP_API STATUS php_http_message_body_add_file(php_http_message_body_t *body, const char *name, const char *path, const char *ctype)
+{
+       return add_file(body, name, path, ctype);
+}
+
+static inline char *format_key(uint type, char *str, ulong num, const char *prefix, int numeric_key_for_empty_prefix) {
+       char *new_key = NULL;
+       
+       if (prefix && *prefix) {
+               if (type == HASH_KEY_IS_STRING) {
+                       spprintf(&new_key, 0, "%s[%s]", prefix, str);
+               } else {
+                       spprintf(&new_key, 0, "%s[%lu]", prefix, num);
+               }
+       } else if (type == HASH_KEY_IS_STRING) {
+               new_key = estrdup(str);
+       } else if (numeric_key_for_empty_prefix) {
+               spprintf(&new_key, 0, "%lu", num);
+       }
+       
+       return new_key;
+}
+
+static inline STATUS add_field(php_http_message_body_t *body, const char *name, const char *value_str, size_t value_len)
+{
+       post_data http_post_data = {NULL, NULL};
+       CURLcode err;
+
+       err = curl_formadd(&http_post_data[0], &http_post_data[1],
+               CURLFORM_COPYNAME, name,
+               CURLFORM_COPYCONTENTS, value_str,
+               CURLFORM_CONTENTSLENGTH, (long) value_len,
+               CURLFORM_END
+       );
+
+       if (CURLE_OK != err) {
+               return FAILURE;
+       }
+
+       err = curl_formget(http_post_data[0], body, (curl_formget_callback) php_http_message_body_append);
+
+       if (CURLE_OK != err) {
+               curl_formfree(http_post_data[0]);
+               return FAILURE;
+       }
+
+       curl_formfree(http_post_data[0]);
+       return SUCCESS;
+}
+
+static inline STATUS add_file(php_http_message_body_t *body, const char *name, const char *path, const char *ctype)
+{
+       post_data http_post_data = {NULL, NULL};
+       CURLcode err;
+
+       err = curl_formadd(&http_post_data[0], &http_post_data[1],
+               CURLFORM_COPYNAME, name,
+               CURLFORM_FILE, path,
+               CURLFORM_CONTENTTYPE, ctype ? ctype : "application/octet-stream",
+               CURLFORM_END
+       );
+
+       if (CURLE_OK != err) {
+               return FAILURE;
+       }
+
+       err = curl_formget(http_post_data[0], body, (curl_formget_callback) php_http_message_body_append);
+
+       if (CURLE_OK != err) {
+               curl_formfree(http_post_data[0]);
+               return FAILURE;
+       }
+
+       curl_formfree(http_post_data[0]);
+       return SUCCESS;
+}
+
+static STATUS recursive_fields(post_data http_post_data, HashTable *fields, const char *prefix TSRMLS_DC) {
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       zval **data_ptr;
+       HashPosition pos;
+       char *new_key = NULL;
+       CURLcode err = 0;
+       
+       if (fields && !fields->nApplyCount) {
+               FOREACH_HASH_KEYVAL(pos, fields, key, data_ptr) {
+                       if (key.type != HASH_KEY_IS_STRING || *key.str) {
+                               new_key = format_key(key.type, key.str, key.num, prefix, 1);
+                               
+                               switch (Z_TYPE_PP(data_ptr)) {
+                                       case IS_ARRAY:
+                                       case IS_OBJECT: {
+                                               STATUS status;
+                                               
+                                               ++fields->nApplyCount;
+                                               status = recursive_fields(http_post_data, HASH_OF(*data_ptr), new_key TSRMLS_CC);
+                                               --fields->nApplyCount;
+                                               
+                                               if (SUCCESS != status) {
+                                                       goto error;
+                                               }
+                                               break;
+                                       }
+                                               
+                                       default: {
+                                               zval *data = php_http_zsep(IS_STRING, *data_ptr);
+                                               
+                                               err = curl_formadd(&http_post_data[0], &http_post_data[1],
+                                                       CURLFORM_COPYNAME,                      new_key,
+                                                       CURLFORM_COPYCONTENTS,          Z_STRVAL_P(data),
+                                                       CURLFORM_CONTENTSLENGTH,        (long) Z_STRLEN_P(data),
+                                                       CURLFORM_END
+                                               );
+                                               
+                                               zval_ptr_dtor(&data);
+                                               
+                                               if (CURLE_OK != err) {
+                                                       goto error;
+                                               }
+                                               break;
+                                       }
+                               }
+                               STR_FREE(new_key);
+                       }
+               }
+       }
+       
+       return SUCCESS;
+       
+error:
+       if (new_key) {
+               efree(new_key);
+       }
+       if (http_post_data[0]) {
+               curl_formfree(http_post_data[0]);
+       }
+       if (err) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not encode post fields: %s", curl_easy_strerror(err));
+       } else {
+               php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not encode post fields: unknown error");
+       }
+       return FAILURE;
+}
+
+static STATUS recursive_files(post_data http_post_data, HashTable *files, const char *prefix TSRMLS_DC) {
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       zval **data_ptr;
+       HashPosition pos;
+       char *new_key = NULL;
+       CURLcode err = 0;
+       
+       if (files && !files->nApplyCount) {
+               FOREACH_HASH_KEYVAL(pos, files, key, data_ptr) {
+                       zval **file_ptr, **type_ptr, **name_ptr;
+                       
+                       if (key.type != HASH_KEY_IS_STRING || *key.str) {
+                               new_key = format_key(key.type, key.str, key.num, prefix, 0);
+                               
+                               if (Z_TYPE_PP(data_ptr) != IS_ARRAY && Z_TYPE_PP(data_ptr) != IS_OBJECT) {
+                                       if (new_key || key.type == HASH_KEY_IS_STRING) {
+                                               php_http_error(HE_NOTICE, PHP_HTTP_E_INVALID_PARAM, "Unrecognized type of post file array entry '%s'", new_key ? new_key : key.str);
+                                       } else {
+                                               php_http_error(HE_NOTICE, PHP_HTTP_E_INVALID_PARAM, "Unrecognized type of post file array entry '%lu'", key.num);
+                                       }
+                               } else if (     SUCCESS != zend_hash_find(HASH_OF(*data_ptr), "name", sizeof("name"), (void *) &name_ptr) ||
+                                                       SUCCESS != zend_hash_find(HASH_OF(*data_ptr), "type", sizeof("type"), (void *) &type_ptr) ||
+                                                       SUCCESS != zend_hash_find(HASH_OF(*data_ptr), "file", sizeof("file"), (void *) &file_ptr)) {
+                                       STATUS status;
+                                       
+                                       ++files->nApplyCount;
+                                       status = recursive_files(http_post_data, HASH_OF(*data_ptr), new_key TSRMLS_CC);
+                                       --files->nApplyCount;
+                                       
+                                       if (SUCCESS != status) {
+                                               goto error;
+                                       }
+                               } else {
+                                       const char *path;
+                                       zval *file = php_http_zsep(IS_STRING, *file_ptr);
+                                       zval *type = php_http_zsep(IS_STRING, *type_ptr);
+                                       zval *name = php_http_zsep(IS_STRING, *name_ptr);
+                                       
+                                       if (SUCCESS != php_check_open_basedir(Z_STRVAL_P(file) TSRMLS_CC)) {
+                                               goto error;
+                                       }
+                                       
+                                       /* this is blatant but should be sufficient for most cases */
+                                       if (strncasecmp(Z_STRVAL_P(file), "file://", lenof("file://"))) {
+                                               path = Z_STRVAL_P(file);
+                                       } else {
+                                               path = Z_STRVAL_P(file) + lenof("file://");
+                                       }
+                                       
+                                       if (new_key) {
+                                               char *tmp_key = format_key(HASH_KEY_IS_STRING, Z_STRVAL_P(name), 0, new_key, 0);
+                                               STR_SET(new_key, tmp_key);
+                                       }
+                                       
+                                       err = curl_formadd(&http_post_data[0], &http_post_data[1],
+                                               CURLFORM_COPYNAME,              new_key ? new_key : Z_STRVAL_P(name),
+                                               CURLFORM_FILE,                  path,
+                                               CURLFORM_CONTENTTYPE,   Z_STRVAL_P(type),
+                                               CURLFORM_END
+                                       );
+                                       
+                                       zval_ptr_dtor(&file);
+                                       zval_ptr_dtor(&type);
+                                       zval_ptr_dtor(&name);
+                                       
+                                       if (CURLE_OK != err) {
+                                               goto error;
+                                       }
+                               }
+                               STR_FREE(new_key);
+                       }
+               }
+       }
+       
+       return SUCCESS;
+       
+error:
+       if (new_key) {
+               efree(new_key);
+       }
+       if (http_post_data[0]) {
+               curl_formfree(http_post_data[0]);
+       }
+       if (err) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not encode post files: %s", curl_easy_strerror(err));
+       } else {
+               php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not encode post files: unknown error");
+       }
+       return FAILURE;
+}
+
+/* PHP */
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)                  PHP_HTTP_BEGIN_ARGS_EX(HttpMessageBody, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                                            PHP_HTTP_EMPTY_ARGS_EX(HttpMessageBody, method, 0)
+#define PHP_HTTP_MESSAGE_BODY_ME(method, visibility)   PHP_ME(HttpMessageBody, method, PHP_HTTP_ARGS(HttpMessageBody, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(__construct, 0)
+       PHP_HTTP_ARG_VAL(stream, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(__toString);
+
+PHP_HTTP_BEGIN_ARGS(toStream, 1)
+       PHP_HTTP_ARG_VAL(stream, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(toCallback, 1)
+       PHP_HTTP_ARG_VAL(callback, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(append, 1)
+       PHP_HTTP_ARG_VAL(string, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(add, 0)
+       PHP_HTTP_ARG_VAL(fields, 0)
+       PHP_HTTP_ARG_VAL(files, 0)
+PHP_HTTP_END_ARGS;
+
+
+zend_class_entry *php_http_message_body_class_entry;
+zend_function_entry php_http_message_body_method_entry[] = {
+       PHP_HTTP_MESSAGE_BODY_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+       PHP_HTTP_MESSAGE_BODY_ME(__toString, ZEND_ACC_PUBLIC)
+       PHP_MALIAS(HttpMessageBody, toString, __toString, args_for_HttpMessageBody___toString, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_BODY_ME(toStream, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_BODY_ME(toCallback, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_BODY_ME(append, ZEND_ACC_PUBLIC)
+       PHP_HTTP_MESSAGE_BODY_ME(add, ZEND_ACC_PUBLIC)
+       EMPTY_FUNCTION_ENTRY
+};
+static zend_object_handlers php_http_message_body_object_handlers;
+
+PHP_MINIT_FUNCTION(http_message_body)
+{
+       PHP_HTTP_REGISTER_CLASS(http\\message, Body, http_message_body, php_http_object_class_entry, 0);
+       php_http_message_body_class_entry->create_object = php_http_message_body_object_new;
+       memcpy(&php_http_message_body_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_message_body_object_handlers.clone_obj = php_http_message_body_object_clone;
+
+       return SUCCESS;
+}
+
+zend_object_value php_http_message_body_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_message_body_object_new_ex(ce, NULL, NULL TSRMLS_CC);
+}
+
+zend_object_value php_http_message_body_object_new_ex(zend_class_entry *ce, php_http_message_body_t *body, php_http_message_body_object_t **ptr TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_message_body_object_t *o;
+
+       o = ecalloc(1, sizeof(php_http_message_body_object_t));
+       zend_object_std_init((zend_object *) o, php_http_message_body_class_entry TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       if (ptr) {
+               *ptr = o;
+       }
+
+       if (body) {
+               o->body = body;
+       }
+
+       ov.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_message_body_object_free, NULL TSRMLS_CC);
+       ov.handlers = &php_http_message_body_object_handlers;
+
+       return ov;
+}
+
+zend_object_value php_http_message_body_object_clone(zval *object TSRMLS_DC)
+{
+       zend_object_value new_ov;
+       php_http_message_body_object_t *new_obj = NULL;
+       php_http_message_body_object_t *old_obj = zend_object_store_get_object(object TSRMLS_CC);
+
+       new_ov = php_http_message_body_object_new_ex(old_obj->zo.ce, php_http_message_body_copy(old_obj->body, NULL, 1), &new_obj);
+       zend_objects_clone_members(&new_obj->zo, new_ov, &old_obj->zo, Z_OBJ_HANDLE_P(object) TSRMLS_CC);
+
+       return new_ov;
+}
+
+void php_http_message_body_object_free(void *object TSRMLS_DC)
+{
+       php_http_message_body_object_t *obj = object;
+
+       php_http_message_body_free(&obj->body);
+
+       zend_object_std_dtor((zend_object *) obj TSRMLS_CC);
+       efree(obj);
+}
+
+PHP_METHOD(HttpMessageBody, __construct)
+{
+       php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+       zval *zstream;
+       php_stream *stream;
+
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream)) {
+                       php_stream_from_zval(stream, &zstream);
+
+                       if (stream) {
+                               if (obj->body) {
+                                       php_http_message_body_dtor(obj->body);
+                               }
+                               obj->body = php_http_message_body_init(obj->body, stream TSRMLS_CC);
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpMessageBody, __toString)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               char *str;
+               size_t len;
+
+               php_http_message_body_to_string(obj->body, &str, &len, 0, 0);
+               if (str) {
+                       RETURN_STRINGL(str, len, 0);
+               }
+       }
+       RETURN_EMPTY_STRING();
+}
+
+PHP_METHOD(HttpMessageBody, toStream)
+{
+       zval *zstream;
+       long offset = 0, forlen = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|ll", &zstream, &offset, &forlen)) {
+               php_stream *stream;
+               php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               php_stream_from_zval(stream, &zstream);
+               php_http_message_body_to_stream(obj->body, stream, offset, forlen);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+struct fcd {
+       zval *fcz;
+       zend_fcall_info *fci;
+       zend_fcall_info_cache *fcc;
+};
+
+static size_t pass(void *cb_arg, const char *str, size_t len TSRMLS_DC)
+{
+       struct fcd *fcd = cb_arg;
+       zval *zdata;
+
+       MAKE_STD_ZVAL(zdata);
+       ZVAL_STRINGL(zdata, str, len, 1);
+       if (SUCCESS == zend_fcall_info_argn(fcd->fci TSRMLS_CC, 2, fcd->fcz, zdata)) {
+               zend_fcall_info_call(fcd->fci, fcd->fcc, NULL, NULL TSRMLS_CC);
+               zend_fcall_info_args_clear(fcd->fci, 0);
+       }
+       zval_ptr_dtor(&zdata);
+       return len;
+}
+
+PHP_METHOD(HttpMessageBody, toCallback)
+{
+       struct fcd fcd;
+       long offset = 0, forlen = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "f|ll", &fcd.fci, &fcd.fcc, &offset, &forlen)) {
+               php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               fcd.fcz = getThis();
+               Z_ADDREF_P(fcd.fcz);
+               php_http_message_body_to_callback(obj->body, pass, &fcd, offset, forlen);
+               zval_ptr_dtor(&fcd.fcz);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessageBody, append)
+{
+       char *str;
+       int len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len)) {
+               php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_LONG(php_http_message_body_append(obj->body, str, len));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpMessageBody, add)
+{
+       HashTable *fields = NULL, *files = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|hh", &fields, &files)) {
+               php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_SUCCESS(php_http_message_body_add(obj->body, fields, files));
+       }
+       RETURN_FALSE;
+}
+/*
+ * 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_message_body.h b/php_http_message_body.h
new file mode 100644 (file)
index 0000000..d253ff6
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_message_body_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_MESSAGE_BODY_H
+#define PHP_HTTP_MESSAGE_BODY_H
+
+typedef struct php_http_message_body {
+       int stream_id;
+       php_stream_statbuf ssb;
+#ifdef ZTS
+       void ***ts;
+#endif
+} php_http_message_body_t;
+
+PHP_HTTP_API php_http_message_body_t *php_http_message_body_init(php_http_message_body_t *body, php_stream *stream TSRMLS_DC);
+PHP_HTTP_API php_http_message_body_t *php_http_message_body_copy(php_http_message_body_t *from, php_http_message_body_t *to, zend_bool dup_internal_stream_and_contents TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_message_body_add(php_http_message_body_t *body, HashTable *fields, HashTable *files);
+PHP_HTTP_API STATUS php_http_message_body_add_field(php_http_message_body_t *body, const char *name, const char *value_str, size_t value_len);
+PHP_HTTP_API STATUS php_http_message_body_add_file(php_http_message_body_t *body, const char *name, const char *path, const char *ctype);
+PHP_HTTP_API size_t php_http_message_body_append(php_http_message_body_t *body, const char *buf, size_t len);
+PHP_HTTP_API void php_http_message_body_to_string(php_http_message_body_t *body, char **buf, size_t *len, off_t offset, size_t forlen);
+PHP_HTTP_API void php_http_message_body_to_stream(php_http_message_body_t *body, php_stream *s, off_t offset, size_t forlen);
+PHP_HTTP_API void php_http_message_body_to_callback(php_http_message_body_t *body, php_http_pass_callback_t cb, void *cb_arg, off_t offset, size_t forlen);
+PHP_HTTP_API void php_http_message_body_dtor(php_http_message_body_t *body);
+PHP_HTTP_API void php_http_message_body_free(php_http_message_body_t **body);
+PHP_HTTP_API php_stream_statbuf *php_http_message_body_stat(php_http_message_body_t *body);
+#define php_http_message_body_size(b) (php_http_message_body_stat((b))->sb.st_size)
+#define php_http_message_body_mtime(b) (php_http_message_body_stat((b))->sb.st_mtime)
+PHP_HTTP_API char *php_http_message_body_etag(php_http_message_body_t *body);
+
+static inline php_stream *php_http_message_body_stream(php_http_message_body_t *body)
+{
+       TSRMLS_FETCH_FROM_CTX(body->ts);
+       return zend_fetch_resource(NULL TSRMLS_CC, body->stream_id, "stream", NULL, 2, php_file_le_stream(), php_file_le_pstream());
+}
+
+
+typedef struct php_http_message_body_object {
+       zend_object zo;
+       php_http_message_body_t *body;
+} php_http_message_body_object_t;
+
+extern zend_class_entry *php_http_message_body_class_entry;
+extern zend_function_entry php_http_message_body_method_entry[];
+
+extern PHP_MINIT_FUNCTION(http_message_body);
+
+extern zend_object_value php_http_message_body_object_new(zend_class_entry *ce TSRMLS_DC);
+extern zend_object_value php_http_message_body_object_new_ex(zend_class_entry *ce, php_http_message_body_t *body, php_http_message_body_object_t **ptr TSRMLS_DC);
+extern zend_object_value php_http_message_body_object_clone(zval *object TSRMLS_DC);
+extern void php_http_message_body_object_free(void *object TSRMLS_DC);
+
+PHP_METHOD(HttpMessageBody, __construct);
+PHP_METHOD(HttpMessageBody, __toString);
+PHP_METHOD(HttpMessageBody, toStream);
+PHP_METHOD(HttpMessageBody, toCallback);
+PHP_METHOD(HttpMessageBody, append);
+PHP_METHOD(HttpMessageBody, add);
+
+#endif
+
+/*
+ * 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_message_parser.c b/php_http_message_parser.c
new file mode 100644 (file)
index 0000000..e2751a4
--- /dev/null
@@ -0,0 +1,363 @@
+#include "php_http.h"
+
+typedef struct php_http_message_parser_state_spec {
+       php_http_message_parser_state_t state;
+       unsigned need_data:1;
+} php_http_message_parser_state_spec_t;
+
+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_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_DONE,                    0}
+};
+
+PHP_HTTP_API php_http_message_parser_t *php_http_message_parser_init(php_http_message_parser_t *parser TSRMLS_DC)
+{
+       if (!parser) {
+               parser = emalloc(sizeof(*parser));
+       }
+       memset(parser, 0, sizeof(*parser));
+
+       TSRMLS_SET_CTX(parser->ts);
+
+       php_http_header_parser_init(&parser->header TSRMLS_CC);
+       zend_stack_init(&parser->stack);
+
+       return parser;
+}
+
+PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_push(php_http_message_parser_t *parser, unsigned argc, ...)
+{
+       va_list va_args;
+       unsigned i;
+       va_start(va_args, argc);
+       php_http_message_parser_state_t state;
+
+       for (i = 0; i < argc; ++i) {
+               state  = va_arg(va_args, php_http_message_parser_state_t);
+               zend_stack_push(&parser->stack, &state, sizeof(state));
+       }
+       va_end(va_args);
+
+       return state;
+}
+
+PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_is(php_http_message_parser_t *parser)
+{
+       php_http_message_parser_state_t *state;
+
+       if (SUCCESS == zend_stack_top(&parser->stack, (void *) &state)) {
+               return *state;
+       }
+       return PHP_HTTP_MESSAGE_PARSER_STATE_START;
+}
+
+PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_pop(php_http_message_parser_t *parser)
+{
+       php_http_message_parser_state_t state, *state_ptr;
+       if (SUCCESS == zend_stack_top(&parser->stack, (void *) &state_ptr)) {
+               state = *state_ptr;
+               zend_stack_del_top(&parser->stack);
+               return state;
+       }
+       return PHP_HTTP_MESSAGE_PARSER_STATE_START;
+}
+
+PHP_HTTP_API void php_http_message_parser_dtor(php_http_message_parser_t *parser)
+{
+       php_http_header_parser_dtor(&parser->header);
+       zend_stack_destroy(&parser->stack);
+       if (parser->dechunk) {
+               php_http_encoding_stream_free(&parser->dechunk TSRMLS_CC);
+       }
+       if (parser->inflate) {
+               php_http_encoding_stream_free(&parser->inflate TSRMLS_CC);
+       }
+}
+
+PHP_HTTP_API void php_http_message_parser_free(php_http_message_parser_t **parser)
+{
+       if (*parser) {
+               php_http_message_parser_dtor(*parser);
+               efree(*parser);
+               *parser = NULL;
+       }
+}
+
+
+PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_parser_t *parser, php_http_buffer *buffer, unsigned flags, php_http_message_t **message)
+{
+       TSRMLS_FETCH_FROM_CTX(parser->ts);
+       char *str = NULL;
+       size_t len = 0;
+       size_t cut = 0;
+
+       while (buffer->used || !php_http_message_parser_states[php_http_message_parser_state_is(parser)].need_data) {
+#if 0
+               const char *state[] = {"START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "DONE"};
+               fprintf(stderr, "#MP: %s (%d) %zu\n",
+                               state[php_http_message_parser_state_is(parser)], (*message)->type, buffer->used);
+#endif
+
+               switch (php_http_message_parser_state_pop(parser))
+               {
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE:
+                               return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE);
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_START:
+                       {
+                               char *ptr = buffer->data;
+
+                               while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
+                                       ++ptr;
+                               }
+
+                               php_http_buffer_cut(buffer, 0, ptr - buffer->data);
+
+                               if (buffer->used) {
+                                       php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER);
+                               }
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER:
+                       {
+                               unsigned header_parser_flags = (flags & PHP_HTTP_MESSAGE_PARSER_CLEANUP) ? PHP_HTTP_HEADER_PARSER_CLEANUP : 0;
+
+                               switch (php_http_header_parser_parse(&parser->header, buffer, header_parser_flags, &(*message)->hdrs, (php_http_info_callback_t) php_http_message_info_callback, message)) {
+                                       case PHP_HTTP_HEADER_PARSER_STATE_FAILURE:
+                                               return PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE;
+
+                                       case PHP_HTTP_HEADER_PARSER_STATE_DONE:
+                                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE);
+                                               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;
+                                               }
+                               }
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE:
+                       {
+                               zval *h, **h_cl = NULL, **h_cr = NULL, **h_te = NULL;
+
+                               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_del(&(*message)->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding"));
+                               }
+                               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"));
+                               }
+
+                               if ((h = php_http_message_header(*message, ZEND_STRL("Content-Encoding"), 1))) {
+                                       if (strstr(Z_STRVAL_P(h), "gzip") || strstr(Z_STRVAL_P(h), "x-gzip") || strstr(Z_STRVAL_P(h), "deflate")) {
+                                               parser->inflate = php_http_encoding_stream_init(parser->inflate, php_http_encoding_stream_get_inflate_ops(), 0 TSRMLS_CC);
+                                               zend_hash_update(&(*message)->hdrs, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), &h, sizeof(zval *), NULL);
+                                               zend_hash_del(&(*message)->hdrs, "Content-Encoding", sizeof("Content-Encoding"));
+                                       } 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);
+
+                               if (h_te) {
+                                       if (strstr(Z_STRVAL_PP(h_te), "chunked")) {
+                                               parser->dechunk = php_http_encoding_stream_init(parser->dechunk, php_http_encoding_stream_get_dechunk_ops(), 0 TSRMLS_CC);
+                                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED);
+                                               break;
+                                       }
+                               }
+
+                               if (h_cl) {
+                                       char *stop;
+
+                                       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, 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")] == '='
+                                               )
+                                       ) {
+                                               char *total_at = NULL, *end_at = NULL;
+                                               char *start_at = Z_STRVAL_PP(h_cr) + sizeof("bytes");
+
+                                               start = strtoul(start_at, &end_at, 10);
+                                               if (end_at) {
+                                                       end = strtoul(end_at + 1, &total_at, 10);
+                                                       if (total_at && strncmp(total_at + 1, "*", 1)) {
+                                                               total = strtoul(total_at + 1, NULL, 10);
+                                                       }
+
+                                                       if (end >= start && (!total || end < total)) {
+                                                               parser->body_length = end + 1 - start;
+                                                               php_http_message_parser_state_push(parser, 1, 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);
+                               } else {
+                                       php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB);
+                               }
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_BODY:
+                       {
+                               zval *zcl;
+
+                               if (parser->inflate) {
+                                       char *dec_str = NULL;
+                                       size_t dec_len;
+
+                                       if (SUCCESS != php_http_encoding_stream_update(parser->inflate, str, len, &dec_str, &dec_len TSRMLS_CC)) {
+                                               return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE);
+                                       }
+
+                                       if (str != buffer->data) {
+                                               STR_FREE(str);
+                                       }
+                                       str = dec_str;
+                                       len = dec_len;
+                               }
+
+                               php_stream_write(php_http_message_body_stream(&(*message)->body), str, len);
+                               php_http_buffer_cut(buffer, 0, cut);
+
+                               /* 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 (str != buffer->data) {
+                                       STR_FREE(str);
+                               }
+                               str = NULL;
+                               len = 0;
+                               cut = 0;
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB:
+                       {
+                               str = buffer->data;
+                               len = buffer->used;
+                               cut = len;
+
+                               php_http_message_parser_state_push(parser, 2, !buffer->used?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH:
+                       {
+                               len = MIN(parser->body_length, buffer->used);
+                               str = buffer->data;
+                               cut = len;
+
+                               parser->body_length -= len;
+
+                               php_http_message_parser_state_push(parser, 2, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED:
+                       {
+                               /*
+                                * - pass available data through the dechunk stream
+                                * - pass decoded data along
+                                * - if stream zeroed:
+                                *      Y:      - cut processed string out of buffer, but leave length of unprocessed dechunk stream data untouched
+                                *              - body done
+                                *      N:      - parse ahaed
+                                */
+                               size_t dec_len;
+                               char *dec_str = NULL;
+
+                               if (SUCCESS != php_http_encoding_stream_update(parser->dechunk, buffer->data, buffer->used, &dec_str, &dec_len TSRMLS_CC)) {
+                                       return FAILURE;
+                               }
+
+                               str = dec_str;
+                               len = dec_len;
+
+                               if (php_http_encoding_stream_done(parser->dechunk)) {
+                                       cut = buffer->used - PHP_HTTP_BUFFER_LEN(parser->dechunk->ctx);
+                                       php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
+                               } else {
+                                       cut = buffer->used;
+                                       php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
+                               }
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:
+                       {
+                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE);
+
+                               if (parser->dechunk) {
+                                       char *dec_str;
+                                       size_t dec_len;
+
+                                       if (SUCCESS != php_http_encoding_stream_finish(parser->dechunk, &dec_str, &dec_len TSRMLS_CC)) {
+                                               return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE);
+                                       }
+                                       php_http_encoding_stream_dtor(parser->dechunk);
+
+                                       if (dec_str && dec_len) {
+                                               str = dec_str;
+                                               len = dec_len;
+                                               cut = 0;
+                                               php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY);
+                                       }
+                               }
+
+                               break;
+                       }
+
+                       case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: {
+                               char *ptr = buffer->data;
+
+                               while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) {
+                                       ++ptr;
+                               }
+
+                               php_http_buffer_cut(buffer, 0, ptr - buffer->data);
+                               break;
+                       }
+               }
+       }
+
+       return php_http_message_parser_state_is(parser);
+}
diff --git a/php_http_message_parser.h b/php_http_message_parser.h
new file mode 100644 (file)
index 0000000..cd85eea
--- /dev/null
@@ -0,0 +1,40 @@
+
+#ifndef PHP_HTTP_MESSAGE_PARSER_H
+#define PHP_HTTP_MESSAGE_PARSER_H
+
+typedef enum php_http_message_parser_state {
+       PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE = FAILURE,
+       PHP_HTTP_MESSAGE_PARSER_STATE_START = 0,
+       PHP_HTTP_MESSAGE_PARSER_STATE_HEADER,
+       PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE,
+       PHP_HTTP_MESSAGE_PARSER_STATE_BODY,
+       PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB,
+       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_DONE
+} php_http_message_parser_state_t;
+
+#define PHP_HTTP_MESSAGE_PARSER_CLEANUP 0x1
+
+typedef struct php_http_message_parser {
+       php_http_header_parser_t header;
+       zend_stack stack;
+       size_t body_length;
+       php_http_message_t *message;
+       php_http_encoding_stream_t *dechunk;
+       php_http_encoding_stream_t *inflate;
+#ifdef ZTS
+       void ***ts;
+#endif
+} php_http_message_parser_t;
+
+PHP_HTTP_API php_http_message_parser_t *php_http_message_parser_init(php_http_message_parser_t *parser TSRMLS_DC);
+PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_push(php_http_message_parser_t *parser, unsigned argc, ...);
+PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_is(php_http_message_parser_t *parser);
+PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_pop(php_http_message_parser_t *parser);
+PHP_HTTP_API void php_http_message_parser_dtor(php_http_message_parser_t *parser);
+PHP_HTTP_API void php_http_message_parser_free(php_http_message_parser_t **parser);
+PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_parser_t *parser, php_http_buffer *buffer, unsigned flags, php_http_message_t **message);
+
+#endif
diff --git a/php_http_misc.c b/php_http_misc.c
new file mode 100644 (file)
index 0000000..8d52382
--- /dev/null
@@ -0,0 +1,238 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id $ */
+
+#include "php_http.h"
+
+/* SLEEP */
+
+PHP_HTTP_API void php_http_sleep(double s)
+{
+#if defined(PHP_WIN32)
+       Sleep((DWORD) PHP_HTTP_MSEC(s));
+#elif defined(HAVE_USLEEP)
+       usleep(PHP_HTTP_USEC(s));
+#elif defined(HAVE_NANOSLEEP)
+       struct timespec req, rem;
+
+       req.tv_sec = (time_t) s;
+       req.tv_nsec = PHP_HTTP_NSEC(s) % PHP_HTTP_NANOSEC;
+
+       while (nanosleep(&req, &rem) && (errno == EINTR) && (PHP_HTTP_NSEC(rem.tv_sec) + rem.tv_nsec) > PHP_HTTP_NSEC(PHP_HTTP_DIFFSEC))) {
+               req.tv_sec = rem.tv_sec;
+               req.tv_nsec = rem.tv_nsec;
+       }
+#else
+       struct timeval timeout;
+
+       timeout.tv.sec = (time_t) s;
+       timeout.tv_usec = PHP_HTTP_USEC(s) % PHP_HTTP_MCROSEC;
+
+       select(0, NULL, NULL, NULL, &timeout);
+#endif
+}
+
+
+/* STRING UTILITIES */
+
+int php_http_match(const char *haystack_str, const char *needle_str, int flags)
+{
+       int result;
+
+       if (flags & PHP_HTTP_MATCH_FULL) {
+               if (flags & PHP_HTTP_MATCH_CASE) {
+                       result = !strcmp(haystack_str, needle_str);
+               } else {
+                       result = !strcasecmp(haystack_str, needle_str);
+               }
+       } else {
+               char *found, *haystack = estrdup(haystack_str), *needle = estrdup(needle_str);
+
+               if (flags & PHP_HTTP_MATCH_CASE) {
+                       found = zend_memnstr(haystack, needle, strlen(needle), haystack+strlen(haystack));
+               } else {
+                       found = php_stristr(haystack, needle, strlen(haystack), strlen(needle));
+               }
+
+               if (found) {
+                       if (!(flags & PHP_HTTP_MATCH_WORD)
+                       ||      (       (found == haystack || !PHP_HTTP_IS_CTYPE(alnum, *(found - 1)))
+                               &&      (!*(found + strlen(needle)) || !PHP_HTTP_IS_CTYPE(alnum, *(found + strlen(needle))))
+                               )
+                       ) {
+                               result = 1;
+                       }
+               }
+
+               STR_FREE(haystack);
+               STR_FREE(needle);
+       }
+
+       return result;
+}
+
+char *php_http_pretty_key(char *key, size_t key_len, zend_bool uctitle, zend_bool xhyphen)
+{
+       size_t i;
+       int wasalpha;
+
+       if (key && key_len) {
+               if ((wasalpha = PHP_HTTP_IS_CTYPE(alpha, key[0]))) {
+                       key[0] = (char) (uctitle ? PHP_HTTP_TO_CTYPE(upper, key[0]) : PHP_HTTP_TO_CTYPE(lower, key[0]));
+               }
+               for (i = 1; i < key_len; i++) {
+                       if (PHP_HTTP_IS_CTYPE(alpha, key[i])) {
+                               key[i] = (char) (((!wasalpha) && uctitle) ? PHP_HTTP_TO_CTYPE(upper, key[i]) : PHP_HTTP_TO_CTYPE(lower, key[i]));
+                               wasalpha = 1;
+                       } else {
+                               if (xhyphen && (key[i] == '_')) {
+                                       key[i] = '-';
+                               }
+                               wasalpha = 0;
+                       }
+               }
+       }
+       return key;
+}
+
+
+size_t php_http_boundary(char *buf, size_t buf_len TSRMLS_DC)
+{
+       return snprintf(buf, buf_len, "%lu%0.9f", (ulong) PHP_HTTP_G->env.request.time, (float) php_combined_lcg(TSRMLS_C));
+}
+
+/* ARRAYS */
+
+int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
+{
+       int flags;
+       char *key = NULL;
+       HashTable *dst;
+       zval **data = NULL, **value = (zval **) pDest;
+
+       dst = va_arg(args, HashTable *);
+       flags = va_arg(args, int);
+
+       if ((!(flags & ARRAY_JOIN_STRONLY)) || hash_key->nKeyLength) {
+               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 {
+                       zend_hash_quick_find(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &data);
+               }
+
+               Z_ADDREF_P(*value);
+               if (data) {
+                       if (Z_TYPE_PP(data) != IS_ARRAY) {
+                               convert_to_array(*data);
+                       }
+                       add_next_index_zval(*data, *value);
+               } else if (key) {
+                       zend_hash_add(dst, key, hash_key->nKeyLength, value, sizeof(zval *), NULL);
+               } else {
+                       zend_hash_quick_add(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, value, sizeof(zval *), NULL);
+               }
+
+               if (key) {
+                       efree(key);
+               }
+       }
+
+       return ZEND_HASH_APPLY_KEEP;
+}
+
+int php_http_array_apply_merge_func(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key)
+{
+       int flags;
+       char *key = NULL;
+       HashTable *dst;
+       zval **value = (zval **) pDest;
+
+       dst = va_arg(args, HashTable *);
+       flags = va_arg(args, int);
+
+       if ((!(flags & ARRAY_JOIN_STRONLY)) || hash_key->nKeyLength) {
+               Z_ADDREF_P(*value);
+               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_update(dst, key, hash_key->nKeyLength, (void *) value, sizeof(zval *), NULL);
+                       efree(key);
+               } else {
+                       zend_hash_quick_update(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) value, sizeof(zval *), NULL);
+               }
+       }
+
+       return ZEND_HASH_APPLY_KEEP;
+}
+
+/* PASS CALLBACK */
+
+PHP_HTTP_API size_t php_http_pass_wrapper(php_http_pass_callback_arg_t *cb, const char *str, size_t len)
+{
+       TSRMLS_FETCH();
+       return cb->cb_zts(cb->cb_arg, str, len TSRMLS_CC);
+}
+
+/* ERROR */
+
+static inline int scope_error_handling(long type TSRMLS_DC)
+{
+       if ((type == E_THROW) || (EG(error_handling) == EH_THROW)) {
+               return EH_THROW;
+       }
+
+       if (EG(This) && instanceof_function(Z_OBJCE_P(EG(This)), php_http_object_class_entry)) {
+               return php_http_object_get_error_handling(EG(This) TSRMLS_CC);
+       }
+
+       return EH_NORMAL;
+}
+
+void php_http_error(long type TSRMLS_DC, long code, const char *format, ...)
+{
+       va_list args;
+
+       va_start(args, format);
+       switch (scope_error_handling(type TSRMLS_CC)) {
+               case EH_THROW: {
+                       char *message;
+                       zend_class_entry *ce;
+
+                       if (EG(exception_class) && instanceof_function(EG(exception_class), php_http_exception_class_entry)) {
+                               ce = EG(exception_class);
+                       } else {
+                               ce = php_http_exception_get_for_code(code);
+                       }
+
+                       vspprintf(&message, 0, format, args);
+                       zend_throw_exception(ce, message, code TSRMLS_CC);
+                       efree(message);
+                       break;
+               }
+               case EH_NORMAL:
+                       php_verror(NULL, "", type, format, args TSRMLS_CC);
+                       break;
+               case EH_SUPPRESS:
+                       break;
+       }
+       va_end(args);
+}
+
+/*
+ * 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_misc.h b/php_http_misc.h
new file mode 100644 (file)
index 0000000..2b3d6e9
--- /dev/null
@@ -0,0 +1,467 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_api.h 298891 2010-05-03 08:26:38Z mike $ */
+
+#ifndef PHP_HTTP_MISC_H
+#define PHP_HTTP_MISC_H
+
+/* DEFAULTS */
+
+/* DATE FORMAT RFC1123 */
+#define PHP_HTTP_DATE_FORMAT "D, d M Y H:i:s \\G\\M\\T"
+
+/* CR LF */
+#define PHP_HTTP_CRLF "\r\n"
+
+/* default cache control */
+#define PHP_HTTP_DEFAULT_CACHECONTROL "private, must-revalidate, max-age=0"
+
+/* max URL length */
+#define PHP_HTTP_URL_MAXLEN 4096
+
+/* max request method length */
+#define PHP_HTTP_REQUEST_METHOD_MAXLEN 31
+
+/* def URL arg separator */
+#define PHP_HTTP_URL_ARGSEP "&"
+
+/* send buffer size */
+#define PHP_HTTP_SENDBUF_SIZE 40960
+
+/* CURL buffer size */
+#define PHP_HTTP_CURLBUF_SIZE 16384
+
+/* SLEEP */
+
+#define PHP_HTTP_DIFFSEC (0.001)
+#define PHP_HTTP_MLLISEC (1000)
+#define PHP_HTTP_MCROSEC (1000 * 1000)
+#define PHP_HTTP_NANOSEC (1000 * 1000 * 1000)
+#define PHP_HTTP_MSEC(s) ((long)(s * PHP_HTTP_MLLISEC))
+#define PHP_HTTP_USEC(s) ((long)(s * PHP_HTTP_MCROSEC))
+#define PHP_HTTP_NSEC(s) ((long)(s * PHP_HTTP_NANOSEC))
+
+PHP_HTTP_API void php_http_sleep(double s);
+
+/* STRING UTILITIES */
+
+#define PHP_HTTP_CHECK_CONTENT_TYPE(ct, action) \
+       if (!strchr((ct), '/')) { \
+               php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, \
+                       "Content type \"%s\" does not seem to contain a primary and a secondary part", (ct)); \
+               action; \
+       }
+
+
+#ifndef STR_SET
+#      define STR_SET(STR, SET) \
+       { \
+               STR_FREE(STR); \
+               STR = SET; \
+       }
+#endif
+
+#define STR_PTR(s) (s?s:"")
+
+#define lenof(S) (sizeof(S) - 1)
+
+#define PHP_HTTP_MATCH_LOOSE   0
+#define PHP_HTTP_MATCH_CASE            0x01
+#define PHP_HTTP_MATCH_WORD            0x10
+#define PHP_HTTP_MATCH_FULL            0x20
+#define PHP_HTTP_MATCH_STRICT  (PHP_HTTP_ENV_MATCH_CASE|PHP_HTTP_ENV_MATCH_FULL)
+
+extern int php_http_match(const char *haystack, const char *needle, int flags);
+
+extern char *php_http_pretty_key(char *key, size_t key_len, zend_bool uctitle, zend_bool xhyphen);
+extern size_t php_http_boundary(char *buf, size_t len TSRMLS_DC);
+
+static inline const char *php_http_locate_str(const char *h, size_t h_len, const char *n, size_t n_len)
+{
+       const char *p, *e;
+
+       if (n_len && h_len) {
+               e = h + h_len;
+               do {
+                       if (*h == *n) {
+                               for (p = n; *p == h[p-n]; ++p) {
+                                       if (p == n+n_len-1) {
+                                               return h;
+                                       }
+                               }
+                       }
+               } while (h++ != e);
+       }
+
+       return NULL;
+}
+
+static inline const char *php_http_locate_body(const char *message)
+{
+       const char *body = NULL, *msg = message;
+
+       while (*msg) {
+               if (*msg == '\n') {
+                       if (*(msg+1) == '\n') {
+                               body = msg + 2;
+                               break;
+                       } else if (*(msg+1) == '\r' && *(msg+2) == '\n') {
+                               body = msg + 3;
+                               break;
+                       }
+               }
+               ++msg;
+       }
+       return body;
+}
+
+static inline const char *php_http_locate_eol(const char *line, int *eol_len)
+{
+       const char *eol = strpbrk(line, "\r\n");
+
+       if (eol_len) {
+               *eol_len = eol ? ((eol[0] == '\r' && eol[1] == '\n') ? 2 : 1) : 0;
+       }
+       return eol;
+}
+
+static inline const char *php_http_locate_bin_eol(const char *bin, size_t len, int *eol_len)
+{
+       const char *eol;
+
+       for (eol = bin; eol - bin < len; ++eol) {
+               if (*eol == '\r' || *eol == '\n') {
+                       *eol_len = eol ? ((eol[0] == '\r' && eol[1] == '\n') ? 2 : 1) : 0;
+                       return eol;
+               }
+       }
+
+       return NULL;
+}
+
+/* ZEND */
+
+#define INIT_PZVAL_ARRAY(zv, ht) \
+       { \
+               INIT_PZVAL((zv)); \
+               Z_TYPE_P(zv) = IS_ARRAY; \
+               Z_ARRVAL_P(zv) = (ht); \
+       }
+
+static inline zval *php_http_zsep(int type, zval *z)
+{
+       SEPARATE_ARG_IF_REF(z);
+       if (Z_TYPE_P(z) != type) {
+               switch (type) {
+                       case IS_NULL:   convert_to_null_ex(&z);         break;
+                       case IS_BOOL:   convert_to_boolean_ex(&z);      break;
+                       case IS_LONG:   convert_to_long_ex(&z);         break;
+                       case IS_DOUBLE: convert_to_double_ex(&z);       break;
+                       case IS_STRING: convert_to_string_ex(&z);       break;
+                       case IS_ARRAY:  convert_to_array_ex(&z);        break;
+                       case IS_OBJECT: convert_to_object_ex(&z);       break;
+               }
+       }
+       return z;
+}
+
+
+/* return bool (v == SUCCESS) */
+#define RETVAL_SUCCESS(v) RETVAL_BOOL(SUCCESS == (v))
+#define RETURN_SUCCESS(v) RETURN_BOOL(SUCCESS == (v))
+/* return object(values) */
+#define RETVAL_OBJECT(o, addref) \
+       RETVAL_OBJVAL((o)->value.obj, addref)
+#define RETURN_OBJECT(o, addref) \
+       RETVAL_OBJECT(o, addref); \
+       return
+#define RETVAL_OBJVAL(ov, addref) \
+       ZVAL_OBJVAL(return_value, ov, addref)
+#define RETURN_OBJVAL(ov, addref) \
+       RETVAL_OBJVAL(ov, addref); \
+       return
+#define ZVAL_OBJVAL(zv, ov, addref) \
+       (zv)->type = IS_OBJECT; \
+       (zv)->value.obj = (ov);\
+       if (addref && Z_OBJ_HT_P(zv)->add_ref) { \
+               Z_OBJ_HT_P(zv)->add_ref((zv) TSRMLS_CC); \
+       }
+/* return property */
+#define RETVAL_PROP(CE, n) RETVAL_PROP_EX(CE, getThis(), n)
+#define RETURN_PROP(CE, n) RETURN_PROP_EX(CE, getThis(), n)
+#define RETVAL_PROP_EX(CE, this, n) \
+       { \
+               zval *__prop = zend_read_property(CE, this, ZEND_STRL(n), 0 TSRMLS_CC); \
+               RETVAL_ZVAL(__prop, 1, 0); \
+       }
+#define RETURN_PROP_EX(CE, this, n) \
+       { \
+               zval *__prop = zend_read_property(CE, this, ZEND_STRL(n), 0 TSRMLS_CC); \
+               RETURN_ZVAL(__prop, 1, 0); \
+       }
+#define RETVAL_SPROP(CE, n) \
+       { \
+               zval *__prop = zend_read_static_property(CE, ZEND_STRL(n), 0 TSRMLS_CC); \
+               RETVAL_ZVAL(__prop, 1, 0); \
+       }
+#define RETURN_SPROP(CE, n) \
+       { \
+               zval *__prop = zend_read_static_property(CE, ZEND_STRL(n), 0 TSRMLS_CC); \
+               RETURN_ZVAL(__prop, 1, 0); \
+       }
+
+#define Z_OBJ_DELREF(z) \
+       if (Z_OBJ_HT(z)->del_ref) { \
+               Z_OBJ_HT(z)->del_ref(&(z) TSRMLS_CC); \
+       }
+#define Z_OBJ_ADDREF(z) \
+       if (Z_OBJ_HT(z)->add_ref) { \
+               Z_OBJ_HT(z)->add_ref(&(z) TSRMLS_CC); \
+       }
+#define Z_OBJ_DELREF_P(z) \
+       if (Z_OBJ_HT_P(z)->del_ref) { \
+               Z_OBJ_HT_P(z)->del_ref((z) TSRMLS_CC); \
+       }
+#define Z_OBJ_ADDREF_P(z) \
+       if (Z_OBJ_HT_P(z)->add_ref) { \
+               Z_OBJ_HT_P(z)->add_ref((z) TSRMLS_CC); \
+       }
+#define Z_OBJ_DELREF_PP(z) \
+       if (Z_OBJ_HT_PP(z)->del_ref) { \
+               Z_OBJ_HT_PP(z)->del_ref(*(z) TSRMLS_CC); \
+       }
+#define Z_OBJ_ADDREF_PP(z) \
+       if (Z_OBJ_HT_PP(z)->add_ref) { \
+               Z_OBJ_HT_PP(z)->add_ref(*(z) TSRMLS_CC); \
+       }
+
+#define PHP_HTTP_BEGIN_ARGS_EX(class, method, ret_ref, req_args)       ZEND_BEGIN_ARG_INFO_EX(args_for_ ##class## _ ##method , 0, ret_ref, req_args)
+#define PHP_HTTP_BEGIN_ARGS_AR(class, method, ret_ref, req_args)       ZEND_BEGIN_ARG_INFO_EX(args_for_ ##class## _ ##method , 1, ret_ref, req_args)
+#define PHP_HTTP_END_ARGS                                                                                      }
+#define PHP_HTTP_EMPTY_ARGS_EX(class, method, ret_ref)                         PHP_HTTP_BEGIN_ARGS_EX(class, method, ret_ref, 0) PHP_HTTP_END_ARGS
+#define PHP_HTTP_ARGS(class, method)                                                           args_for_ ##class## _ ##method
+#define PHP_HTTP_ARG_VAL(name, pass_ref)                                                       ZEND_ARG_INFO(pass_ref, name)
+#define PHP_HTTP_ARG_OBJ(class, name, allow_null)                                      ZEND_ARG_OBJ_INFO(0, name, class, allow_null)
+
+#define EMPTY_FUNCTION_ENTRY {NULL, NULL, NULL, 0, 0}
+
+#define PHP_MINIT_CALL(func) PHP_MINIT(func)(INIT_FUNC_ARGS_PASSTHRU)
+#define PHP_RINIT_CALL(func) PHP_RINIT(func)(INIT_FUNC_ARGS_PASSTHRU)
+#define PHP_MSHUTDOWN_CALL(func) PHP_MSHUTDOWN(func)(SHUTDOWN_FUNC_ARGS_PASSTHRU)
+#define PHP_RSHUTDOWN_CALL(func) PHP_RSHUTDOWN(func)(SHUTDOWN_FUNC_ARGS_PASSTHRU)
+
+
+#define PHP_HTTP_INI_ENTRY(entry, default, scope, updater, global) \
+       STD_PHP_INI_ENTRY(entry, default, scope, updater, global, zend_php_http_globals, php_http_globals)
+#define PHP_HTTP_INI_ENTRY_EX(entry, default, scope, updater, displayer, global) \
+       STD_PHP_INI_ENTRY_EX(entry, default, scope, updater, global, zend_php_http_globals, php_http_globals, displayer)
+
+#define PHP_HTTP_REGISTER_CLASS(ns, classname, name, parent, flags) \
+       { \
+               zend_class_entry ce; \
+               memset(&ce, 0, sizeof(zend_class_entry)); \
+               INIT_NS_CLASS_ENTRY(ce, #ns, #classname, php_ ##name## _method_entry); \
+               php_ ##name## _class_entry = zend_register_internal_class_ex(&ce, parent, NULL TSRMLS_CC); \
+               php_ ##name## _class_entry->ce_flags |= flags;  \
+       }
+
+#define PHP_HTTP_REGISTER_EXCEPTION(classname, cename, parent) \
+       { \
+               zend_class_entry ce; \
+               memset(&ce, 0, sizeof(zend_class_entry)); \
+               INIT_NS_CLASS_ENTRY(ce, "http", #classname, NULL); \
+               ce.create_object = NULL; \
+               cename = zend_register_internal_class_ex(&ce, parent, NULL TSRMLS_CC); \
+       }
+
+#define ACC_PROP_PRIVATE(ce, flags)            ((flags & ZEND_ACC_PRIVATE) && (EG(scope) && ce == EG(scope))
+#define ACC_PROP_PROTECTED(ce, flags)  ((flags & ZEND_ACC_PROTECTED) && (zend_check_protected(ce, EG(scope))))
+#define ACC_PROP_PUBLIC(flags)                 (flags & ZEND_ACC_PUBLIC)
+#define ACC_PROP(ce, flags)                            (ACC_PROP_PUBLIC(flags) || ACC_PROP_PRIVATE(ce, flags) || ACC_PROP_PROTECTED(ce, flags))
+
+#ifdef PHP_HTTP_HAVE_CURL
+#              define PHP_HTTP_DECLARE_ARG_PASS_INFO() \
+                       ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_2, 0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(1) \
+                       ZEND_END_ARG_INFO(); \
+ \
+                       ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_3, 0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(1) \
+                       ZEND_END_ARG_INFO(); \
+ \
+                       ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_4, 0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(1) \
+                       ZEND_END_ARG_INFO(); \
+ \
+                       ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_5, 0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(1) \
+                       ZEND_END_ARG_INFO();
+
+#else
+#              define PHP_HTTP_DECLARE_ARG_PASS_INFO() \
+                       ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_2, 0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(1) \
+                       ZEND_END_ARG_INFO(); \
+\
+                       ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_4, 0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(0) \
+                               ZEND_ARG_PASS_INFO(1) \
+                       ZEND_END_ARG_INFO();
+#endif /* PHP_HTTP_HAVE_CURL */
+
+/* ARRAYS */
+
+typedef struct php_http_array_hashkey {
+       char *str;
+       uint len;
+       ulong num;
+       uint dup:1;
+       uint type:31;
+} php_http_array_hashkey_t;
+#define php_http_array_hashkey_init(dup) {NULL, 0, 0, (dup), 0}
+
+#define FOREACH_VAL(pos, array, val) FOREACH_HASH_VAL(pos, Z_ARRVAL_P(array), val)
+#define FOREACH_HASH_VAL(pos, hash, val) \
+       for (   zend_hash_internal_pointer_reset_ex(hash, &pos); \
+                       zend_hash_get_current_data_ex(hash, (void *) &val, &pos) == SUCCESS; \
+                       zend_hash_move_forward_ex(hash, &pos))
+
+#define FOREACH_KEY(pos, array, key) FOREACH_HASH_KEY(pos, Z_ARRVAL_P(array), key)
+#define FOREACH_HASH_KEY(pos, hash, _key) \
+       for (   zend_hash_internal_pointer_reset_ex(hash, &pos); \
+                       ((_key).type = zend_hash_get_current_key_ex(hash, &(_key).str, &(_key).len, &(_key).num, (zend_bool) (_key).dup, &pos)) != HASH_KEY_NON_EXISTANT; \
+                       zend_hash_move_forward_ex(hash, &pos)) \
+
+#define FOREACH_KEYVAL(pos, array, key, val) FOREACH_HASH_KEYVAL(pos, Z_ARRVAL_P(array), key, val)
+#define FOREACH_HASH_KEYVAL(pos, hash, _key, val) \
+       for (   zend_hash_internal_pointer_reset_ex(hash, &pos); \
+                       ((_key).type = zend_hash_get_current_key_ex(hash, &(_key).str, &(_key).len, &(_key).num, (zend_bool) (_key).dup, &pos)) != HASH_KEY_NON_EXISTANT && \
+                       zend_hash_get_current_data_ex(hash, (void *) &val, &pos) == SUCCESS; \
+                       zend_hash_move_forward_ex(hash, &pos))
+
+#define array_copy(src, dst) zend_hash_copy(dst, src, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *))
+#define ARRAY_JOIN_STRONLY 1
+#define ARRAY_JOIN_PRETTIFY 2
+#define array_join(src, dst, append, flags) zend_hash_apply_with_arguments(src TSRMLS_CC, (append)?php_http_array_apply_append_func:php_http_array_apply_merge_func, 2, dst, (int)flags)
+
+extern int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key);
+extern int php_http_array_apply_merge_func(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key);
+
+/* PASS CALLBACK */
+
+typedef size_t (*php_http_pass_callback_t)(void *cb_arg, const char *str, size_t len);
+typedef size_t (*php_http_pass_php_http_buffer_callback_t)(void *cb_arg, php_http_buffer *str);
+
+typedef struct php_http_pass_callback_arg {
+       size_t (*cb_zts)(void *cb_arg, const char *str, size_t len TSRMLS_DC);
+       void *cb_arg;
+} php_http_pass_callback_arg_t;
+
+PHP_HTTP_API size_t php_http_pass_wrapper(php_http_pass_callback_arg_t *cb_arg, const char *str, size_t len);
+
+/* ERROR */
+
+extern void php_http_error(long type TSRMLS_DC, long code, const char *format, ...);
+
+#define with_error_handling(eh, ec) \
+       { \
+               zend_error_handling __eh; \
+               zend_replace_error_handling((eh), (ec), &__eh TSRMLS_CC);
+
+#define end_error_handling() \
+               zend_restore_error_handling(&__eh TSRMLS_CC); \
+       }
+
+#ifndef E_THROW
+#      define E_THROW -1
+#endif
+#define HE_THROW       E_THROW TSRMLS_CC
+#define HE_NOTICE      E_NOTICE TSRMLS_CC
+#define HE_WARNING     E_WARNING TSRMLS_CC
+#define HE_ERROR       E_ERROR TSRMLS_CC
+
+typedef enum php_http_error {
+       PHP_HTTP_E_RUNTIME,
+       PHP_HTTP_E_INVALID_PARAM,
+       PHP_HTTP_E_HEADER,
+       PHP_HTTP_E_MALFORMED_HEADERS,
+       PHP_HTTP_E_REQUEST_METHOD,
+       PHP_HTTP_E_MESSAGE,
+       PHP_HTTP_E_MESSAGE_TYPE,
+       PHP_HTTP_E_ENCODING,
+       PHP_HTTP_E_REQUEST,
+       PHP_HTTP_E_REQUEST_POOL,
+       PHP_HTTP_E_SOCKET,
+       PHP_HTTP_E_RESPONSE,
+       PHP_HTTP_E_URL,
+       PHP_HTTP_E_QUERYSTRING,
+       PHP_HTTP_E_COOKIE,
+} php_http_error_t;
+
+/* CURL */
+
+#define PHP_HTTP_CURL_OPT(OPTION, p) curl_easy_setopt((request->ch), OPTION, (p))
+
+#define PHP_HTTP_CURL_OPT_STRING(OPTION, ldiff, obdc) \
+       { \
+               char *K = #OPTION; \
+               PHP_HTTP_CURL_OPT_STRING_EX(K+lenof("CURLOPT_KEY")+ldiff, OPTION, obdc); \
+       }
+#define PHP_HTTP_CURL_OPT_STRING_EX(keyname, optname, obdc) \
+       if (!strcasecmp(key.str, keyname)) { \
+               zval *copy = php_http_request_option_cache(request, keyname, strlen(keyname)+1, 0, php_http_zsep(IS_STRING, *param)); \
+               if (obdc) { \
+                       if (SUCCESS != php_check_open_basedir(Z_STRVAL_P(copy) TSRMLS_CC)) { \
+                               return FAILURE; \
+                       } \
+               } \
+               PHP_HTTP_CURL_OPT(optname, Z_STRVAL_P(copy)); \
+               zval_ptr_dtor(&copy); \
+               continue; \
+       }
+#define PHP_HTTP_CURL_OPT_LONG(OPTION, ldiff) \
+       { \
+               char *K = #OPTION; \
+               PHP_HTTP_CURL_OPT_LONG_EX(K+lenof("CURLOPT_KEY")+ldiff, OPTION); \
+       }
+#define PHP_HTTP_CURL_OPT_LONG_EX(keyname, optname) \
+       if (!strcasecmp(key.str, keyname)) { \
+               zval *copy = php_http_zsep(IS_LONG, *param); \
+               PHP_HTTP_CURL_OPT(optname, Z_LVAL_P(copy)); \
+               zval_ptr_dtor(&copy); \
+               continue; \
+       }
+
+#endif
+
+/*
+ * 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_negotiate.c b/php_http_negotiate.c
new file mode 100644 (file)
index 0000000..43b4236
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_headers_api.h 300300 2010-06-09 07:29:35Z mike $ */
+
+#include "php_http.h"
+
+#ifndef PHP_HTTP_DBG_NEG
+#      define PHP_HTTP_DBG_NEG 0
+#endif
+
+char *php_http_negotiate_language_func(const char *test, double *quality, HashTable *supported TSRMLS_DC)
+{
+       zval **value;
+       HashPosition pos;
+       const char *dash_test;
+
+       FOREACH_HASH_VAL(pos, supported, value) {
+#if PHP_HTTP_DBG_NEG
+               fprintf(stderr, "strcasecmp('%s', '%s')\n", Z_STRVAL_PP(value), test);
+#endif
+               if (!strcasecmp(Z_STRVAL_PP(value), test)) {
+                       return Z_STRVAL_PP(value);
+               }
+       }
+
+       /* no distinct match found, so try primaries */
+       if ((dash_test = strchr(test, '-'))) {
+               FOREACH_HASH_VAL(pos, supported, value) {
+                       int len = dash_test - test;
+#if PHP_HTTP_DBG_NEG
+                       fprintf(stderr, "strncasecmp('%s', '%s', %d)\n", Z_STRVAL_PP(value), test, len);
+#endif
+                       if (    (!strncasecmp(Z_STRVAL_PP(value), test, len)) &&
+                                       (       (Z_STRVAL_PP(value)[len] == '\0') ||
+                                               (Z_STRVAL_PP(value)[len] == '-'))) {
+                               *quality *= .9;
+                               return Z_STRVAL_PP(value);
+                       }
+               }
+       }
+
+       return NULL;
+}
+
+
+char *php_http_negotiate_default_func(const char *test, double *quality, HashTable *supported TSRMLS_DC)
+{
+       zval **value;
+       HashPosition pos;
+       (void) quality;
+
+       FOREACH_HASH_VAL(pos, supported, value) {
+#if PHP_HTTP_DBG_NEG
+               fprintf(stderr, "strcasecmp('%s', '%s')\n", Z_STRVAL_PP(value), test);
+#endif
+               if (!strcasecmp(Z_STRVAL_PP(value), test)) {
+                       return Z_STRVAL_PP(value);
+               }
+       }
+
+       return NULL;
+}
+
+
+static int php_http_negotiate_sort(const void *a, const void *b TSRMLS_DC)
+{
+       zval result, *first, *second;
+
+       first = *((zval **) (*((Bucket **) a))->pData);
+       second= *((zval **) (*((Bucket **) b))->pData);
+
+       if (numeric_compare_function(&result, first, second TSRMLS_CC) != SUCCESS) {
+               return 0;
+       }
+       return (Z_LVAL(result) > 0 ? -1 : (Z_LVAL(result) < 0 ? 1 : 0));
+}
+
+
+PHP_HTTP_API HashTable *php_http_negotiate(const char *value, HashTable *supported, php_http_negotiate_func_t neg TSRMLS_DC)
+{
+       HashTable *result = NULL;
+
+       if (*value) {
+               zval ex_arr, ex_del, ex_val;
+
+               INIT_PZVAL(&ex_del);
+               INIT_PZVAL(&ex_arr);
+               INIT_PZVAL(&ex_val);
+               ZVAL_STRINGL(&ex_del, ",", 1, 0);
+               ZVAL_STRING(&ex_val, value, 1);
+               array_init(&ex_arr);
+
+               php_explode(&ex_del, &ex_val, &ex_arr, INT_MAX);
+
+               if (zend_hash_num_elements(Z_ARRVAL(ex_arr)) > 0) {
+                       int i = 0;
+                       HashPosition pos;
+                       zval **entry, array;
+
+                       INIT_PZVAL(&array);
+                       array_init(&array);
+
+                       if (!neg) {
+                               neg = php_http_negotiate_default_func;
+                       }
+
+                       FOREACH_HASH_VAL(pos, Z_ARRVAL(ex_arr), entry) {
+                               int ident_len;
+                               double quality;
+                               char *selected, *identifier, *freeme;
+                               const char *separator;
+
+#if PHP_HTTP_DBG_NEG
+                               fprintf(stderr, "Checking %s\n", Z_STRVAL_PP(entry));
+#endif
+
+                               if ((separator = strchr(Z_STRVAL_PP(entry), ';'))) {
+                                       const char *ptr = separator;
+
+                                       while (*++ptr && !PHP_HTTP_IS_CTYPE(digit, *ptr) && '.' != *ptr);
+
+                                       quality = zend_strtod(ptr, NULL);
+                                       identifier = estrndup(Z_STRVAL_PP(entry), ident_len = separator - Z_STRVAL_PP(entry));
+                               } else {
+                                       quality = 1000.0 - i++;
+                                       identifier = estrndup(Z_STRVAL_PP(entry), ident_len = Z_STRLEN_PP(entry));
+                               }
+                               freeme = identifier;
+
+                               while (PHP_HTTP_IS_CTYPE(space, *identifier)) {
+                                       ++identifier;
+                                       --ident_len;
+                               }
+                               while (ident_len && PHP_HTTP_IS_CTYPE(space, identifier[ident_len - 1])) {
+                                       identifier[--ident_len] = '\0';
+                               }
+
+                               if ((selected = neg(identifier, &quality, supported TSRMLS_CC))) {
+                                       /* don't overwrite previously set with higher quality */
+                                       if (!zend_hash_exists(Z_ARRVAL(array), selected, strlen(selected) + 1)) {
+                                               add_assoc_double(&array, selected, quality);
+                                       }
+                               }
+
+                               efree(freeme);
+                       }
+
+                       result = Z_ARRVAL(array);
+                       zend_hash_sort(result, zend_qsort, php_http_negotiate_sort, 0 TSRMLS_CC);
+               }
+
+               zval_dtor(&ex_arr);
+               zval_dtor(&ex_val);
+       }
+       
+       return result;
+}
+
+
+
+/*
+ * 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_negotiate.h b/php_http_negotiate.h
new file mode 100644 (file)
index 0000000..d8814fa
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_headers_api.h 300300 2010-06-09 07:29:35Z mike $ */
+
+#ifndef PHP_HTTP_NEGOTIATE_H
+#define PHP_HTTP_NEGOTIATE_H
+
+typedef char *(*php_http_negotiate_func_t)(const char *test, double *quality, HashTable *supported TSRMLS_DC);
+
+extern char *php_http_negotiate_language_func(const char *test, double *quality, HashTable *supported TSRMLS_DC);
+extern char *php_http_negotiate_default_func(const char *test, double *quality, HashTable *supported TSRMLS_DC);
+
+PHP_HTTP_API HashTable *php_http_negotiate(const char *value, HashTable *supported, php_http_negotiate_func_t neg TSRMLS_DC);
+
+static inline HashTable *php_http_negotiate_language(HashTable *supported TSRMLS_DC)
+{
+       HashTable *result = NULL;
+       char *value = php_http_env_get_request_header(ZEND_STRL("Accept-Language") TSRMLS_CC);
+
+       if (value) {
+               result = php_http_negotiate(value, supported, php_http_negotiate_language_func TSRMLS_CC);
+       }
+       STR_FREE(value);
+
+       return result;
+}
+
+static inline HashTable *php_http_negotiate_encoding(HashTable *supported TSRMLS_DC)
+{
+       HashTable *result = NULL;
+       char *value = php_http_env_get_request_header(ZEND_STRL("Accept-Encoding") TSRMLS_CC);
+
+       if (value) {
+               result = php_http_negotiate(value, supported, NULL TSRMLS_CC);
+       }
+       STR_FREE(value);
+
+       return result;
+}
+
+static inline HashTable *php_http_negotiate_charset(HashTable *supported TSRMLS_DC)
+{
+       HashTable *result = NULL;
+       char *value = php_http_env_get_request_header(ZEND_STRL("Accept-Charset") TSRMLS_CC);
+
+       if (value) {
+               result = php_http_negotiate(value, supported, NULL TSRMLS_CC);
+       }
+       STR_FREE(value);
+
+       return result;
+}
+
+static inline HashTable *php_http_negotiate_content_type(HashTable *supported TSRMLS_DC)
+{
+       HashTable *result = NULL;
+       char *value = php_http_env_get_request_header(ZEND_STRL("Accept") TSRMLS_CC);
+
+       if (value) {
+               result = php_http_negotiate(value, supported, NULL TSRMLS_CC);
+       }
+       STR_FREE(value);
+
+       return result;
+}
+
+
+#endif
+
+/*
+ * 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_object.c b/php_http_object.c
new file mode 100644 (file)
index 0000000..5a1a279
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_api.c 300299 2010-06-09 06:23:16Z mike $ */
+
+
+#include "php_http.h"
+
+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)
+{
+       if (!ce) {
+               ce = parent_ce;
+       } else if (parent_ce && !instanceof_function(ce, parent_ce TSRMLS_CC)) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "Class %s does not extend %s", ce->name, parent_ce->name);
+               return FAILURE;
+       }
+
+       *ov = create(ce, intern_ptr, obj_ptr TSRMLS_CC);
+       return SUCCESS;
+}
+
+PHP_HTTP_API zend_error_handling_t php_http_object_get_error_handling(zval *object TSRMLS_DC)
+{
+       zval *zeh, *lzeh;
+       long eh;
+
+       zeh = zend_read_property(Z_OBJCE_P(object), object, ZEND_STRL("errorHandling"), 0 TSRMLS_CC);
+       if (Z_TYPE_P(zeh) != IS_NULL) {
+               lzeh = php_http_zsep(IS_LONG, zeh);
+               eh = Z_LVAL_P(lzeh);
+               zval_ptr_dtor(&lzeh);
+               return eh;
+       }
+       zeh = zend_read_static_property(php_http_object_class_entry, ZEND_STRL("defaultErrorHandling"), 0 TSRMLS_CC);
+       if (Z_TYPE_P(zeh) != IS_NULL) {
+               lzeh = php_http_zsep(IS_LONG, zeh);
+               eh = Z_LVAL_P(lzeh);
+               zval_ptr_dtor(&lzeh);
+               return eh;
+       }
+       return EH_NORMAL;
+}
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)                  PHP_HTTP_BEGIN_ARGS_EX(HttpObject, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                                            PHP_HTTP_EMPTY_ARGS_EX(HttpObject, method, 0)
+#define PHP_HTTP_OBJECT_ME(method, visibility)                 PHP_ME(HttpObject, method, PHP_HTTP_ARGS(HttpObject, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(factory, 1)
+       PHP_HTTP_ARG_VAL(class_name, 0)
+       PHP_HTTP_ARG_VAL(ctor_args, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(setErrorHandling, 1)
+       PHP_HTTP_ARG_VAL(eh, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getErrorHandling);
+
+PHP_HTTP_BEGIN_ARGS(setDefaultErrorHandling, 1)
+       PHP_HTTP_ARG_VAL(eh, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getDefaultErrorHandling);
+
+zend_class_entry *php_http_object_class_entry;
+zend_function_entry php_http_object_method_entry[] = {
+       PHP_HTTP_OBJECT_ME(factory, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+
+       PHP_HTTP_OBJECT_ME(setErrorHandling, ZEND_ACC_PUBLIC)
+       PHP_HTTP_OBJECT_ME(getErrorHandling, ZEND_ACC_PUBLIC)
+       PHP_HTTP_OBJECT_ME(setDefaultErrorHandling, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+       PHP_HTTP_OBJECT_ME(getDefaultErrorHandling, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+       
+       EMPTY_FUNCTION_ENTRY
+};
+
+zend_object_value php_http_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_object_new_ex(ce, NULL, NULL TSRMLS_CC);
+}
+
+zend_object_value php_http_object_new_ex(zend_class_entry *ce, void *nothing, php_http_object_t **ptr TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_object_t *o;
+
+       o = ecalloc(1, sizeof(php_http_object_t));
+       zend_object_std_init((zend_object *)o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       if (ptr) {
+               *ptr = o;
+       }
+
+       ov.handle = zend_objects_store_put(o, NULL, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC);
+       ov.handlers = zend_get_std_object_handlers();
+
+       return ov;
+}
+
+PHP_METHOD(HttpObject, factory)
+{
+       zval *ctor_args = NULL;
+       zend_class_entry *class_entry = NULL;
+       zval *object_ctor;
+       zend_fcall_info fci;
+       zend_fcall_info_cache fcc;
+
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "C|a/!", &class_entry, &ctor_args)) {
+                       object_init_ex(return_value, class_entry);
+
+                       MAKE_STD_ZVAL(object_ctor);
+                       array_init(object_ctor);
+
+                       Z_ADDREF_P(return_value);
+                       add_next_index_zval(object_ctor, return_value);
+                       add_next_index_stringl(object_ctor, ZEND_STRL("__construct"), 1);
+
+                       zend_fcall_info_init(object_ctor, 0, &fci, &fcc, NULL, NULL TSRMLS_CC);
+                       zend_fcall_info_call(&fci, &fcc, NULL, ctor_args TSRMLS_CC);
+
+                       zval_ptr_dtor(&object_ctor);
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpObject, getErrorHandling)
+{
+       RETURN_PROP(php_http_object_class_entry, "errorHandling");
+}
+
+PHP_METHOD(HttpObject, setErrorHandling)
+{
+       long eh;
+       zval *old;
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &eh)) {
+               RETURN_FALSE;
+       }
+
+       switch (eh) {
+               case EH_NORMAL:
+               case EH_SUPPRESS:
+               case EH_THROW:
+                       break;
+               default:
+                       php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "unknown error handling code (%ld)", eh);
+                       RETURN_FALSE;
+       }
+       
+       old = zend_read_property(php_http_object_class_entry, getThis(), ZEND_STRL("errorHandling"), 0 TSRMLS_CC);
+       Z_ADDREF_P(old);
+       zend_update_property_long(php_http_object_class_entry, getThis(), ZEND_STRL("errorHandling"), eh TSRMLS_CC);
+       RETURN_ZVAL(old, 0, 0);
+}
+
+PHP_METHOD(HttpObject, getDefaultErrorHandling)
+{
+       RETURN_SPROP(php_http_object_class_entry, "defaultErrorHandling");
+}
+
+PHP_METHOD(HttpObject, setDefaultErrorHandling)
+{
+       long eh;
+       zval *old;
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &eh)) {
+               RETURN_FALSE;
+       }
+
+       switch (eh) {
+               case EH_NORMAL:
+               case EH_SUPPRESS:
+               case EH_THROW:
+                       break;
+               default:
+                       php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "unknown error handling code (%ld)", eh);
+                       RETURN_FALSE;
+       }
+
+       old = zend_read_static_property(php_http_object_class_entry, ZEND_STRL("defaultErrorHandling"), 0 TSRMLS_CC);
+       Z_ADDREF_P(old);
+       zend_update_static_property_long(php_http_object_class_entry, ZEND_STRL("defaultErrorHandling"), eh TSRMLS_CC);
+       RETURN_ZVAL(old, 0, 1);
+}
+PHP_MINIT_FUNCTION(http_object)
+{
+       PHP_HTTP_REGISTER_CLASS(http, Object, http_object, NULL, ZEND_ACC_ABSTRACT);
+       php_http_object_class_entry->create_object = php_http_object_new;
+
+       zend_declare_property_null(php_http_object_class_entry, ZEND_STRL("defaultErrorHandling"), (ZEND_ACC_STATIC|ZEND_ACC_PROTECTED) TSRMLS_CC);
+       zend_declare_property_null(php_http_object_class_entry, ZEND_STRL("errorHandling"), ZEND_ACC_PROTECTED TSRMLS_CC);
+
+       zend_declare_class_constant_long(php_http_object_class_entry, ZEND_STRL("EH_NORMAL"), EH_NORMAL TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_object_class_entry, ZEND_STRL("EH_SUPPRESS"), EH_SUPPRESS TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_object_class_entry, ZEND_STRL("EH_THROW"), EH_THROW TSRMLS_CC);
+
+       return SUCCESS;
+}
+
+
+/*
+ * 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_object.h b/php_http_object.h
new file mode 100644 (file)
index 0000000..c4ede0e
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_api.c 300299 2010-06-09 06:23:16Z mike $ */
+
+#ifndef PHP_HTTP_OBJECT_H
+#define PHP_HTTP_OBJECT_H
+
+typedef zend_object_value (*php_http_new_t)(zend_class_entry *ce, void *, void ** TSRMLS_DC);
+
+extern 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);
+
+typedef struct php_http_object {
+       zend_object zo;
+} php_http_object_t;
+
+extern zend_class_entry *php_http_object_class_entry;
+extern zend_function_entry php_http_object_method_entry[];
+
+extern PHP_MINIT_FUNCTION(http_object);
+
+extern zend_object_value php_http_object_new(zend_class_entry *ce TSRMLS_DC);
+extern zend_object_value php_http_object_new_ex(zend_class_entry *ce, void *nothing, php_http_object_t **ptr TSRMLS_DC);
+
+PHP_HTTP_API zend_error_handling_t php_http_object_get_error_handling(zval *object TSRMLS_DC);
+
+PHP_METHOD(HttpObject, factory);
+PHP_METHOD(HttpObject, setErrorHandling);
+PHP_METHOD(HttpObject, getErrorHandling);
+PHP_METHOD(HttpObject, setDefaultErrorHandling);
+PHP_METHOD(HttpObject, getDefaultErrorHandling);
+
+#endif
+
+
+/*
+ * 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_params.c b/php_http_params.c
new file mode 100644 (file)
index 0000000..2cc9a09
--- /dev/null
@@ -0,0 +1,277 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_api.c 300299 2010-06-09 06:23:16Z mike $ */
+
+#include "php_http.h"
+
+PHP_HTTP_API void php_http_params_parse_default_func(void *arg, const char *key, int keylen, const char *val, int vallen TSRMLS_DC)
+{
+       char *kdup;
+       zval tmp, *entry;
+       HashTable *ht = (HashTable *) arg;
+
+       if (ht) {
+               INIT_PZVAL_ARRAY(&tmp, ht);
+
+               if (vallen) {
+                       MAKE_STD_ZVAL(entry);
+                       array_init(entry);
+                       if (keylen) {
+                               kdup = estrndup(key, keylen);
+                               add_assoc_stringl_ex(entry, kdup, keylen + 1, (char *) val, vallen, 1);
+                               efree(kdup);
+                       } else {
+                               add_next_index_stringl(entry, (char *) val, vallen, 1);
+                       }
+                       add_next_index_zval(&tmp, entry);
+               } else {
+                       add_next_index_stringl(&tmp, (char *) key, keylen, 1);
+               }
+       }
+}
+
+PHP_HTTP_API STATUS    php_http_params_parse(const char *param, int flags, php_http_params_parse_func_t cb, void *cb_arg TSRMLS_DC)
+{
+#define ST_QUOTE       1
+#define ST_VALUE       2
+#define ST_KEY         3
+#define ST_ASSIGN      4
+#define ST_ADD         5
+
+       int st = ST_KEY, keylen = 0, vallen = 0;
+       char *s, *c, *key = NULL, *val = NULL;
+
+       if (!cb) {
+               cb = php_http_params_parse_default_func;
+       }
+
+       for(c = s = estrdup(param);;) {
+       continued:
+#if 0
+       {
+               char *tk = NULL, *tv = NULL;
+
+               if (key) {
+                       if (keylen) {
+                               tk= estrndup(key, keylen);
+                       } else {
+                               tk = ecalloc(1, 7);
+                               memcpy(tk, key, 3);
+                               tk[3]='.'; tk[4]='.'; tk[5]='.';
+                       }
+               }
+               if (val) {
+                       if (vallen) {
+                               tv = estrndup(val, vallen);
+                       } else {
+                               tv = ecalloc(1, 7);
+                               memcpy(tv, val, 3);
+                               tv[3]='.'; tv[4]='.'; tv[5]='.';
+                       }
+               }
+               fprintf(stderr, "[%6s] %c \"%s=%s\"\n",
+                               (
+                                               st == ST_QUOTE ? "QUOTE" :
+                                               st == ST_VALUE ? "VALUE" :
+                                               st == ST_KEY ? "KEY" :
+                                               st == ST_ASSIGN ? "ASSIGN" :
+                                               st == ST_ADD ? "ADD":
+                                               "HUH?"
+                               ), *c?*c:'0', tk, tv
+               );
+               STR_FREE(tk); STR_FREE(tv);
+       }
+#endif
+               switch (st) {
+                       case ST_QUOTE:
+                       quote:
+                               if (*c == '"') {
+                                       if (*(c-1) == '\\') {
+                                               memmove(c-1, c, strlen(c)+1);
+                                               goto quote;
+                                       } else {
+                                               goto add;
+                                       }
+                               } else {
+                                       if (!val) {
+                                               val = c;
+                                       }
+                                       if (!*c) {
+                                               --val;
+                                               st = ST_ADD;
+                                       }
+                               }
+                               break;
+
+                       case ST_VALUE:
+                               switch (*c) {
+                                       case '"':
+                                               if (!val) {
+                                                       st = ST_QUOTE;
+                                               }
+                                               break;
+
+                                       case ' ':
+                                               break;
+
+                                       case ';':
+                                       case '\0':
+                                               goto add;
+                                               break;
+                                       case ',':
+                                               if (flags & PHP_HTTP_PARAMS_ALLOW_COMMA) {
+                                                       goto add;
+                                               }
+                                       default:
+                                               if (!val) {
+                                                       val = c;
+                                               }
+                                               break;
+                               }
+                               break;
+
+                       case ST_KEY:
+                               switch (*c) {
+                                       case ',':
+                                               if (flags & PHP_HTTP_PARAMS_ALLOW_COMMA) {
+                                                       goto allow_comma;
+                                               }
+                                       case '\r':
+                                       case '\n':
+                                       case '\t':
+                                       case '\013':
+                                       case '\014':
+                                               goto failure;
+                                               break;
+
+                                       case ' ':
+                                               if (key) {
+                                                       keylen = c - key;
+                                                       st = ST_ASSIGN;
+                                               }
+                                               break;
+
+                                       case ';':
+                                       case '\0':
+                                       allow_comma:
+                                               if (key) {
+                                                       keylen = c-- - key;
+                                                       st = ST_ADD;
+                                               }
+                                               break;
+
+                                       case ':':
+                                               if (!(flags & PHP_HTTP_PARAMS_COLON_SEPARATOR)) {
+                                                       goto not_separator;
+                                               }
+                                               if (key) {
+                                                       keylen = c - key;
+                                                       st = ST_VALUE;
+                                               } else {
+                                                       goto failure;
+                                               }
+                                               break;
+
+                                       case '=':
+                                               if (flags & PHP_HTTP_PARAMS_COLON_SEPARATOR) {
+                                                       goto not_separator;
+                                               }
+                                               if (key) {
+                                                       keylen = c - key;
+                                                       st = ST_VALUE;
+                                               } else {
+                                                       goto failure;
+                                               }
+                                               break;
+
+                                       default:
+                                       not_separator:
+                                               if (!key) {
+                                                       key = c;
+                                               }
+                                               break;
+                               }
+                               break;
+
+                       case ST_ASSIGN:
+                               if (*c == '=') {
+                                       st = ST_VALUE;
+                               } else if (!*c || *c == ';' || ((flags & PHP_HTTP_PARAMS_ALLOW_COMMA) && *c == ',')) {
+                                       st = ST_ADD;
+                               } else if (*c != ' ') {
+                                       goto failure;
+                               }
+                               break;
+
+                       case ST_ADD:
+                       add:
+                               if (val) {
+                                       vallen = c - val;
+                                       if (st != ST_QUOTE) {
+                                               while (val[vallen-1] == ' ') --vallen;
+                                       }
+                               } else {
+                                       val = "";
+                                       vallen = 0;
+                               }
+
+                               cb(cb_arg, key, keylen, val, vallen TSRMLS_CC);
+
+                               st = ST_KEY;
+                               key = val = NULL;
+                               keylen = vallen = 0;
+                               break;
+               }
+               if (*c) {
+                       ++c;
+               } else if (st == ST_ADD) {
+                       goto add;
+               } else {
+                       break;
+               }
+       }
+
+       efree(s);
+       return SUCCESS;
+
+failure:
+       if (flags & PHP_HTTP_PARAMS_RAISE_ERROR) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Unexpected character (%c) at pos %tu of %zu", *c, c-s, strlen(s));
+       }
+       if (flags & PHP_HTTP_PARAMS_ALLOW_FAILURE) {
+               if (st == ST_KEY) {
+                       if (key) {
+                               keylen = c - key;
+                       } else {
+                               key = c;
+                       }
+               } else {
+                       --c;
+               }
+               st = ST_ADD;
+               goto continued;
+       }
+       efree(s);
+       return FAILURE;
+}
+
+
+/*
+ * 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_params.h b/php_http_params.h
new file mode 100644 (file)
index 0000000..c570017
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_api.h 298891 2010-05-03 08:26:38Z mike $ */
+
+#ifndef PHP_HTTP_PARAMS_H
+#define PHP_HTTP_PARAMS_H
+
+#define PHP_HTTP_PARAMS_ALLOW_COMMA                    0x01
+#define PHP_HTTP_PARAMS_ALLOW_FAILURE          0x02
+#define PHP_HTTP_PARAMS_RAISE_ERROR                    0x04
+#define PHP_HTTP_PARAMS_DEFAULT        (PHP_HTTP_PARAMS_ALLOW_COMMA|PHP_HTTP_PARAMS_ALLOW_FAILURE|PHP_HTTP_PARAMS_RAISE_ERROR)
+#define PHP_HTTP_PARAMS_COLON_SEPARATOR                0x10
+
+typedef void (*php_http_params_parse_func_t)(void *cb_arg, const char *key, int keylen, const char *val, int vallen TSRMLS_DC);
+
+PHP_HTTP_API void php_http_params_parse_default_func(void *ht, const char *key, int keylen, const char *val, int vallen TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_params_parse(const char *params, int flags, php_http_params_parse_func_t cb, void *cb_arg TSRMLS_DC);
+
+#endif
+
+/*
+ * 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_persistent_handle.c b/php_http_persistent_handle.c
new file mode 100644 (file)
index 0000000..a5f9689
--- /dev/null
@@ -0,0 +1,385 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_persistent_handle_api.c 292841 2009-12-31 08:48:57Z mike $ */
+
+#include "php_http.h"
+
+#ifndef PHP_HTTP_DEBUG_PHANDLES
+#      define PHP_HTTP_DEBUG_PHANDLES 0
+#endif
+#if PHP_HTTP_DEBUG_PHANDLES
+#      undef inline
+#      define inline
+#endif
+
+static HashTable php_http_persistent_handles_hash;
+#ifdef ZTS
+#      define LOCK() tsrm_mutex_lock(php_http_persistent_handles_lock)
+#      define UNLOCK() tsrm_mutex_unlock(php_http_persistent_handles_lock)
+static MUTEX_T php_http_persistent_handles_lock;
+#else
+#      define LOCK()
+#      define UNLOCK()
+#endif
+
+typedef struct php_http_persistent_handle_list {
+       HashTable free;
+       ulong used;
+} php_http_persistent_handle_list_t;
+
+typedef struct php_http_persistent_handle_provider {
+       php_http_persistent_handle_list_t list; /* "ident" => array(handles) entries */
+       php_http_persistent_handle_ctor_t ctor;
+       php_http_persistent_handle_dtor_t dtor;
+       php_http_persistent_handle_copy_t copy;
+} php_http_persistent_handle_provider_t;
+
+static inline php_http_persistent_handle_list_t *php_http_persistent_handle_list_init(php_http_persistent_handle_list_t *list)
+{
+       int free_list;
+       
+       if ((free_list = !list)) {
+               list = pemalloc(sizeof(php_http_persistent_handle_list_t), 1);
+       }
+       
+       list->used = 0;
+       
+       if (SUCCESS != zend_hash_init(&list->free, 0, NULL, NULL, 1)) {
+               if (free_list) {
+                       pefree(list, 1);
+               }
+               list = NULL;
+       }
+       
+       return list;
+}
+
+static inline void php_http_persistent_handle_list_dtor(php_http_persistent_handle_list_t *list, php_http_persistent_handle_dtor_t dtor)
+{
+       HashPosition pos;
+       void **handle;
+       
+#if PHP_HTTP_DEBUG_PHANDLES
+       fprintf(stderr, "LSTDTOR: %p\n", list);
+#endif
+       FOREACH_HASH_VAL(pos, &list->free, handle) {
+#if PHP_HTTP_DEBUG_PHANDLES
+               fprintf(stderr, "DESTROY: %p\n", *handle);
+#endif
+               
+               dtor(*handle);
+       }
+       zend_hash_destroy(&list->free);
+}
+
+static inline void php_http_persistent_handle_list_free(php_http_persistent_handle_list_t **list, php_http_persistent_handle_dtor_t dtor)
+{
+       php_http_persistent_handle_list_dtor(*list, dtor);
+#if PHP_HTTP_DEBUG_PHANDLES
+       fprintf(stderr, "LSTFREE: %p\n", *list);
+#endif
+       pefree(*list, 1);
+       *list = NULL;
+}
+
+static inline php_http_persistent_handle_list_t *php_http_persistent_handle_list_find(php_http_persistent_handle_provider_t *provider TSRMLS_DC)
+{
+       php_http_persistent_handle_list_t **list, *new_list;
+       
+       if (SUCCESS == zend_hash_quick_find(&provider->list.free, PHP_HTTP_G->persistent_handle.ident.s, PHP_HTTP_G->persistent_handle.ident.l, PHP_HTTP_G->persistent_handle.ident.h, (void *) &list)) {
+#if PHP_HTTP_DEBUG_PHANDLES
+               fprintf(stderr, "LSTFIND: %p\n", *list);
+#endif
+               return *list;
+       }
+       
+       if ((new_list = php_http_persistent_handle_list_init(NULL))) {
+               if (SUCCESS == zend_hash_quick_add(&provider->list.free, PHP_HTTP_G->persistent_handle.ident.s, PHP_HTTP_G->persistent_handle.ident.l, PHP_HTTP_G->persistent_handle.ident.h, (void *) &new_list, sizeof(php_http_persistent_handle_list_t *), (void *) &list)) {
+#if PHP_HTTP_DEBUG_PHANDLES
+                       fprintf(stderr, "LSTFIND: %p (new)\n", *list);
+#endif
+                       return *list;
+               }
+               php_http_persistent_handle_list_free(&new_list, provider->dtor);
+       }
+       
+       return NULL;
+}
+
+static inline STATUS php_http_persistent_handle_do_acquire(php_http_persistent_handle_provider_t *provider, void **handle TSRMLS_DC)
+{
+       ulong index;
+       void **handle_ptr;
+       php_http_persistent_handle_list_t *list;
+       
+       if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) {
+               zend_hash_internal_pointer_end(&list->free);
+               if (HASH_KEY_NON_EXISTANT != zend_hash_get_current_key(&list->free, NULL, &index, 0) && SUCCESS == zend_hash_get_current_data(&list->free, (void *) &handle_ptr)) {
+                       *handle = *handle_ptr;
+                       zend_hash_index_del(&list->free, index);
+               } else {
+                       *handle = provider->ctor();
+               }
+               
+               if (*handle) {
+                       ++provider->list.used;
+                       ++list->used;
+                       return SUCCESS;
+               }
+       } else {
+               *handle = NULL;
+       }
+       
+       return FAILURE;
+}
+
+static inline STATUS php_http_persistent_handle_do_release(php_http_persistent_handle_provider_t *provider, void **handle TSRMLS_DC)
+{
+       php_http_persistent_handle_list_t *list;
+       
+       if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) {
+               if (provider->list.used >= PHP_HTTP_G->persistent_handle.limit) {
+                       provider->dtor(*handle);
+               } else {
+                       if (SUCCESS != zend_hash_next_index_insert(&list->free, (void *) handle, sizeof(void *), NULL)) {
+                               return FAILURE;
+                       }
+               }
+               
+               *handle = NULL;
+               --provider->list.used;
+               --list->used;
+               return SUCCESS;
+       }
+       
+       return FAILURE;
+}
+
+static inline STATUS php_http_persistent_handle_do_accrete(php_http_persistent_handle_provider_t *provider, void *old_handle, void **new_handle TSRMLS_DC)
+{
+       php_http_persistent_handle_list_t *list;
+       
+       if (provider->copy && (*new_handle = provider->copy(old_handle))) {
+               if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) {
+                       ++list->used;
+               }
+               ++provider->list.used;
+               return SUCCESS;
+       }
+       return FAILURE;
+}
+
+static void php_http_persistent_handles_hash_dtor(void *p)
+{
+       php_http_persistent_handle_provider_t *provider = (php_http_persistent_handle_provider_t *) p;
+       php_http_persistent_handle_list_t **list, *list_tmp;
+       HashPosition pos;
+       
+       FOREACH_HASH_VAL(pos, &provider->list.free, list) {
+               /* fix shutdown crash in PHP4 */
+               list_tmp = *list;
+               php_http_persistent_handle_list_free(&list_tmp, provider->dtor);
+       }
+       
+       zend_hash_destroy(&provider->list.free);
+}
+
+PHP_MINIT_FUNCTION(http_persistent_handle)
+{
+       zend_hash_init(&php_http_persistent_handles_hash, 0, NULL, php_http_persistent_handles_hash_dtor, 1);
+#ifdef ZTS
+       php_http_persistent_handles_lock = tsrm_mutex_alloc();
+#endif
+       return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(http_persistent_handle)
+{
+       zend_hash_destroy(&php_http_persistent_handles_hash);
+#ifdef ZTS
+       tsrm_mutex_free(php_http_persistent_handles_lock);
+#endif
+       return SUCCESS;
+}
+
+PHP_HTTP_API STATUS php_http_persistent_handle_provide(const char *name_str, size_t name_len, php_http_persistent_handle_ctor_t ctor, php_http_persistent_handle_dtor_t dtor, php_http_persistent_handle_copy_t copy)
+{
+       STATUS status = FAILURE;
+       php_http_persistent_handle_provider_t provider;
+       
+       LOCK();
+       if (php_http_persistent_handle_list_init(&provider.list)) {
+               provider.ctor = ctor;
+               provider.dtor = dtor;
+               provider.copy = copy;
+               
+#if PHP_HTTP_DEBUG_PHANDLES
+               fprintf(stderr, "PROVIDE: %s\n", name_str);
+#endif
+               
+               if (SUCCESS == zend_hash_add(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider, sizeof(php_http_persistent_handle_provider_t), NULL)) {
+                       status = SUCCESS;
+               }
+       }
+       UNLOCK();
+       
+       return status;
+}
+
+PHP_HTTP_API STATUS php_http_persistent_handle_acquire(const char *name_str, size_t name_len, void **handle TSRMLS_DC)
+{
+       STATUS status = FAILURE;
+       php_http_persistent_handle_provider_t *provider;
+       
+       *handle = NULL;
+       LOCK();
+       if (SUCCESS == zend_hash_find(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider)) {
+               status = php_http_persistent_handle_do_acquire(provider, handle TSRMLS_CC);
+       }
+       UNLOCK();
+       
+#if PHP_HTTP_DEBUG_PHANDLES
+       fprintf(stderr, "ACQUIRE: %p (%s)\n", *handle, name_str);
+#endif
+       
+       return status;
+}
+
+PHP_HTTP_API STATUS php_http_persistent_handle_release(const char *name_str, size_t name_len, void **handle TSRMLS_DC)
+{
+       STATUS status = FAILURE;
+       php_http_persistent_handle_provider_t *provider;
+#if PHP_HTTP_DEBUG_PHANDLES
+       void *handle_tmp = *handle;
+#endif
+       
+       LOCK();
+       if (SUCCESS == zend_hash_find(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider)) {
+               status = php_http_persistent_handle_do_release(provider, handle TSRMLS_CC);
+       }
+       UNLOCK();
+       
+#if PHP_HTTP_DEBUG_PHANDLES
+       fprintf(stderr, "RELEASE: %p (%s)\n", handle_tmp, name_str);
+#endif
+       
+       return status;
+}
+
+PHP_HTTP_API STATUS php_http_persistent_handle_accrete(const char *name_str, size_t name_len, void *old_handle, void **new_handle TSRMLS_DC)
+{
+       STATUS status = FAILURE;
+       php_http_persistent_handle_provider_t *provider;
+       
+       *new_handle = NULL;
+       LOCK();
+       if (SUCCESS == zend_hash_find(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider)) {
+               status = php_http_persistent_handle_do_accrete(provider, old_handle, new_handle TSRMLS_CC);
+       }
+       UNLOCK();
+       
+#if PHP_HTTP_DEBUG_PHANDLES
+       fprintf(stderr, "ACCRETE: %p > %p (%s)\n", old_handle, *new_handle, name_str);
+#endif
+       
+       return status;
+}
+
+PHP_HTTP_API void php_http_persistent_handle_cleanup(const char *name_str, size_t name_len, int current_ident_only TSRMLS_DC)
+{
+       php_http_persistent_handle_provider_t *provider;
+       php_http_persistent_handle_list_t *list, **listp;
+       HashPosition pos1, pos2;
+       
+       LOCK();
+       if (name_str && name_len) {
+               if (SUCCESS == zend_hash_find(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider)) {
+                       if (current_ident_only) {
+                               if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) {
+                                       php_http_persistent_handle_list_dtor(list, provider->dtor);
+                                       php_http_persistent_handle_list_init(list);
+                               }
+                       } else {
+                               FOREACH_HASH_VAL(pos1, &provider->list.free, listp) {
+                                       php_http_persistent_handle_list_dtor(*listp, provider->dtor);
+                                       php_http_persistent_handle_list_init(*listp);
+                               }
+                       }
+               }
+       } else {
+               FOREACH_HASH_VAL(pos1, &php_http_persistent_handles_hash, provider) {
+                       if (current_ident_only) {
+                               if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) {
+                                       php_http_persistent_handle_list_dtor(list, provider->dtor);
+                                       php_http_persistent_handle_list_init(list);
+                               }
+                       } else {
+                               FOREACH_HASH_VAL(pos2, &provider->list.free, listp) {
+                                       php_http_persistent_handle_list_dtor(*listp, provider->dtor);
+                                       php_http_persistent_handle_list_init(*listp);
+                               }
+                       }
+               }
+       }
+       UNLOCK();
+}
+
+PHP_HTTP_API HashTable *php_http_persistent_handle_statall(HashTable *ht TSRMLS_DC)
+{
+       zval *zentry[2];
+       HashPosition pos1, pos2;
+       php_http_array_hashkey_t key1 = php_http_array_hashkey_init(0), key2 = php_http_array_hashkey_init(0);
+       php_http_persistent_handle_provider_t *provider;
+       php_http_persistent_handle_list_t **list;
+       
+       LOCK();
+       if (zend_hash_num_elements(&php_http_persistent_handles_hash)) {
+               if (!ht) {
+                       ALLOC_HASHTABLE(ht);
+                       zend_hash_init(ht, 0, NULL, ZVAL_PTR_DTOR, 0);
+               }
+               
+               FOREACH_HASH_KEYVAL(pos1, &php_http_persistent_handles_hash, key1, provider) {
+                       MAKE_STD_ZVAL(zentry[0]);
+                       array_init(zentry[0]);
+                       
+                       FOREACH_HASH_KEYVAL(pos2, &provider->list.free, key2, list) {
+                               MAKE_STD_ZVAL(zentry[1]);
+                               array_init(zentry[1]);
+                               add_assoc_long_ex(zentry[1], ZEND_STRS("used"), (*list)->used);
+                               add_assoc_long_ex(zentry[1], ZEND_STRS("free"), zend_hash_num_elements(&(*list)->free));
+                               
+                               /* use zend_hash_* not add_assoc_* (which is zend_symtable_*) as we want a string even for numbers */
+                               zend_hash_add(Z_ARRVAL_P(zentry[0]), key2.str, key2.len, &zentry[1], sizeof(zval *), NULL);
+                       }
+                       
+                       zend_hash_add(ht, key1.str, key1.len, &zentry[0], sizeof(zval *), NULL);
+               }
+       } else if (ht) {
+               ht = NULL;
+       }
+       UNLOCK();
+       
+       return ht;
+}
+
+
+/*
+ * 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_persistent_handle.h b/php_http_persistent_handle.h
new file mode 100644 (file)
index 0000000..075fe92
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_persistent_handle_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_PERSISTENT_HANDLE_H
+#define PHP_HTTP_PERSISTENT_HANDLE_H
+
+typedef void *(*php_http_persistent_handle_ctor_t)(void);
+typedef void (*php_http_persistent_handle_dtor_t)(void *handle);
+typedef void *(*php_http_persistent_handle_copy_t)(void *handle);
+
+struct php_http_persistent_handle_globals {
+       ulong limit;
+       struct {
+               ulong h;
+               char *s;
+               size_t l;
+       } ident;
+};
+
+PHP_MINIT_FUNCTION(http_persistent_handle);
+PHP_MSHUTDOWN_FUNCTION(http_persistent_handle);
+
+PHP_HTTP_API STATUS php_http_persistent_handle_provide(const char *name_str, size_t name_len, php_http_persistent_handle_ctor_t ctor, php_http_persistent_handle_dtor_t dtor, php_http_persistent_handle_copy_t copy);
+PHP_HTTP_API void php_http_persistent_handle_cleanup(const char *name_str, size_t name_len, int current_ident_only TSRMLS_DC);
+PHP_HTTP_API HashTable *php_http_persistent_handle_statall(HashTable *ht TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_persistent_handle_acquire(const char *name_str, size_t name_len, void **handle TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_persistent_handle_release(const char *name_str, size_t name_len, void **handle TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_persistent_handle_accrete(const char *name_str, size_t name_len, void *old_handle, void **new_handle TSRMLS_DC);
+
+#endif /* PHP_HTTP_PERSISTENT_HANDLE_H */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/php_http_property_proxy.c b/php_http_property_proxy.c
new file mode 100644 (file)
index 0000000..196ccce
--- /dev/null
@@ -0,0 +1,189 @@
+
+#include "php_http.h"
+
+
+php_http_property_proxy_t *php_http_property_proxy_init(php_http_property_proxy_t *proxy, zval *object, zval *member TSRMLS_DC)
+{
+       if (!proxy) {
+               proxy = emalloc(sizeof(*proxy));
+       }
+       memset(proxy, 0, sizeof(*proxy));
+
+       MAKE_STD_ZVAL(proxy->myself);
+       ZVAL_OBJVAL(proxy->myself, php_http_property_proxy_object_new_ex(php_http_property_proxy_class_entry, proxy, NULL TSRMLS_CC), 0);
+       Z_ADDREF_P(object);
+       proxy->object = object;
+       proxy->member = php_http_zsep(IS_STRING, member);
+
+       return proxy;
+}
+
+void php_http_property_proxy_dtor(php_http_property_proxy_t *proxy)
+{
+       zval_ptr_dtor(&proxy->object);
+       zval_ptr_dtor(&proxy->member);
+       zval_ptr_dtor(&proxy->myself);
+}
+
+void php_http_property_proxy_free(php_http_property_proxy_t **proxy)
+{
+       if (*proxy) {
+               php_http_property_proxy_dtor(*proxy);
+               efree(*proxy);
+               *proxy = NULL;
+       }
+}
+
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)                  PHP_HTTP_BEGIN_ARGS_EX(HttpPropertyProxy, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                                            PHP_HTTP_EMPTY_ARGS_EX(HttpPropertyProxy, method, 0)
+#define PHP_HTTP_PP_ME(method, visibility)                             PHP_ME(HttpPropertyProxy, method, PHP_HTTP_ARGS(HttpPropertyProxy, method), visibility)
+
+PHP_HTTP_EMPTY_ARGS(__construct);
+
+zend_class_entry *php_http_property_proxy_class_entry;
+zend_function_entry php_http_property_proxy_method_entry[] = {
+       PHP_HTTP_PP_ME(__construct, ZEND_ACC_FINAL|ZEND_ACC_PRIVATE)
+       EMPTY_FUNCTION_ENTRY
+};
+static zend_object_handlers php_http_property_proxy_object_handlers;
+
+zend_object_value php_http_property_proxy_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_property_proxy_object_new_ex(ce, NULL, NULL TSRMLS_CC);
+}
+
+zend_object_value php_http_property_proxy_object_new_ex(zend_class_entry *ce, php_http_property_proxy_t *proxy, php_http_property_proxy_object_t **ptr TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_property_proxy_object_t *o;
+
+       o = ecalloc(1, sizeof(*o));
+       zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       if (ptr) {
+               *ptr = o;
+       }
+       o->proxy = proxy;
+
+       ov.handle = zend_objects_store_put(o, NULL, php_http_property_proxy_object_free, NULL TSRMLS_CC);
+       ov.handlers = &php_http_property_proxy_object_handlers;
+
+       return ov;
+}
+
+void php_http_property_proxy_object_free(void *object TSRMLS_DC)
+{
+       php_http_property_proxy_object_t *o = object;
+
+       if (o->proxy) {
+               php_http_property_proxy_free(&o->proxy);
+       }
+       zend_object_std_dtor((zend_object *) o TSRMLS_CC);
+       efree(o);
+}
+
+static void php_http_property_proxy_object_set(zval **object, zval *value TSRMLS_DC)
+{
+       php_http_property_proxy_object_t *obj = zend_object_store_get_object(*object TSRMLS_CC);
+
+       zend_update_property(Z_OBJCE_P(obj->proxy->object), obj->proxy->object, Z_STRVAL_P(obj->proxy->member), Z_STRLEN_P(obj->proxy->member), value TSRMLS_CC);
+}
+
+static zval *php_http_property_proxy_object_get(zval *object TSRMLS_DC)
+{
+       php_http_property_proxy_object_t *obj = zend_object_store_get_object(object TSRMLS_CC);
+
+       return zend_read_property(Z_OBJCE_P(obj->proxy->object), obj->proxy->object, Z_STRVAL_P(obj->proxy->member), Z_STRLEN_P(obj->proxy->member), 0 TSRMLS_CC);
+}
+
+static STATUS php_http_property_proxy_object_cast(zval *object, zval *return_value, int type TSRMLS_DC)
+{
+       zval *old_value, *new_value;
+
+       old_value = php_http_property_proxy_object_get(object TSRMLS_CC);
+       new_value = php_http_zsep(type, old_value);
+
+       if (old_value != new_value) {
+               zval_ptr_dtor(&old_value);
+       }
+
+       RETVAL_ZVAL(new_value, 0, 0);
+
+       return SUCCESS;
+}
+
+static zval *php_http_property_proxy_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC)
+{
+       zval *retval = NULL, *property = php_http_property_proxy_object_get(object TSRMLS_CC);
+
+       if (Z_TYPE_P(property) == IS_ARRAY) {
+               zval **data = NULL;
+
+               if (Z_TYPE_P(offset) == IS_LONG) {
+                       if (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(property), Z_LVAL_P(offset), (void *) &data))  {
+                               retval = *data;
+                       }
+               } else {
+                       offset = php_http_zsep(IS_STRING, offset);
+                       if (SUCCESS == zend_hash_find(Z_ARRVAL_P(property), Z_STRVAL_P(offset), Z_STRLEN_P(offset), (void *) &data)) {
+                               retval = *data;
+                       }
+                       zval_ptr_dtor(&offset);
+               }
+
+               if (data) {
+                       Z_ADDREF_PP(data);
+               }
+       }
+       zval_ptr_dtor(&property);
+
+       return retval;
+}
+
+static void php_http_property_proxy_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC)
+{
+       zval *property = php_http_property_proxy_object_get(object TSRMLS_CC);
+
+       switch (Z_TYPE_P(property)) {
+               case IS_NULL:
+                       array_init(property);
+               case IS_ARRAY:
+                       Z_ADDREF_P(value);
+                       if (!offset) {
+                               add_next_index_zval(property, value);
+                       } else if (Z_TYPE_P(offset) == IS_LONG) {
+                               add_index_zval(property, Z_LVAL_P(offset), value);
+                       } else {
+                               offset = php_http_zsep(IS_STRING, offset);
+                               add_assoc_zval_ex(property, Z_STRVAL_P(offset), Z_STRLEN_P(offset) + 1, value);
+                               zval_ptr_dtor(&offset);
+                       }
+                       php_http_property_proxy_object_set(&object, property TSRMLS_CC);
+                       break;
+
+               default:
+                       zval_ptr_dtor(&property);
+                       break;
+       }
+}
+
+PHP_METHOD(HttpPropertyProxy, __construct)
+{
+}
+
+PHP_MINIT_FUNCTION(http_property_proxy)
+{
+       PHP_HTTP_REGISTER_CLASS(http\\object, PropertyProxy, http_property_proxy, NULL, ZEND_ACC_FINAL);
+       php_http_property_proxy_class_entry->create_object = php_http_property_proxy_object_new;
+       memcpy(&php_http_property_proxy_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_property_proxy_object_handlers.set = php_http_property_proxy_object_set;
+       php_http_property_proxy_object_handlers.get = php_http_property_proxy_object_get;
+       php_http_property_proxy_object_handlers.cast_object = php_http_property_proxy_object_cast;
+       php_http_property_proxy_object_handlers.read_dimension = php_http_property_proxy_object_read_dimension;
+       php_http_property_proxy_object_handlers.write_dimension = php_http_property_proxy_object_write_dimension;
+
+       return SUCCESS;
+}
+
diff --git a/php_http_property_proxy.h b/php_http_property_proxy.h
new file mode 100644 (file)
index 0000000..bc63aaf
--- /dev/null
@@ -0,0 +1,31 @@
+
+#ifndef PHP_HTTP_PROPERTY_PROXY_H
+#define PHP_HTTP_PROPERTY_PROXY_H
+
+typedef struct php_http_property_proxy {
+       zval *myself;
+       zval *object;
+       zval *member;
+} php_http_property_proxy_t;
+
+PHP_HTTP_API php_http_property_proxy_t *php_http_property_proxy_init(php_http_property_proxy_t *proxy, zval *object, zval *member TSRMLS_DC);
+PHP_HTTP_API void php_http_property_proxy_dtor(php_http_property_proxy_t *proxy);
+PHP_HTTP_API void php_http_property_proxy_free(php_http_property_proxy_t **proxy);
+
+typedef struct php_http_property_proxy_object {
+       zend_object zo;
+       php_http_property_proxy_t *proxy;
+} php_http_property_proxy_object_t;
+
+extern zend_class_entry *php_http_property_proxy_class_entry;
+extern zend_function_entry php_http_property_proxy_method_entry[];
+
+extern zend_object_value php_http_property_proxy_object_new(zend_class_entry *ce TSRMLS_DC);
+extern zend_object_value php_http_property_proxy_object_new_ex(zend_class_entry *ce, php_http_property_proxy_t *proxy, php_http_property_proxy_object_t **ptr TSRMLS_DC);
+extern void php_http_property_proxy_object_free(void *object TSRMLS_DC);
+
+PHP_METHOD(HttpPropertyProxy, __construct);
+
+PHP_MINIT_FUNCTION(http_property_proxy);
+
+#endif /* PHP_HTTP_PROPERTY_PROXY_H_ */
diff --git a/php_http_querystring.c b/php_http_querystring.c
new file mode 100644 (file)
index 0000000..ffa5052
--- /dev/null
@@ -0,0 +1,684 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id$ */
+
+#include "php_http.h"
+
+/** API **/
+
+static inline int php_http_querystring_modify_array_ex(zval *qarray, int key_type, char *key, int keylen, ulong idx, zval *params_entry TSRMLS_DC);
+static inline int php_http_querystring_modify_array(zval *qarray, zval *params TSRMLS_DC);
+
+#ifdef PHP_HTTP_HAVE_ICONV
+PHP_HTTP_API int php_http_querystring_xlate(zval *array, zval *param, const char *ie, const char *oe TSRMLS_DC)
+{
+       HashPosition pos;
+       zval **entry = NULL;
+       char *xlate_str = NULL, *xkey;
+       size_t xlate_len = 0, xlen;
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       
+       FOREACH_KEYVAL(pos, param, key, entry) {
+               if (key.type == HASH_KEY_IS_STRING) {
+                       if (PHP_ICONV_ERR_SUCCESS != php_iconv_string(key.str, key.len-1, &xkey, &xlen, oe, ie)) {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Failed to convert '%.*s' from '%s' to '%s'", key.len-1, key.str, ie, oe);
+                               return FAILURE;
+                       }
+               }
+               
+               if (Z_TYPE_PP(entry) == IS_STRING) {
+                       if (PHP_ICONV_ERR_SUCCESS != php_iconv_string(Z_STRVAL_PP(entry), Z_STRLEN_PP(entry), &xlate_str, &xlate_len, oe, ie)) {
+                               if (key.type == HASH_KEY_IS_STRING) {
+                                       efree(xkey);
+                               }
+                               php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Failed to convert '%.*s' from '%s' to '%s'", Z_STRLEN_PP(entry), Z_STRVAL_PP(entry), ie, oe);
+                               return FAILURE;
+                       }
+                       if (key.type == HASH_KEY_IS_STRING) {
+                               add_assoc_stringl_ex(array, xkey, xlen+1, xlate_str, xlate_len, 0);
+                       } else {
+                               add_index_stringl(array, key.num, xlate_str, xlate_len, 0);
+                       }
+               } else if (Z_TYPE_PP(entry) == IS_ARRAY) {
+                       zval *subarray;
+                       
+                       MAKE_STD_ZVAL(subarray);
+                       array_init(subarray);
+                       if (key.type == HASH_KEY_IS_STRING) {
+                               add_assoc_zval_ex(array, xkey, xlen+1, subarray);
+                       } else {
+                               add_index_zval(array, key.num, subarray);
+                       }
+                       if (SUCCESS != php_http_querystring_xlate(subarray, *entry, ie, oe TSRMLS_CC)) {
+                               if (key.type == HASH_KEY_IS_STRING) {
+                                       efree(xkey);
+                               }
+                               return FAILURE;
+                       }
+               }
+               
+               if (key.type == HASH_KEY_IS_STRING) {
+                       efree(xkey);
+               }
+       }
+       return SUCCESS;
+}
+#endif /* HAVE_ICONV */
+
+PHP_HTTP_API void php_http_querystring_update(zval *qarray, zval *qstring TSRMLS_DC)
+{
+       char *s = NULL;
+       size_t l = 0;
+       
+       if (Z_TYPE_P(qarray) != IS_ARRAY) {
+               convert_to_array(qarray);
+       }
+       if (SUCCESS == php_http_url_encode_hash(Z_ARRVAL_P(qarray), 0, NULL, 0, &s, &l TSRMLS_CC)) {
+               if (Z_TYPE_P(qstring) == IS_STRING)
+               zval_dtor(qstring);
+               ZVAL_STRINGL(qstring, s, l, 0);
+       } else {
+               php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Failed to update query string");
+       }
+}
+
+PHP_HTTP_API int php_http_querystring_modify(zval *qarray, zval *params TSRMLS_DC)
+{
+       if (Z_TYPE_P(params) == IS_ARRAY) {
+               return php_http_querystring_modify_array(qarray, params TSRMLS_CC);
+       } else if (Z_TYPE_P(params) == IS_OBJECT) {
+               if (instanceof_function(Z_OBJCE_P(params), php_http_querystring_class_entry TSRMLS_CC)) {
+                       return php_http_querystring_modify_array(qarray, zend_read_property(php_http_querystring_class_entry, params, ZEND_STRL("queryArray"), 0 TSRMLS_CC) TSRMLS_CC);
+               } else {
+                       return  php_http_querystring_modify_array(qarray, params TSRMLS_CC);
+               }
+       } else {
+               int rv;
+               zval array;
+               zval *qstring = php_http_zsep(IS_STRING, params);
+               
+               INIT_PZVAL(&array);
+               array_init(&array);
+               
+               php_default_treat_data(PARSE_STRING, estrdup(Z_STRVAL_P(qstring)), &array TSRMLS_CC);
+               zval_ptr_dtor(&qstring);
+               
+               rv = php_http_querystring_modify_array(qarray, &array TSRMLS_CC);
+               zval_dtor(&array);
+               return rv;
+       }
+}
+
+static inline int php_http_querystring_modify_array(zval *qarray, zval *params TSRMLS_DC)
+{
+       int rv = 0;
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       HashPosition pos;
+       zval **params_entry = NULL;
+       
+       FOREACH_HASH_KEYVAL(pos, HASH_OF(params), key, params_entry) {
+               /* only public properties */
+               if ((key.type != HASH_KEY_IS_STRING || *key.str) && php_http_querystring_modify_array_ex(qarray, key.type, key.str, key.len, key.num, *params_entry TSRMLS_CC)) {
+                       rv = 1;
+               }
+       }
+       
+       return rv;
+}
+
+static inline int php_http_querystring_modify_array_ex(zval *qarray, int key_type, char *key, int keylen, ulong idx, zval *params_entry TSRMLS_DC)
+{
+       zval **qarray_entry;
+
+       /* ensure array type */
+       if (Z_TYPE_P(qarray) != IS_ARRAY) {
+               convert_to_array(qarray);
+       }
+       
+       /* delete */
+       if (Z_TYPE_P(params_entry) == IS_NULL) {
+               if (key_type == HASH_KEY_IS_STRING) {
+                       return (SUCCESS == zend_hash_del(Z_ARRVAL_P(qarray), key, keylen));
+               } else {
+                       return (SUCCESS == zend_hash_index_del(Z_ARRVAL_P(qarray), idx));
+               }
+       }
+       
+       /* update */
+       if (    ((key_type == HASH_KEY_IS_STRING) && (SUCCESS == zend_hash_find(Z_ARRVAL_P(qarray), key, keylen, (void *) &qarray_entry))) ||
+                       ((key_type == HASH_KEY_IS_LONG) && (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(qarray), idx, (void *) &qarray_entry)))) {
+               zval equal;
+               
+               /* recursive */
+               if (Z_TYPE_P(params_entry) == IS_ARRAY || Z_TYPE_P(params_entry) == IS_OBJECT) {
+                       return php_http_querystring_modify(*qarray_entry, params_entry TSRMLS_CC);
+               }
+               /* equal */
+               if ((SUCCESS == is_equal_function(&equal, *qarray_entry, params_entry TSRMLS_CC)) && Z_BVAL(equal)) {
+                       return 0;
+               }
+       }
+       
+       /* add */
+       if (Z_TYPE_P(params_entry) == IS_OBJECT) {
+               zval *new_array;
+               
+               MAKE_STD_ZVAL(new_array);
+               array_init(new_array);
+               php_http_querystring_modify_array(new_array, params_entry TSRMLS_CC);
+               params_entry = new_array;
+       } else {
+               Z_ADDREF_P(params_entry);
+       }
+       if (key_type == HASH_KEY_IS_STRING) {
+               add_assoc_zval_ex(qarray, key, keylen, params_entry);
+       } else {
+               add_index_zval(qarray, idx, params_entry);
+       }
+       return 1;
+}
+
+/** PHP **/
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)                  PHP_HTTP_BEGIN_ARGS_EX(HttpQueryString, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                                            PHP_HTTP_EMPTY_ARGS_EX(HttpQueryString, method, 0)
+#define PHP_HTTP_QUERYSTRING_ME(method, visibility)            PHP_ME(HttpQueryString, method, PHP_HTTP_ARGS(HttpQueryString, method), visibility)
+#define PHP_HTTP_QUERYSTRING_GME(method, visibility)   PHP_ME(HttpQueryString, method, PHP_HTTP_ARGS(HttpQueryString, __getter), visibility)
+
+PHP_HTTP_BEGIN_ARGS(__construct, 0)
+       PHP_HTTP_ARG_VAL(params, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getGlobalInstance);
+
+PHP_HTTP_EMPTY_ARGS(toArray);
+PHP_HTTP_EMPTY_ARGS(toString);
+
+PHP_HTTP_BEGIN_ARGS(get, 0)
+       PHP_HTTP_ARG_VAL(name, 0)
+       PHP_HTTP_ARG_VAL(type, 0)
+       PHP_HTTP_ARG_VAL(defval, 0)
+       PHP_HTTP_ARG_VAL(delete, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(set, 1)
+       PHP_HTTP_ARG_VAL(params, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(mod, 0)
+       PHP_HTTP_ARG_VAL(params, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(__getter, 1)
+       PHP_HTTP_ARG_VAL(name, 0)
+       PHP_HTTP_ARG_VAL(defval, 0)
+       PHP_HTTP_ARG_VAL(delete, 0)
+PHP_HTTP_END_ARGS;
+
+#ifdef PHP_HTTP_HAVE_ICONV
+PHP_HTTP_BEGIN_ARGS(xlate, 2)
+       PHP_HTTP_ARG_VAL(from_encoding, 0)
+       PHP_HTTP_ARG_VAL(to_encoding, 0)
+PHP_HTTP_END_ARGS;
+#endif
+
+PHP_HTTP_EMPTY_ARGS(serialize);
+PHP_HTTP_BEGIN_ARGS(unserialize, 1)
+       PHP_HTTP_ARG_VAL(serialized, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(offsetGet, 1)
+       PHP_HTTP_ARG_VAL(offset, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(offsetSet, 2)
+       PHP_HTTP_ARG_VAL(offset, 0)
+       PHP_HTTP_ARG_VAL(value, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(offsetExists, 1)
+       PHP_HTTP_ARG_VAL(offset, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(offsetUnset, 1)
+       PHP_HTTP_ARG_VAL(offset, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getIterator);
+
+zend_class_entry *php_http_querystring_class_entry;
+zend_function_entry php_http_querystring_method_entry[] = {
+       PHP_HTTP_QUERYSTRING_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR|ZEND_ACC_FINAL)
+       
+       PHP_HTTP_QUERYSTRING_ME(toArray, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_ME(toString, ZEND_ACC_PUBLIC)
+       ZEND_MALIAS(HttpQueryString, __toString, toString, PHP_HTTP_ARGS(HttpQueryString, toString), ZEND_ACC_PUBLIC)
+       
+       PHP_HTTP_QUERYSTRING_ME(get, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_ME(set, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_ME(mod, ZEND_ACC_PUBLIC)
+       
+       PHP_HTTP_QUERYSTRING_GME(getBool, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_GME(getInt, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_GME(getFloat, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_GME(getString, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_GME(getArray, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_GME(getObject, ZEND_ACC_PUBLIC)
+       
+       PHP_HTTP_QUERYSTRING_ME(getIterator, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_QUERYSTRING_ME(getGlobalInstance, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+#ifdef PHP_HTTP_HAVE_ICONV
+       PHP_HTTP_QUERYSTRING_ME(xlate, ZEND_ACC_PUBLIC)
+#endif
+       
+       /* Implements Serializable */
+       PHP_HTTP_QUERYSTRING_ME(serialize, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_ME(unserialize, ZEND_ACC_PUBLIC)
+       
+       /* Implements ArrayAccess */
+       PHP_HTTP_QUERYSTRING_ME(offsetGet, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_ME(offsetSet, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_ME(offsetExists, ZEND_ACC_PUBLIC)
+       PHP_HTTP_QUERYSTRING_ME(offsetUnset, ZEND_ACC_PUBLIC)
+       
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_MINIT_FUNCTION(http_querystring)
+{
+       PHP_HTTP_REGISTER_CLASS(http, QueryString, http_querystring, php_http_object_class_entry, 0);
+       
+       zend_class_implements(php_http_querystring_class_entry TSRMLS_CC, 3, zend_ce_serializable, zend_ce_arrayaccess, zend_ce_aggregate);
+       
+       zend_declare_property_null(php_http_querystring_class_entry, ZEND_STRL("instance"), (ZEND_ACC_STATIC|ZEND_ACC_PRIVATE) TSRMLS_CC);
+       zend_declare_property_null(php_http_querystring_class_entry, ZEND_STRL("queryArray"), ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_null(php_http_querystring_class_entry, ZEND_STRL("queryString"), ZEND_ACC_PRIVATE TSRMLS_CC);
+       
+       zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_BOOL"), PHP_HTTP_QUERYSTRING_TYPE_BOOL TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_INT"), PHP_HTTP_QUERYSTRING_TYPE_INT TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_FLOAT"), PHP_HTTP_QUERYSTRING_TYPE_FLOAT TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_STRING"), PHP_HTTP_QUERYSTRING_TYPE_STRING TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_ARRAY"), PHP_HTTP_QUERYSTRING_TYPE_ARRAY TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_OBJECT"), PHP_HTTP_QUERYSTRING_TYPE_OBJECT TSRMLS_CC);
+       
+       return SUCCESS;
+}
+
+static inline void php_http_querystring_get(zval *this_ptr, int type, char *name, uint name_len, zval *defval, zend_bool del, zval *return_value TSRMLS_DC)
+{
+       zval **arrval, *qarray = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC);
+               
+       if ((Z_TYPE_P(qarray) == IS_ARRAY) && (SUCCESS == zend_hash_find(Z_ARRVAL_P(qarray), name, name_len + 1, (void *) &arrval))) {
+               if (type) {
+                       zval *value = php_http_zsep(type, *arrval);
+                       RETVAL_ZVAL(value, 1, 1);
+               } else {
+                       RETVAL_ZVAL(*arrval, 1, 0);
+               }
+                       
+               if (del && (SUCCESS == zend_hash_del(Z_ARRVAL_P(qarray), name, name_len + 1))) {
+                       php_http_querystring_update(qarray, zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), 0 TSRMLS_CC) TSRMLS_CC);
+               }
+       } else if(defval) {
+               RETURN_ZVAL(defval, 1, 0);
+       }
+}
+
+static inline void php_http_querystring_set(zval *instance, zval *params TSRMLS_DC)
+{
+       zval *na = NULL, *qa = zend_read_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryArray"), 0 TSRMLS_CC);
+
+       if (Z_TYPE_P(qa) != IS_ARRAY) {
+               MAKE_STD_ZVAL(qa);
+               array_init(qa);
+               zend_update_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryArray"), qa TSRMLS_CC);
+               na = qa;
+       }
+
+       if (params && php_http_querystring_modify(qa, params TSRMLS_CC)) {
+               zval *ns = NULL, *qs = zend_read_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryString"), 0 TSRMLS_CC);
+
+               if (Z_TYPE_P(qs) != IS_STRING) {
+                       MAKE_STD_ZVAL(qs);
+                       ZVAL_EMPTY_STRING(qs);
+                       zend_update_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryString"), qs TSRMLS_CC);
+                       ns = qs;
+               }
+
+               php_http_querystring_update(qa, qs TSRMLS_CC);
+
+               if (ns) {
+                       zval_ptr_dtor(&ns);
+               }
+       }
+
+       if (na) {
+               zval_ptr_dtor(&na);
+       }
+}
+
+PHP_METHOD(HttpQueryString, __construct)
+{
+       zval *params = NULL;
+       
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z", &params)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) {
+                               php_http_querystring_set(getThis(), params TSRMLS_CC);
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpQueryString, getGlobalInstance)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) {
+                               zval *instance = *zend_std_get_static_property(php_http_querystring_class_entry, ZEND_STRL("instance"), 0, NULL TSRMLS_CC);
+
+                               if (Z_TYPE_P(instance) != IS_OBJECT) {
+                                       zval **_SERVER = NULL, **_GET = NULL, **QUERY_STRING = NULL;
+
+                                       zend_is_auto_global("_GET", lenof("_GET") TSRMLS_CC);
+                                       zend_is_auto_global("_SERVER", lenof("_SERVER") TSRMLS_CC);
+
+                                       if ((SUCCESS == zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void *) &_SERVER))
+                                       &&      (Z_TYPE_PP(_SERVER) == IS_ARRAY)
+                                       &&      (SUCCESS == zend_hash_find(Z_ARRVAL_PP(_SERVER), "QUERY_STRING", sizeof("QUERY_STRING"), (void *) &QUERY_STRING))
+                                       &&      (SUCCESS == zend_hash_find(&EG(symbol_table), "_GET", sizeof("_GET"), (void *) &_GET))
+                                       &&      (Z_TYPE_PP(_GET) == IS_ARRAY)
+                                       ) {
+                                               zval *qstring = *QUERY_STRING, *qarray = *_GET;
+
+                                               if (Z_TYPE_P(qstring) != IS_STRING) {
+                                                       convert_to_string(qstring);
+                                               }
+
+                                               MAKE_STD_ZVAL(instance);
+                                               ZVAL_OBJVAL(instance, php_http_querystring_object_new(php_http_querystring_class_entry TSRMLS_CC), 0);
+
+                                               zend_update_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryArray"), qarray TSRMLS_CC);
+                                               zend_update_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryString"), qstring TSRMLS_CC);
+
+                                               Z_SET_ISREF_P(zend_read_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryArray"), 0 TSRMLS_CC));
+                                               Z_SET_ISREF_P(zend_read_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryString"), 0 TSRMLS_CC));
+
+                                               zend_update_static_property(php_http_querystring_class_entry, ZEND_STRL("instance"), instance TSRMLS_CC);
+                                               zval_ptr_dtor(&instance);
+                                       } else {
+                                               php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Could not acquire reference to superglobal GET or QUERY_STRING");
+                                       }
+                               }
+                               RETVAL_ZVAL(instance, 1, 0);
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpQueryString, getIterator)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) {
+                               zval *retval = NULL, *qa = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC);
+
+                               object_init_ex(return_value, spl_ce_RecursiveArrayIterator);
+                               zend_call_method_with_1_params(&return_value, spl_ce_RecursiveArrayIterator, NULL, "__construct", &retval, qa);
+                               if (retval) {
+                                       zval_ptr_dtor(&retval);
+                               }
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpQueryString, toString)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_querystring_class_entry, "queryString");
+       }
+       RETURN_EMPTY_STRING();
+}
+
+PHP_METHOD(HttpQueryString, toArray)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_querystring_class_entry, "queryArray");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpQueryString, get)
+{
+       char *name_str = NULL;
+       int name_len = 0;
+       long type = 0;
+       zend_bool del = 0;
+       zval *ztype = NULL, *defval = NULL;
+       
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|szzb", &name_str, &name_len, &ztype, &defval, &del)) {
+               if (name_str && name_len) {
+                       if (ztype) {
+                               if (Z_TYPE_P(ztype) == IS_LONG) {
+                                       type = Z_LVAL_P(ztype);
+                               } else if(Z_TYPE_P(ztype) == IS_STRING) {
+                                       switch (Z_STRVAL_P(ztype)[0]) {
+                                               case 'B': 
+                                               case 'b':       type = PHP_HTTP_QUERYSTRING_TYPE_BOOL;          break;
+                                               case 'L':
+                                               case 'l':
+                                               case 'I':
+                                               case 'i':       type = PHP_HTTP_QUERYSTRING_TYPE_INT;           break;
+                                               case 'd':
+                                               case 'D':
+                                               case 'F':
+                                               case 'f':       type = PHP_HTTP_QUERYSTRING_TYPE_FLOAT;         break;  
+                                               case 'S':
+                                               case 's':       type = PHP_HTTP_QUERYSTRING_TYPE_STRING;        break;
+                                               case 'A':
+                                               case 'a':       type = PHP_HTTP_QUERYSTRING_TYPE_ARRAY;         break;
+                                               case 'O':
+                                               case 'o':       type = PHP_HTTP_QUERYSTRING_TYPE_OBJECT;        break;
+                                       }
+                               }
+                       }
+                       php_http_querystring_get(getThis(), type, name_str, name_len, defval, del, return_value TSRMLS_CC);
+               } else {
+                       RETURN_PROP(php_http_querystring_class_entry, "queryString");
+               }
+       }
+}
+
+PHP_METHOD(HttpQueryString, set)
+{
+       zval *params;
+       
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &params)) {
+               php_http_querystring_set(getThis(), params TSRMLS_CC);
+               RETURN_PROP(php_http_querystring_class_entry, "queryString");
+       }
+       
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpQueryString, mod)
+{
+       zval *params;
+       
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &params)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) {
+                               ZVAL_OBJVAL(return_value, Z_OBJ_HT_P(getThis())->clone_obj(getThis() TSRMLS_CC), 0);
+                               php_http_querystring_set(return_value, params TSRMLS_CC);
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+#define PHP_HTTP_QUERYSTRING_GETTER(method, TYPE) \
+PHP_METHOD(HttpQueryString, method) \
+{ \
+       char *name; \
+       int name_len; \
+       zval *defval = NULL; \
+       zend_bool del = 0; \
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|zb", &name, &name_len, &defval, &del)) { \
+               php_http_querystring_get(getThis(), TYPE, name, name_len, defval, del, return_value TSRMLS_CC); \
+       } \
+}
+PHP_HTTP_QUERYSTRING_GETTER(getBool, IS_BOOL);
+PHP_HTTP_QUERYSTRING_GETTER(getInt, IS_LONG);
+PHP_HTTP_QUERYSTRING_GETTER(getFloat, IS_DOUBLE);
+PHP_HTTP_QUERYSTRING_GETTER(getString, IS_STRING);
+PHP_HTTP_QUERYSTRING_GETTER(getArray, IS_ARRAY);
+PHP_HTTP_QUERYSTRING_GETTER(getObject, IS_OBJECT);
+
+#ifdef PHP_HTTP_HAVE_ICONV
+PHP_METHOD(HttpQueryString, xlate)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               char *ie, *oe;
+               int ie_len, oe_len;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &ie, &ie_len, &oe, &oe_len)) {
+                       with_error_handling(EH_THROW,  PHP_HTTP_EX_CE(querystring)) {
+                               zval *qa = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC);
+
+                               if (Z_TYPE_P(qa) != IS_ARRAY) {
+                                       MAKE_STD_ZVAL(qa);
+                                       array_init(qa);
+                                       zend_update_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), qa TSRMLS_CC);
+                                       zval_ptr_dtor(&qa);
+                               } else {
+                                       zval xa;
+
+                                       INIT_PZVAL(&xa);
+                                       array_init(&xa);
+                                       if (SUCCESS == php_http_querystring_xlate(&xa, qa, ie, oe TSRMLS_CC)) {
+                                               zval *ns = NULL, *qs = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), 0 TSRMLS_CC);
+
+                                               /* shitty internal zvals */
+                                               if (Z_TYPE_P(qs) != IS_STRING) {
+                                                       MAKE_STD_ZVAL(qs);
+                                                       ZVAL_EMPTY_STRING(qs);
+                                                       zend_update_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), qs TSRMLS_CC);
+                                                       ns = qs;
+                                               }
+
+                                               zend_hash_clean(Z_ARRVAL_P(qa));
+                                               array_copy(Z_ARRVAL(xa), Z_ARRVAL_P(qa));
+                                               php_http_querystring_update(qa, qs TSRMLS_CC);
+
+                                               if (ns) {
+                                                       zval_ptr_dtor(&ns);
+                                               }
+                                       }
+                                       zval_dtor(&xa);
+                               }
+                               RETVAL_ZVAL(getThis(), 1, 0);
+                       } end_error_handling();
+               }
+       } end_error_handling();
+       
+}
+#endif /* HAVE_ICONV */
+
+PHP_METHOD(HttpQueryString, serialize)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_querystring_class_entry, "queryString");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpQueryString, unserialize)
+{
+       zval *serialized;
+       
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &serialized)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) {
+                               if (Z_TYPE_P(serialized) == IS_STRING) {
+                                       php_http_querystring_set(getThis(), serialized TSRMLS_CC);
+                               } else {
+                                       php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Expected a string as parameter");
+                               }
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpQueryString, offsetGet)
+{
+       char *offset_str;
+       int offset_len;
+       zval **value;
+       
+       if ((SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset_str, &offset_len))
+       &&      (SUCCESS == zend_hash_find(Z_ARRVAL_P(zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC)), offset_str, offset_len + 1, (void *) &value))
+       ) {
+               RETVAL_ZVAL(*value, 1, 0);
+       }
+}
+
+PHP_METHOD(HttpQueryString, offsetSet)
+{
+       char *offset_str;
+       int offset_len;
+       zval *value;
+       
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &offset_str, &offset_len, &value)) {
+               zval *qarr = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC), *qstr = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), 0 TSRMLS_CC);
+               
+               Z_ADDREF_P(value);
+               add_assoc_zval_ex(qarr, offset_str, offset_len + 1, value);
+               php_http_querystring_update(qarr, qstr TSRMLS_CC);
+       }
+}
+
+PHP_METHOD(HttpQueryString, offsetExists)
+{
+       char *offset_str;
+       int offset_len;
+       zval **value;
+       
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset_str, &offset_len)) {
+               RETURN_BOOL((SUCCESS == zend_hash_find(Z_ARRVAL_P(zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC)), offset_str, offset_len + 1, (void *) &value)) && (Z_TYPE_PP(value) != IS_NULL));
+       }
+}
+
+PHP_METHOD(HttpQueryString, offsetUnset)
+{
+       char *offset_str;
+       int offset_len;
+       
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset_str, &offset_len)) {
+               zval *qarr = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC);
+               
+               if (SUCCESS == zend_hash_del(Z_ARRVAL_P(qarr), offset_str, offset_len + 1)) {
+                       php_http_querystring_update(qarr, zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), 0 TSRMLS_CC) TSRMLS_CC);
+               }
+       }
+}
+
+
+/*
+ * 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_querystring.h b/php_http_querystring.h
new file mode 100644 (file)
index 0000000..92abd01
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_querystring_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_QUERYSTRING_H
+#define PHP_HTTP_QUERYSTRING_H
+
+/* API */
+
+#ifdef PHP_HTTP_HAVE_ICONV
+PHP_HTTP_API int php_http_querystring_xlate(zval *array, zval *param, const char *ie, const char *oe TSRMLS_DC);
+#endif /* PHP_HTTP_HAVE_ICONV */
+PHP_HTTP_API void php_http_querystring_update(zval *qarray, zval *qstring TSRMLS_DC);
+PHP_HTTP_API int php_http_querystring_modify(zval *qarray, zval *params TSRMLS_DC);
+
+/* PHP */
+
+typedef struct php_http_querystring_object {
+       zend_object zo;
+} php_http_querystring_object_t;
+
+#define PHP_HTTP_QUERYSTRING_TYPE_BOOL         IS_BOOL
+#define PHP_HTTP_QUERYSTRING_TYPE_INT          IS_LONG
+#define PHP_HTTP_QUERYSTRING_TYPE_FLOAT                IS_DOUBLE
+#define PHP_HTTP_QUERYSTRING_TYPE_STRING       IS_STRING
+#define PHP_HTTP_QUERYSTRING_TYPE_ARRAY                IS_ARRAY
+#define PHP_HTTP_QUERYSTRING_TYPE_OBJECT       IS_OBJECT
+
+extern zend_class_entry *php_http_querystring_class_entry;
+extern zend_function_entry php_http_querystring_method_entry[];
+
+extern PHP_MINIT_FUNCTION(http_querystring);
+
+#define php_http_querystring_object_new php_http_object_new
+#define php_http_querystring_object_new_ex php_http_object_new_ex
+
+PHP_METHOD(HttpQueryString, getGlobalInstance);
+PHP_METHOD(HttpQueryString, __construct);
+PHP_METHOD(HttpQueryString, getIterator);
+PHP_METHOD(HttpQueryString, toString);
+PHP_METHOD(HttpQueryString, toArray);
+PHP_METHOD(HttpQueryString, get);
+PHP_METHOD(HttpQueryString, set);
+PHP_METHOD(HttpQueryString, mod);
+PHP_METHOD(HttpQueryString, getBool);
+PHP_METHOD(HttpQueryString, getInt);
+PHP_METHOD(HttpQueryString, getFloat);
+PHP_METHOD(HttpQueryString, getString);
+PHP_METHOD(HttpQueryString, getArray);
+PHP_METHOD(HttpQueryString, getObject);
+#ifdef PHP_HTTP_HAVE_ICONV
+PHP_METHOD(HttpQueryString, xlate);
+#endif /* PHP_HTTP_HAVE_ICONV */
+PHP_METHOD(HttpQueryString, factory);
+PHP_METHOD(HttpQueryString, singleton);
+PHP_METHOD(HttpQueryString, serialize);
+PHP_METHOD(HttpQueryString, unserialize);
+PHP_METHOD(HttpQueryString, offsetGet);
+PHP_METHOD(HttpQueryString, offsetSet);
+PHP_METHOD(HttpQueryString, offsetExists);
+PHP_METHOD(HttpQueryString, offsetUnset);
+
+#endif /* PHP_HTTP_QUERYSTRING_H */
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
diff --git a/php_http_request.c b/php_http_request.c
new file mode 100644 (file)
index 0000000..1df601f
--- /dev/null
@@ -0,0 +1,2362 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_request_api.c 298591 2010-04-26 11:46:55Z mike $ */
+
+#include "php_http.h"
+
+#ifdef PHP_HTTP_NEED_OPENSSL_TSL
+static MUTEX_T *php_http_openssl_tsl = NULL;
+
+static void php_http_openssl_thread_lock(int mode, int n, const char * file, int line)
+{
+       if (mode & CRYPTO_LOCK) {
+               tsrm_mutex_lock(php_http_openssl_tsl[n]);
+       } else {
+               tsrm_mutex_unlock(php_http_openssl_tsl[n]);
+       }
+}
+
+static ulong php_http_openssl_thread_id(void)
+{
+       return (ulong) tsrm_thread_id();
+}
+#endif
+#ifdef PHP_HTTP_NEED_GNUTLS_TSL
+static int php_http_gnutls_mutex_create(void **m)
+{
+       if (*((MUTEX_T *) m) = tsrm_mutex_alloc()) {
+               return SUCCESS;
+       } else {
+               return FAILURE;
+       }
+}
+
+static int php_http_gnutls_mutex_destroy(void **m)
+{
+       tsrm_mutex_free(*((MUTEX_T *) m));
+       return SUCCESS;
+}
+
+static int php_http_gnutls_mutex_lock(void **m)
+{
+       return tsrm_mutex_lock(*((MUTEX_T *) m));
+}
+
+static int php_http_gnutls_mutex_unlock(void **m)
+{
+       return tsrm_mutex_unlock(*((MUTEX_T *) m));
+}
+
+static struct gcry_thread_cbs php_http_gnutls_tsl = {
+       GCRY_THREAD_OPTION_USER,
+       NULL,
+       php_http_gnutls_mutex_create,
+       php_http_gnutls_mutex_destroy,
+       php_http_gnutls_mutex_lock,
+       php_http_gnutls_mutex_unlock
+};
+#endif
+
+
+/* safe curl wrappers */
+#define init_curl_storage(ch) \
+       {\
+               php_http_request_storage_t *st = pecalloc(1, sizeof(php_http_request_storage_t), 1); \
+               curl_easy_setopt(ch, CURLOPT_PRIVATE, st); \
+               curl_easy_setopt(ch, CURLOPT_ERRORBUFFER, st->errorbuffer); \
+       }
+
+static void *safe_curl_init(void)
+{
+       CURL *ch;
+       
+       if ((ch = curl_easy_init())) {
+               init_curl_storage(ch);
+               return ch;
+       }
+       return NULL;
+}
+static void *safe_curl_copy(void *p)
+{
+       CURL *ch;
+       
+       if ((ch = curl_easy_duphandle(p))) {
+               init_curl_storage(ch);
+               return ch;
+       }
+       return NULL;
+}
+static void safe_curl_dtor(void *p) {
+       php_http_request_storage_t *st = php_http_request_storage_get(p);
+       
+       curl_easy_cleanup(p);
+       
+       if (st) {
+               if (st->url) {
+                       pefree(st->url, 1);
+               }
+               if (st->cookiestore) {
+                       pefree(st->cookiestore, 1);
+               }
+               pefree(st, 1);
+       }
+}
+
+static inline zval *php_http_request_option(php_http_request_t *request, HashTable *options, char *key, size_t keylen, int type);
+static inline zval *php_http_request_option_cache(php_http_request_t *r, char *key, size_t keylen, ulong h, zval *opt);
+static inline int php_http_request_cookies_enabled(php_http_request_t *r);
+
+static size_t php_http_curl_read_callback(void *, size_t, size_t, void *);
+static int php_http_curl_progress_callback(void *, double, double, double, double);
+static int php_http_curl_raw_callback(CURL *, curl_infotype, char *, size_t, void *);
+static int php_http_curl_dummy_callback(char *data, size_t n, size_t l, void *s) { return n*l; }
+static curlioerr php_http_curl_ioctl_callback(CURL *, curliocmd, void *);
+
+PHP_HTTP_API CURL * php_http_curl_init(CURL *ch, php_http_request_t *request TSRMLS_DC)
+{
+       if (ch || (SUCCESS == php_http_persistent_handle_acquire(ZEND_STRL("http_request"), &ch TSRMLS_CC))) {
+#if defined(ZTS)
+               curl_easy_setopt(ch, CURLOPT_NOSIGNAL, 1L);
+#endif
+               curl_easy_setopt(ch, CURLOPT_HEADER, 0L);
+               curl_easy_setopt(ch, CURLOPT_FILETIME, 1L);
+               curl_easy_setopt(ch, CURLOPT_AUTOREFERER, 1L);
+               curl_easy_setopt(ch, CURLOPT_VERBOSE, 1L);
+               curl_easy_setopt(ch, CURLOPT_HEADERFUNCTION, NULL);
+               curl_easy_setopt(ch, CURLOPT_DEBUGFUNCTION, php_http_curl_raw_callback);
+               curl_easy_setopt(ch, CURLOPT_READFUNCTION, php_http_curl_read_callback);
+               curl_easy_setopt(ch, CURLOPT_IOCTLFUNCTION, php_http_curl_ioctl_callback);
+               curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, php_http_curl_dummy_callback);
+               
+               /* set context */
+               if (request) {
+                       curl_easy_setopt(ch, CURLOPT_DEBUGDATA, request);
+                       
+                       /* attach curl handle */
+                       request->ch = ch;
+                       /* set defaults (also in php_http_request_reset()) */
+                       php_http_request_defaults(request);
+               }
+       }
+       
+       return ch;
+}
+PHP_HTTP_API CURL *php_http_curl_copy(CURL *ch TSRMLS_DC)
+{
+       CURL *copy;
+       
+       if (SUCCESS == php_http_persistent_handle_accrete(ZEND_STRL("http_request"), ch, &copy TSRMLS_CC)) {
+               return copy;
+       }
+       return NULL;
+}
+PHP_HTTP_API void php_http_curl_free(CURL **ch TSRMLS_DC)
+{
+       if (*ch) {
+               curl_easy_setopt(*ch, CURLOPT_NOPROGRESS, 1L);
+               curl_easy_setopt(*ch, CURLOPT_PROGRESSFUNCTION, NULL);
+               curl_easy_setopt(*ch, CURLOPT_VERBOSE, 0L);
+               curl_easy_setopt(*ch, CURLOPT_DEBUGFUNCTION, NULL);
+               
+               php_http_persistent_handle_release(ZEND_STRL("http_request"), ch TSRMLS_CC);
+       }
+}
+
+PHP_HTTP_API php_http_request_t *php_http_request_init(php_http_request_t *request, CURL *ch, php_http_request_method_t meth, const char *url TSRMLS_DC)
+{
+       php_http_request_t *r;
+       
+       if (request) {
+               r = request;
+       } else {
+               r = emalloc(sizeof(php_http_request_t));
+       }
+       memset(r, 0, sizeof(php_http_request_t));
+       
+       r->ch = ch;
+       r->url = (url) ? php_http_url_absolute(url, 0) : NULL;
+       r->meth = (meth > 0) ? meth : PHP_HTTP_GET;
+
+       r->parser.ctx = php_http_message_parser_init(NULL TSRMLS_CC);
+       r->parser.msg = php_http_message_init(NULL, 0 TSRMLS_CC);
+       r->parser.buf = php_http_buffer_init(NULL);
+
+       php_http_buffer_init(&r->_cache.cookies);
+       zend_hash_init(&r->_cache.options, 0, NULL, ZVAL_PTR_DTOR, 0);
+       
+       TSRMLS_SET_CTX(r->ts);
+       
+       return r;
+}
+
+PHP_HTTP_API void php_http_request_dtor(php_http_request_t *request)
+{
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+       php_http_request_reset(request);
+       php_http_curl_free(&request->ch);
+       php_http_message_body_free(&request->body);
+       php_http_message_parser_free(&request->parser.ctx);
+       php_http_message_free(&request->parser.msg);
+       php_http_buffer_free(&request->parser.buf);
+
+       php_http_buffer_dtor(&request->_cache.cookies);
+       zend_hash_destroy(&request->_cache.options);
+       if (request->_cache.headers) {
+               curl_slist_free_all(request->_cache.headers);
+               request->_cache.headers = NULL;
+       }
+       if (request->_progress.callback) {
+               zval_ptr_dtor(&request->_progress.callback);
+               request->_progress.callback = NULL;
+       }
+}
+
+PHP_HTTP_API void php_http_request_free(php_http_request_t **request)
+{
+       if (*request) {
+               TSRMLS_FETCH_FROM_CTX((*request)->ts);
+               php_http_request_dtor(*request);
+               efree(*request);
+               *request = NULL;
+       }
+}
+
+PHP_HTTP_API void php_http_request_reset(php_http_request_t *request)
+{
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       STR_SET(request->url, NULL);
+       php_http_message_body_dtor(request->body);
+       php_http_request_defaults(request);
+       
+       if (request->ch) {
+               php_http_request_storage_t *st = php_http_request_storage_get(request->ch);
+               
+               if (st) {
+                       if (st->url) {
+                               pefree(st->url, 1);
+                               st->url = NULL;
+                       }
+                       if (st->cookiestore) {
+                               pefree(st->cookiestore, 1);
+                               st->cookiestore = NULL;
+                       }
+                       st->errorbuffer[0] = '\0';
+               }
+       }
+}
+
+PHP_HTTP_API STATUS php_http_request_enable_cookies(php_http_request_t *request)
+{
+       int initialized = 1;
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+       PHP_HTTP_CHECK_CURL_INIT(request->ch, php_http_curl_init(request->ch, request TSRMLS_CC), initialized = 0);
+       if (initialized && (php_http_request_cookies_enabled(request) || (CURLE_OK == curl_easy_setopt(request->ch, CURLOPT_COOKIEFILE, "")))) {
+               return SUCCESS;
+       }
+       php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not enable cookies for this session");
+       return FAILURE;
+}
+
+PHP_HTTP_API STATUS php_http_request_reset_cookies(php_http_request_t *request, int session_only)
+{
+       int initialized = 1;
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+       PHP_HTTP_CHECK_CURL_INIT(request->ch, php_http_curl_init(request->ch, request TSRMLS_CC), initialized = 0);
+       if (initialized) {
+               if (!php_http_request_cookies_enabled(request)) {
+                       if (SUCCESS != php_http_request_enable_cookies(request)) {
+                               return FAILURE;
+                       }
+               }
+               if (session_only) {
+                       if (CURLE_OK == curl_easy_setopt(request->ch, CURLOPT_COOKIELIST, "SESS")) {
+                               return SUCCESS;
+                       }
+               } else {
+                       if (CURLE_OK == curl_easy_setopt(request->ch, CURLOPT_COOKIELIST, "ALL")) {
+                               return SUCCESS;
+                       }
+               }
+       }
+       return FAILURE;
+}
+
+PHP_HTTP_API STATUS php_http_request_flush_cookies(php_http_request_t *request)
+{
+       int initialized = 1;
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+       PHP_HTTP_CHECK_CURL_INIT(request->ch, php_http_curl_init(request->ch, request TSRMLS_CC), initialized = 0);
+       if (initialized) {
+               if (!php_http_request_cookies_enabled(request)) {
+                       return FAILURE;
+               }
+               if (CURLE_OK == curl_easy_setopt(request->ch, CURLOPT_COOKIELIST, "FLUSH")) {
+                       return SUCCESS;
+               }
+       }
+       return FAILURE;
+}
+
+PHP_HTTP_API void php_http_request_defaults(php_http_request_t *request)
+{
+       if (request->ch) {
+               PHP_HTTP_CURL_OPT(CURLOPT_NOPROGRESS, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_PROGRESSDATA, request);
+               PHP_HTTP_CURL_OPT(CURLOPT_PROGRESSFUNCTION, php_http_curl_progress_callback);
+               PHP_HTTP_CURL_OPT(CURLOPT_URL, NULL);
+#if PHP_HTTP_CURL_VERSION(7,19,4)
+               PHP_HTTP_CURL_OPT(CURLOPT_NOPROXY, NULL);
+#endif
+               PHP_HTTP_CURL_OPT(CURLOPT_PROXY, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_PROXYPORT, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_PROXYTYPE, 0L);
+               /* libcurl < 7.19.6 does not clear auth info with USERPWD set to NULL */
+#if PHP_HTTP_CURL_VERSION(7,19,1)              
+               PHP_HTTP_CURL_OPT(CURLOPT_PROXYUSERNAME, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_PROXYPASSWORD, NULL);
+#endif
+               PHP_HTTP_CURL_OPT(CURLOPT_PROXYAUTH, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTPPROXYTUNNEL, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_DNS_CACHE_TIMEOUT, 60L);
+               PHP_HTTP_CURL_OPT(CURLOPT_IPRESOLVE, 0);
+               PHP_HTTP_CURL_OPT(CURLOPT_LOW_SPEED_LIMIT, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_LOW_SPEED_TIME, 0L);
+               /* LFS weirdance
+               PHP_HTTP_CURL_OPT(CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) 0);
+               PHP_HTTP_CURL_OPT(CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) 0);
+               */
+               /* crashes
+               PHP_HTTP_CURL_OPT(CURLOPT_MAXCONNECTS, 5L); */
+               PHP_HTTP_CURL_OPT(CURLOPT_FRESH_CONNECT, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_FORBID_REUSE, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_INTERFACE, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_PORT, 0L);
+#if PHP_HTTP_CURL_VERSION(7,19,0)
+               PHP_HTTP_CURL_OPT(CURLOPT_ADDRESS_SCOPE, 0L);
+#endif
+               PHP_HTTP_CURL_OPT(CURLOPT_LOCALPORT, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_LOCALPORTRANGE, 0L);
+               /* libcurl < 7.19.6 does not clear auth info with USERPWD set to NULL */
+#if PHP_HTTP_CURL_VERSION(7,19,1)
+               PHP_HTTP_CURL_OPT(CURLOPT_USERNAME, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_PASSWORD, NULL);
+#endif
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTPAUTH, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_ENCODING, NULL);
+               /* we do this ourself anyway */
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTP_CONTENT_DECODING, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTP_TRANSFER_DECODING, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_FOLLOWLOCATION, 0L);
+#if PHP_HTTP_CURL_VERSION(7,19,1)
+               PHP_HTTP_CURL_OPT(CURLOPT_POSTREDIR, 0L);
+#else
+               PHP_HTTP_CURL_OPT(CURLOPT_POST301, 0L);
+#endif
+               PHP_HTTP_CURL_OPT(CURLOPT_UNRESTRICTED_AUTH, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_REFERER, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_USERAGENT, "PECL::HTTP/" PHP_HTTP_EXT_VERSION " (PHP/" PHP_VERSION ")");
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTPHEADER, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_COOKIE, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_COOKIESESSION, 0L);
+               /* these options would enable curl's cookie engine by default which we don't want
+               PHP_HTTP_CURL_OPT(CURLOPT_COOKIEFILE, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_COOKIEJAR, NULL); */
+               PHP_HTTP_CURL_OPT(CURLOPT_COOKIELIST, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_RANGE, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_RESUME_FROM, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_MAXFILESIZE, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_TIMECONDITION, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_TIMEVALUE, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_TIMEOUT, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_CONNECTTIMEOUT, 3);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSLCERT, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSLCERTTYPE, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSLCERTPASSWD, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSLKEY, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSLKEYTYPE, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSLKEYPASSWD, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSLENGINE, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSLVERSION, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSL_VERIFYPEER, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSL_VERIFYHOST, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_SSL_CIPHER_LIST, NULL);
+#if PHP_HTTP_CURL_VERSION(7,19,0)
+               PHP_HTTP_CURL_OPT(CURLOPT_ISSUERCERT, NULL);
+       #if defined(PHP_HTTP_HAVE_OPENSSL)
+               PHP_HTTP_CURL_OPT(CURLOPT_CRLFILE, NULL);
+       #endif
+#endif
+#if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL)
+               PHP_HTTP_CURL_OPT(CURLOPT_CERTINFO, NULL);
+#endif
+#ifdef PHP_HTTP_CURL_CAINFO
+               PHP_HTTP_CURL_OPT(CURLOPT_CAINFO, PHP_HTTP_CURL_CAINFO);
+#else
+               PHP_HTTP_CURL_OPT(CURLOPT_CAINFO, NULL);
+#endif
+               PHP_HTTP_CURL_OPT(CURLOPT_CAPATH, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_RANDOM_FILE, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_EGDSOCKET, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_POSTFIELDS, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_POSTFIELDSIZE, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTPPOST, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_IOCTLDATA, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_READDATA, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_INFILESIZE, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE);
+               PHP_HTTP_CURL_OPT(CURLOPT_CUSTOMREQUEST, NULL);
+               PHP_HTTP_CURL_OPT(CURLOPT_NOBODY, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_POST, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_UPLOAD, 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTPGET, 1L);
+       }
+}
+
+PHP_HTTP_API void php_http_request_set_progress_callback(php_http_request_t *request, zval *cb)
+{
+       if (request->_progress.callback) {
+               zval_ptr_dtor(&request->_progress.callback);
+       }
+       if ((request->_progress.callback = cb)) {
+               Z_ADDREF_P(cb);
+       }
+}
+
+PHP_HTTP_API STATUS php_http_request_prepare(php_http_request_t *request, HashTable *options)
+{
+       zval *zoption;
+       zend_bool range_req = 0;
+       php_http_request_storage_t *storage;
+
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+       PHP_HTTP_CHECK_CURL_INIT(request->ch, php_http_curl_init(NULL, request TSRMLS_CC), return FAILURE);
+       
+       if (!(storage = php_http_request_storage_get(request->ch))) {
+               return FAILURE;
+       }
+       storage->errorbuffer[0] = '\0';
+       /* set options */
+       if (storage->url) {
+               pefree(storage->url, 1);
+       }
+       storage->url = pestrdup(request->url, 1);
+       PHP_HTTP_CURL_OPT(CURLOPT_URL, storage->url);
+
+       /* progress callback */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("onprogress"), -1))) {
+               php_http_request_set_progress_callback(request, zoption);
+       }
+
+       /* proxy */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxyhost"), IS_STRING))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_PROXY, Z_STRVAL_P(zoption));
+               /* type */
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxytype"), IS_LONG))) {
+                       PHP_HTTP_CURL_OPT(CURLOPT_PROXYTYPE, Z_LVAL_P(zoption));
+               }
+               /* port */
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxyport"), IS_LONG))) {
+                       PHP_HTTP_CURL_OPT(CURLOPT_PROXYPORT, Z_LVAL_P(zoption));
+               }
+               /* user:pass */
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxyauth"), IS_STRING)) && Z_STRLEN_P(zoption)) {
+                       PHP_HTTP_CURL_OPT(CURLOPT_PROXYUSERPWD, Z_STRVAL_P(zoption));
+               }
+               /* auth method */
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxyauthtype"), IS_LONG))) {
+                       PHP_HTTP_CURL_OPT(CURLOPT_PROXYAUTH, Z_LVAL_P(zoption));
+               }
+               /* tunnel */
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxytunnel"), IS_BOOL)) && Z_BVAL_P(zoption)) {
+                       PHP_HTTP_CURL_OPT(CURLOPT_HTTPPROXYTUNNEL, 1L);
+               }
+       }
+#if PHP_HTTP_CURL_VERSION(7,19,4)
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("noproxy"), IS_STRING))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_NOPROXY, Z_STRVAL_P(zoption));
+       }
+#endif
+
+       /* dns */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("dns_cache_timeout"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_DNS_CACHE_TIMEOUT, Z_LVAL_P(zoption));
+       }
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("ipresolve"), IS_LONG)) && Z_LVAL_P(zoption)) {
+               PHP_HTTP_CURL_OPT(CURLOPT_IPRESOLVE, Z_LVAL_P(zoption));
+       }
+       
+       /* limits */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("low_speed_limit"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_LOW_SPEED_LIMIT, Z_LVAL_P(zoption));
+       }
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("low_speed_time"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_LOW_SPEED_TIME, Z_LVAL_P(zoption));
+       }
+       /* LSF weirdance
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("max_send_speed"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) Z_LVAL_P(zoption));
+       }
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("max_recv_speed"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) Z_LVAL_P(zoption));
+       }
+       */
+       /* crashes
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("maxconnects"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_MAXCONNECTS, Z_LVAL_P(zoption));
+       } */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("fresh_connect"), IS_BOOL)) && Z_BVAL_P(zoption)) {
+               PHP_HTTP_CURL_OPT(CURLOPT_FRESH_CONNECT, 1L);
+       }
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("forbid_reuse"), IS_BOOL)) && Z_BVAL_P(zoption)) {
+               PHP_HTTP_CURL_OPT(CURLOPT_FORBID_REUSE, 1L);
+       }
+       
+       /* outgoing interface */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("interface"), IS_STRING))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_INTERFACE, Z_STRVAL_P(zoption));
+               
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("portrange"), IS_ARRAY))) {
+                       zval **prs, **pre;
+                       
+                       zend_hash_internal_pointer_reset(Z_ARRVAL_P(zoption));
+                       if (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void *) &prs)) {
+                               zend_hash_move_forward(Z_ARRVAL_P(zoption));
+                               if (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void *) &pre)) {
+                                       zval *prs_cpy = php_http_zsep(IS_LONG, *prs);
+                                       zval *pre_cpy = php_http_zsep(IS_LONG, *pre);
+                                       
+                                       if (Z_LVAL_P(prs_cpy) && Z_LVAL_P(pre_cpy)) {
+                                               PHP_HTTP_CURL_OPT(CURLOPT_LOCALPORT, MIN(Z_LVAL_P(prs_cpy), Z_LVAL_P(pre_cpy)));
+                                               PHP_HTTP_CURL_OPT(CURLOPT_LOCALPORTRANGE, labs(Z_LVAL_P(prs_cpy)-Z_LVAL_P(pre_cpy))+1L);
+                                       }
+                                       zval_ptr_dtor(&prs_cpy);
+                                       zval_ptr_dtor(&pre_cpy);
+                               }
+                       }
+               }
+       }
+
+       /* another port */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("port"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_PORT, Z_LVAL_P(zoption));
+       }
+       
+       /* RFC4007 zone_id */
+#if PHP_HTTP_CURL_VERSION(7,19,0)
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("address_scope"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_ADDRESS_SCOPE, Z_LVAL_P(zoption));
+       }
+#endif
+
+       /* auth */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("httpauth"), IS_STRING)) && Z_STRLEN_P(zoption)) {
+               PHP_HTTP_CURL_OPT(CURLOPT_USERPWD, Z_STRVAL_P(zoption));
+       }
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("httpauthtype"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTPAUTH, Z_LVAL_P(zoption));
+       }
+
+       /* redirects, defaults to 0 */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("redirect"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_FOLLOWLOCATION, Z_LVAL_P(zoption) ? 1L : 0L);
+               PHP_HTTP_CURL_OPT(CURLOPT_MAXREDIRS, Z_LVAL_P(zoption));
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("unrestrictedauth"), IS_BOOL))) {
+                       PHP_HTTP_CURL_OPT(CURLOPT_UNRESTRICTED_AUTH, Z_LVAL_P(zoption));
+               }
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("postredir"), IS_BOOL))) {
+#if PHP_HTTP_CURL_VERSION(7,19,1)
+                       PHP_HTTP_CURL_OPT(CURLOPT_POSTREDIR, Z_BVAL_P(zoption) ? 1L : 0L);
+#else
+                       PHP_HTTP_CURL_OPT(CURLOPT_POST301, Z_BVAL_P(zoption) ? 1L : 0L);
+#endif
+               }
+       }
+       
+       /* retries, defaults to 0 */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("retrycount"), IS_LONG))) {
+               request->_retry.count = Z_LVAL_P(zoption);
+               if ((zoption = php_http_request_option(request, options, ZEND_STRS("retrydelay"), IS_DOUBLE))) {
+                       request->_retry.delay = Z_DVAL_P(zoption);
+               } else {
+                       request->_retry.delay = 0;
+               }
+       } else {
+               request->_retry.count = 0;
+       }
+
+       /* referer */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("referer"), IS_STRING)) && Z_STRLEN_P(zoption)) {
+               PHP_HTTP_CURL_OPT(CURLOPT_REFERER, Z_STRVAL_P(zoption));
+       }
+
+       /* useragent, default "PECL::HTTP/version (PHP/version)" */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("useragent"), IS_STRING))) {
+               /* allow to send no user agent, not even default one */
+               if (Z_STRLEN_P(zoption)) {
+                       PHP_HTTP_CURL_OPT(CURLOPT_USERAGENT, Z_STRVAL_P(zoption));
+               } else {
+                       PHP_HTTP_CURL_OPT(CURLOPT_USERAGENT, NULL);
+               }
+       }
+
+       /* resume */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("resume"), IS_LONG)) && (Z_LVAL_P(zoption) > 0)) {
+               range_req = 1;
+               PHP_HTTP_CURL_OPT(CURLOPT_RESUME_FROM, Z_LVAL_P(zoption));
+       }
+       /* or range of kind array(array(0,499), array(100,1499)) */
+       else if ((zoption = php_http_request_option(request, options, ZEND_STRS("range"), IS_ARRAY)) && zend_hash_num_elements(Z_ARRVAL_P(zoption))) {
+               HashPosition pos1, pos2;
+               zval **rr, **rb, **re;
+               php_http_buffer rs;
+               
+               php_http_buffer_init(&rs);
+               FOREACH_VAL(pos1, zoption, rr) {
+                       if (Z_TYPE_PP(rr) == IS_ARRAY) {
+                               zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(rr), &pos2);
+                               if (SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(rr), (void *) &rb, &pos2)) {
+                                       zend_hash_move_forward_ex(Z_ARRVAL_PP(rr), &pos2);
+                                       if (SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(rr), (void *) &re, &pos2)) {
+                                               if (    ((Z_TYPE_PP(rb) == IS_LONG) || ((Z_TYPE_PP(rb) == IS_STRING) && is_numeric_string(Z_STRVAL_PP(rb), Z_STRLEN_PP(rb), NULL, NULL, 1))) &&
+                                                               ((Z_TYPE_PP(re) == IS_LONG) || ((Z_TYPE_PP(re) == IS_STRING) && is_numeric_string(Z_STRVAL_PP(re), Z_STRLEN_PP(re), NULL, NULL, 1)))) {
+                                                       zval *rbl = php_http_zsep(IS_LONG, *rb);
+                                                       zval *rel = php_http_zsep(IS_LONG, *re);
+                                                       
+                                                       if ((Z_LVAL_P(rbl) >= 0) && (Z_LVAL_P(rel) >= 0)) {
+                                                               php_http_buffer_appendf(&rs, "%ld-%ld,", Z_LVAL_P(rbl), Z_LVAL_P(rel));
+                                                       }
+                                                       zval_ptr_dtor(&rbl);
+                                                       zval_ptr_dtor(&rel);
+                                               }
+                                       }
+                               }
+                       }
+               }
+               
+               if (PHP_HTTP_BUFFER_LEN(&rs)) {
+                       zval *cached_range;
+                       
+                       /* ditch last comma */
+                       PHP_HTTP_BUFFER_VAL(&rs)[PHP_HTTP_BUFFER_LEN(&rs)-- -1] = '\0';
+                       /* cache string */
+                       MAKE_STD_ZVAL(cached_range);
+                       ZVAL_STRINGL(cached_range, PHP_HTTP_BUFFER_VAL(&rs), PHP_HTTP_BUFFER_LEN(&rs), 0);
+                       PHP_HTTP_CURL_OPT(CURLOPT_RANGE, Z_STRVAL_P(php_http_request_option_cache(request, ZEND_STRS("range"), 0, cached_range)));
+                       zval_ptr_dtor(&cached_range);
+               }
+       }
+
+       /* additional headers, array('name' => 'value') */
+       if (request->_cache.headers) {
+               curl_slist_free_all(request->_cache.headers);
+               request->_cache.headers = NULL;
+       }
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("headers"), IS_ARRAY))) {
+               php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0);
+               zval **header_val;
+               HashPosition pos;
+               php_http_buffer header;
+               
+               php_http_buffer_init(&header);
+               FOREACH_KEYVAL(pos, zoption, header_key, header_val) {
+                       if (header_key.type == HASH_KEY_IS_STRING) {
+                               zval *header_cpy = php_http_zsep(IS_STRING, *header_val);
+                               
+                               if (!strcasecmp(header_key.str, "range")) {
+                                       range_req = 1;
+                               }
+
+                               php_http_buffer_appendf(&header, "%s: %s", header_key.str, Z_STRVAL_P(header_cpy));
+                               php_http_buffer_fix(&header);
+                               request->_cache.headers = curl_slist_append(request->_cache.headers, PHP_HTTP_BUFFER_VAL(&header));
+                               php_http_buffer_reset(&header);
+                               
+                               zval_ptr_dtor(&header_cpy);
+                       }
+               }
+               php_http_buffer_dtor(&header);
+       }
+       /* etag */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("etag"), IS_STRING)) && Z_STRLEN_P(zoption)) {
+               zend_bool is_quoted = !((Z_STRVAL_P(zoption)[0] != '"') || (Z_STRVAL_P(zoption)[Z_STRLEN_P(zoption)-1] != '"'));
+               php_http_buffer header;
+               
+               php_http_buffer_init(&header);
+               php_http_buffer_appendf(&header, is_quoted?"%s: %s":"%s: \"%s\"", range_req?"If-Match":"If-None-Match", Z_STRVAL_P(zoption));
+               php_http_buffer_fix(&header);
+               request->_cache.headers = curl_slist_append(request->_cache.headers, PHP_HTTP_BUFFER_VAL(&header));
+               php_http_buffer_dtor(&header);
+       }
+       /* compression */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("compress"), IS_BOOL)) && Z_LVAL_P(zoption)) {
+               request->_cache.headers = curl_slist_append(request->_cache.headers, "Accept-Encoding: gzip;q=1.0,deflate;q=0.5");
+       }
+       PHP_HTTP_CURL_OPT(CURLOPT_HTTPHEADER, request->_cache.headers);
+
+       /* lastmodified */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("lastmodified"), IS_LONG))) {
+               if (Z_LVAL_P(zoption)) {
+                       if (Z_LVAL_P(zoption) > 0) {
+                               PHP_HTTP_CURL_OPT(CURLOPT_TIMEVALUE, Z_LVAL_P(zoption));
+                       } else {
+                               PHP_HTTP_CURL_OPT(CURLOPT_TIMEVALUE, (long) PHP_HTTP_G->env.request.time + Z_LVAL_P(zoption));
+                       }
+                       PHP_HTTP_CURL_OPT(CURLOPT_TIMECONDITION, (long) (range_req ? CURL_TIMECOND_IFUNMODSINCE : CURL_TIMECOND_IFMODSINCE));
+               } else {
+                       PHP_HTTP_CURL_OPT(CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
+               }
+       }
+
+       /* cookies, array('name' => 'value') */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("cookies"), IS_ARRAY))) {
+               php_http_buffer_dtor(&request->_cache.cookies);
+               if (zend_hash_num_elements(Z_ARRVAL_P(zoption))) {
+                       zval *urlenc_cookies = NULL;
+                       /* check whether cookies should not be urlencoded; default is to urlencode them */
+                       if ((!(urlenc_cookies = php_http_request_option(request, options, ZEND_STRS("encodecookies"), IS_BOOL))) || Z_BVAL_P(urlenc_cookies)) {
+                               if (SUCCESS == php_http_url_encode_hash_recursive(HASH_OF(zoption), &request->_cache.cookies, "; ", lenof("; "), NULL, 0 TSRMLS_CC)) {
+                                       php_http_buffer_fix(&request->_cache.cookies);
+                                       PHP_HTTP_CURL_OPT(CURLOPT_COOKIE, request->_cache.cookies.data);
+                               }
+                       } else {
+                               HashPosition pos;
+                               php_http_array_hashkey_t cookie_key = php_http_array_hashkey_init(0);
+                               zval **cookie_val;
+                               
+                               FOREACH_KEYVAL(pos, zoption, cookie_key, cookie_val) {
+                                       if (cookie_key.type == HASH_KEY_IS_STRING) {
+                                               zval *val = php_http_zsep(IS_STRING, *cookie_val);
+                                               php_http_buffer_appendf(&request->_cache.cookies, "%s=%s; ", cookie_key.str, Z_STRVAL_P(val));
+                                               zval_ptr_dtor(&val);
+                                       }
+                               }
+                               
+                               php_http_buffer_fix(&request->_cache.cookies);
+                               if (PHP_HTTP_BUFFER_LEN(&request->_cache.cookies)) {
+                                       PHP_HTTP_CURL_OPT(CURLOPT_COOKIE, PHP_HTTP_BUFFER_VAL(&request->_cache.cookies));
+                               }
+                       }
+               }
+       }
+
+       /* don't load session cookies from cookiestore */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("cookiesession"), IS_BOOL)) && Z_BVAL_P(zoption)) {
+               PHP_HTTP_CURL_OPT(CURLOPT_COOKIESESSION, 1L);
+       }
+
+       /* cookiestore, read initial cookies from that file and store cookies back into that file */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("cookiestore"), IS_STRING))) {
+               if (Z_STRLEN_P(zoption)) {
+                       if (SUCCESS != php_check_open_basedir(Z_STRVAL_P(zoption) TSRMLS_CC)) {
+                               return FAILURE;
+                       }
+               }
+               if (storage->cookiestore) {
+                       pefree(storage->cookiestore, 1);
+               }
+               storage->cookiestore = pestrndup(Z_STRVAL_P(zoption), Z_STRLEN_P(zoption), 1);
+               PHP_HTTP_CURL_OPT(CURLOPT_COOKIEFILE, storage->cookiestore);
+               PHP_HTTP_CURL_OPT(CURLOPT_COOKIEJAR, storage->cookiestore);
+       }
+
+       /* maxfilesize */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("maxfilesize"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_MAXFILESIZE, Z_LVAL_P(zoption));
+       }
+
+       /* http protocol */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("protocol"), IS_LONG))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_HTTP_VERSION, Z_LVAL_P(zoption));
+       }
+
+       /* timeout, defaults to 0 */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("timeout"), IS_DOUBLE))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_TIMEOUT_MS, (long)(Z_DVAL_P(zoption)*1000));
+       }
+       /* connecttimeout, defaults to 0 */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("connecttimeout"), IS_DOUBLE))) {
+               PHP_HTTP_CURL_OPT(CURLOPT_CONNECTTIMEOUT_MS, (long)(Z_DVAL_P(zoption)*1000));
+       }
+
+       /* ssl */
+       if ((zoption = php_http_request_option(request, options, ZEND_STRS("ssl"), IS_ARRAY))) {
+               php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+               zval **param;
+               HashPosition pos;
+
+               FOREACH_KEYVAL(pos, zoption, key, param) {
+                       if (key.type == HASH_KEY_IS_STRING) {
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLCERT, 0, 1);
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLCERTTYPE, 0, 0);
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLCERTPASSWD, 0, 0);
+
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLKEY, 0, 0);
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLKEYTYPE, 0, 0);
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLKEYPASSWD, 0, 0);
+
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLENGINE, 0, 0);
+                               PHP_HTTP_CURL_OPT_LONG(CURLOPT_SSLVERSION, 0);
+
+                               PHP_HTTP_CURL_OPT_LONG(CURLOPT_SSL_VERIFYPEER, 1);
+                               PHP_HTTP_CURL_OPT_LONG(CURLOPT_SSL_VERIFYHOST, 1);
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSL_CIPHER_LIST, 1, 0);
+
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_CAINFO, -3, 1);
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_CAPATH, -3, 1);
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_RANDOM_FILE, -3, 1);
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_EGDSOCKET, -3, 1);
+#if PHP_HTTP_CURL_VERSION(7,19,0)
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_ISSUERCERT, -3, 1);
+       #if defined(PHP_HTTP_HAVE_OPENSSL)
+                               PHP_HTTP_CURL_OPT_STRING(CURLOPT_CRLFILE, -3, 1);
+       #endif
+#endif
+#if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL)
+                               PHP_HTTP_CURL_OPT_LONG(CURLOPT_CERTINFO, -3);
+#endif
+                       }
+               }
+       }
+
+       /* request method */
+       switch (request->meth) {
+               case PHP_HTTP_GET:
+                       PHP_HTTP_CURL_OPT(CURLOPT_HTTPGET, 1L);
+                       break;
+
+               case PHP_HTTP_HEAD:
+                       PHP_HTTP_CURL_OPT(CURLOPT_NOBODY, 1L);
+                       break;
+
+               case PHP_HTTP_POST:
+                       PHP_HTTP_CURL_OPT(CURLOPT_POST, 1L);
+                       break;
+
+               case PHP_HTTP_PUT:
+                       PHP_HTTP_CURL_OPT(CURLOPT_UPLOAD, 1L);
+                       break;
+
+               default: {
+                       const char *meth = php_http_request_method_name(request->meth);
+
+                       if (meth) {
+                               PHP_HTTP_CURL_OPT(CURLOPT_CUSTOMREQUEST, meth);
+                       } else {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_METHOD, "Unsupported request method: %d (%s)", request->meth, request->url);
+                               return FAILURE;
+                       }
+                       break;
+               }
+       }
+
+       /* attach request body */
+       if (request->body && (request->meth != PHP_HTTP_GET) && (request->meth != PHP_HTTP_HEAD) && (request->meth != PHP_HTTP_OPTIONS)) {
+               if (1 || request->meth == PHP_HTTP_PUT) {
+                       /* PUT/UPLOAD _needs_ READDATA */
+                       PHP_HTTP_CURL_OPT(CURLOPT_IOCTLDATA, request);
+                       PHP_HTTP_CURL_OPT(CURLOPT_READDATA, request);
+                       PHP_HTTP_CURL_OPT(CURLOPT_INFILESIZE, php_http_message_body_size(request->body));
+               } else {
+                       abort();
+                       //PHP_HTTP_CURL_OPT(CURLOPT_POSTFIELDS, request->body->real->data);
+                       PHP_HTTP_CURL_OPT(CURLOPT_POSTFIELDSIZE, php_http_message_body_size(request->body));
+               }
+       }
+
+       return SUCCESS;
+}
+
+PHP_HTTP_API void php_http_request_exec(php_http_request_t *request)
+{
+       uint tries = 0;
+       CURLcode result;
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+retry:
+       if (CURLE_OK != (result = curl_easy_perform(request->ch))) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "%s; %s (%s)", curl_easy_strerror(result), php_http_request_storage_get(request->ch)->errorbuffer, request->url);
+               if (EG(exception)) {
+                       add_property_long(EG(exception), "curlCode", result);
+               }
+               
+               if (request->_retry.count > tries++) {
+                       switch (result) {
+                               case CURLE_COULDNT_RESOLVE_PROXY:
+                               case CURLE_COULDNT_RESOLVE_HOST:
+                               case CURLE_COULDNT_CONNECT:
+                               case CURLE_WRITE_ERROR:
+                               case CURLE_READ_ERROR:
+                               case CURLE_OPERATION_TIMEDOUT:
+                               case CURLE_SSL_CONNECT_ERROR:
+                               case CURLE_GOT_NOTHING:
+                               case CURLE_SSL_ENGINE_SETFAILED:
+                               case CURLE_SEND_ERROR:
+                               case CURLE_RECV_ERROR:
+                               case CURLE_SSL_ENGINE_INITFAILED:
+                               case CURLE_LOGIN_DENIED:
+                                       if (request->_retry.delay >= PHP_HTTP_DIFFSEC) {
+                                               php_http_sleep(request->_retry.delay);
+                                       }
+                                       goto retry;
+                               default:
+                                       break;
+                       }
+               }
+       }
+}
+
+static size_t php_http_curl_read_callback(void *data, size_t len, size_t n, void *ctx)
+{
+       php_http_request_t *request = (php_http_request_t *) ctx;
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+       if (request->body) {
+               return php_stream_read(php_http_message_body_stream(request->body), data, len * n);
+       }
+       return 0;
+}
+
+static int php_http_curl_progress_callback(void *ctx, double dltotal, double dlnow, double ultotal, double ulnow)
+{
+       php_http_request_t *request = (php_http_request_t *) ctx;
+
+       request->_progress.state.dl.total = dltotal;
+       request->_progress.state.dl.now = dlnow;
+       request->_progress.state.ul.total = ultotal;
+       request->_progress.state.ul.now = ulnow;
+
+       if (request->_progress.callback) {
+               zval *param, retval;
+               TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+               INIT_PZVAL(&retval);
+               ZVAL_NULL(&retval);
+       
+               MAKE_STD_ZVAL(param);
+               array_init(param);
+               add_assoc_double(param, "dltotal", request->_progress.state.dl.total);
+               add_assoc_double(param, "dlnow", request->_progress.state.dl.now);
+               add_assoc_double(param, "ultotal", request->_progress.state.ul.total);
+               add_assoc_double(param, "ulnow", request->_progress.state.ul.now);
+
+               with_error_handling(EH_NORMAL, NULL) {
+                       request->_progress.in_cb = 1;
+                       call_user_function(EG(function_table), NULL, request->_progress.callback, &retval, 1, &param TSRMLS_CC);
+                       request->_progress.in_cb = 0;
+               } end_error_handling();
+
+               zval_ptr_dtor(&param);
+               zval_dtor(&retval);
+       }
+       
+       return 0;
+}
+
+static curlioerr php_http_curl_ioctl_callback(CURL *ch, curliocmd cmd, void *ctx)
+{
+       php_http_request_t *request = (php_http_request_t *) ctx;
+       TSRMLS_FETCH_FROM_CTX(request->ts);
+       
+       if (cmd != CURLIOCMD_RESTARTREAD) {
+               return CURLIOE_UNKNOWNCMD;
+       }
+       
+       if (request->body) {
+               if (SUCCESS == php_stream_rewind(php_http_message_body_stream(request->body))) {
+                       return CURLIOE_OK;
+               }
+       }
+       
+       return CURLIOE_FAILRESTART;
+}
+
+static int php_http_curl_raw_callback(CURL *ch, curl_infotype type, char *data, size_t length, void *ctx)
+{
+       php_http_request_t *request = (php_http_request_t *) ctx;
+
+       /* process data */
+       switch (type) {
+               case CURLINFO_HEADER_IN:
+               case CURLINFO_DATA_IN:
+               case CURLINFO_HEADER_OUT:
+               case CURLINFO_DATA_OUT:
+                       php_http_buffer_append(request->parser.buf, data, length);
+                       if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(request->parser.ctx, request->parser.buf, 0, &request->parser.msg)) {
+                               return -1;
+                       }
+                       break;
+               default:
+                       break;
+       }
+
+       /* debug */
+#if 0
+       {
+               const char _sym[] = "><><><";
+               if (type) {
+                       for (fprintf(stderr, "%c ", _sym[type-1]); length--; data++) {
+                               fprintf(stderr, PHP_HTTP_IS_CTYPE(print, *data)?"%c":"\\x%02X", (int) *data);
+                               if (*data == '\n' && length) {
+                                       fprintf(stderr, "\n%c ", _sym[type-1]);
+                               }
+                       }
+                       fprintf(stderr, "\n");
+               } else {
+                       fprintf(stderr, "# %s", data);
+               }
+       }
+#endif
+       
+       return 0;
+}
+
+static inline zval *php_http_request_option(php_http_request_t *r, HashTable *options, char *key, size_t keylen, int type)
+{
+       TSRMLS_FETCH_FROM_CTX(r->ts);
+
+       if (options) {
+               zval **zoption;
+               ulong h = zend_hash_func(key, keylen);
+               
+               if (SUCCESS == zend_hash_quick_find(options, key, keylen, h, (void *) &zoption)) {
+                       zval *option, *cached;
+                       
+                       option = php_http_zsep(type, *zoption);
+                       cached = php_http_request_option_cache(r, key, keylen, h, option);
+                       
+                       zval_ptr_dtor(&option);
+                       return cached;
+               }
+       }
+       
+       return NULL;
+}
+
+static inline zval *php_http_request_option_cache(php_http_request_t *r, char *key, size_t keylen, ulong h, zval *opt)
+{
+       TSRMLS_FETCH_FROM_CTX(r->ts);
+       Z_ADDREF_P(opt);
+       
+       if (h) {
+               zend_hash_quick_update(&r->_cache.options, key, keylen, h, &opt, sizeof(zval *), NULL);
+       } else {
+               zend_hash_update(&r->_cache.options, key, keylen, &opt, sizeof(zval *), NULL);
+       }
+       
+       return opt;
+}
+
+static inline int php_http_request_cookies_enabled(php_http_request_t *request) {
+       php_http_request_storage_t *st;
+       
+       if (request->ch && (st = php_http_request_storage_get(request->ch)) && st->cookiestore) {
+               /* cookies are enabled */
+               return 1;
+       }
+       return 0;
+}
+
+/* USERLAND */
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpRequest, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpRequest, method, 0)
+#define PHP_HTTP_REQUEST_ME(method, visibility)        PHP_ME(HttpRequest, method, PHP_HTTP_ARGS(HttpRequest, method), visibility)
+#define PHP_HTTP_REQUEST_ALIAS(method, func)   PHP_HTTP_STATIC_ME_ALIAS(method, func, PHP_HTTP_ARGS(HttpRequest, method))
+#define PHP_HTTP_REQUEST_MALIAS(me, al, vis)   ZEND_FENTRY(me, ZEND_MN(HttpRequest_##al), PHP_HTTP_ARGS(HttpRequest, al), vis)
+
+PHP_HTTP_BEGIN_ARGS(__construct, 0)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(method, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(factory, 0)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(method, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+       PHP_HTTP_ARG_VAL(class_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getOptions);
+PHP_HTTP_BEGIN_ARGS(setOptions, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getSslOptions);
+PHP_HTTP_BEGIN_ARGS(setSslOptions, 0)
+       PHP_HTTP_ARG_VAL(ssl_options, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(addSslOptions, 0)
+       PHP_HTTP_ARG_VAL(ssl_optins, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getHeaders);
+PHP_HTTP_BEGIN_ARGS(setHeaders, 0)
+       PHP_HTTP_ARG_VAL(headers, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(addHeaders, 1)
+       PHP_HTTP_ARG_VAL(headers, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getCookies);
+PHP_HTTP_BEGIN_ARGS(setCookies, 0)
+       PHP_HTTP_ARG_VAL(cookies, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(addCookies, 1)
+       PHP_HTTP_ARG_VAL(cookies, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(enableCookies);
+PHP_HTTP_BEGIN_ARGS(resetCookies, 0)
+       PHP_HTTP_ARG_VAL(session_only, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_EMPTY_ARGS(flushCookies);
+
+PHP_HTTP_EMPTY_ARGS(getUrl);
+PHP_HTTP_BEGIN_ARGS(setUrl, 1)
+       PHP_HTTP_ARG_VAL(url, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getMethod);
+PHP_HTTP_BEGIN_ARGS(setMethod, 1)
+       PHP_HTTP_ARG_VAL(request_method, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getContentType);
+PHP_HTTP_BEGIN_ARGS(setContentType, 1)
+       PHP_HTTP_ARG_VAL(content_type, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getQueryData);
+PHP_HTTP_BEGIN_ARGS(setQueryData, 0)
+       PHP_HTTP_ARG_VAL(query_data, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(addQueryData, 1)
+       PHP_HTTP_ARG_VAL(query_data, 0)
+PHP_HTTP_END_ARGS;
+
+
+PHP_HTTP_EMPTY_ARGS(getBody);
+PHP_HTTP_BEGIN_ARGS(setBody, 0)
+       PHP_HTTP_ARG_OBJ(http\\message\\Body, body, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(addBody, 1)
+       PHP_HTTP_ARG_OBJ(http\\message\\Body, body, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(getResponseCookies, 0)
+       PHP_HTTP_ARG_VAL(flags, 0)
+       PHP_HTTP_ARG_VAL(allowed_extras, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getResponseBody);
+PHP_HTTP_EMPTY_ARGS(getResponseCode);
+PHP_HTTP_EMPTY_ARGS(getResponseStatus);
+PHP_HTTP_BEGIN_ARGS(getResponseInfo, 0)
+       PHP_HTTP_ARG_VAL(name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(getResponseHeader, 0)
+       PHP_HTTP_ARG_VAL(header_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getMessageClass);
+PHP_HTTP_BEGIN_ARGS(setMessageClass, 1)
+       PHP_HTTP_ARG_VAL(message_class_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(getResponseMessage);
+PHP_HTTP_EMPTY_ARGS(getRawResponseMessage);
+PHP_HTTP_EMPTY_ARGS(getRequestMessage);
+PHP_HTTP_EMPTY_ARGS(getRawRequestMessage);
+PHP_HTTP_EMPTY_ARGS(getHistory);
+PHP_HTTP_EMPTY_ARGS(clearHistory);
+PHP_HTTP_EMPTY_ARGS(send);
+
+PHP_HTTP_BEGIN_ARGS(get, 1)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+       PHP_HTTP_ARG_VAL(info, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(head, 1)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+       PHP_HTTP_ARG_VAL(info, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(postData, 2)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(data, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+       PHP_HTTP_ARG_VAL(info, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(postFields, 2)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(data, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+       PHP_HTTP_ARG_VAL(info, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(putData, 2)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(data, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+       PHP_HTTP_ARG_VAL(info, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(putFile, 2)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(file, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+       PHP_HTTP_ARG_VAL(info, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(putStream, 2)
+       PHP_HTTP_ARG_VAL(url, 0)
+       PHP_HTTP_ARG_VAL(stream, 0)
+       PHP_HTTP_ARG_VAL(options, 0)
+       PHP_HTTP_ARG_VAL(info, 1)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(methodRegister, 1)
+       PHP_HTTP_ARG_VAL(method_name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(methodUnregister, 1)
+       PHP_HTTP_ARG_VAL(method, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(methodName, 1)
+       PHP_HTTP_ARG_VAL(method_id, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(methodExists, 1)
+       PHP_HTTP_ARG_VAL(method, 0)
+PHP_HTTP_END_ARGS;
+
+#ifdef HAVE_CURL_FORMGET
+PHP_HTTP_BEGIN_ARGS(encodeBody, 2)
+       PHP_HTTP_ARG_VAL(fields, 0)
+       PHP_HTTP_ARG_VAL(files, 0)
+PHP_HTTP_END_ARGS;
+#endif
+
+zend_class_entry *php_http_request_class_entry;
+zend_function_entry php_http_request_method_entry[] = {
+       PHP_HTTP_REQUEST_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+
+       PHP_HTTP_REQUEST_ME(setOptions, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getOptions, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(setSslOptions, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getSslOptions, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(addSslOptions, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(addHeaders, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getHeaders, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(setHeaders, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(addCookies, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getCookies, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(setCookies, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(enableCookies, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(resetCookies, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(flushCookies, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(setMethod, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getMethod, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(setUrl, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getUrl, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(setContentType, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getContentType, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(setQueryData, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getQueryData, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(addQueryData, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(setBody, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getBody, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(addBody, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(send, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(getResponseHeader, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getResponseCookies, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getResponseCode, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getResponseStatus, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getResponseBody, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getResponseInfo, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getResponseMessage, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getRequestMessage, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(getHistory, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(clearHistory, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQUEST_ME(getMessageClass, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQUEST_ME(setMessageClass, ZEND_ACC_PUBLIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+static zend_object_handlers php_http_request_object_handlers;
+
+zend_object_value php_http_request_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_request_object_new_ex(ce, NULL, NULL);
+}
+
+zend_object_value php_http_request_object_new_ex(zend_class_entry *ce, CURL *ch, php_http_request_object_t **ptr TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_request_object_t *o;
+
+       o = ecalloc(1, sizeof(php_http_request_object_t));
+       zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       o->request = php_http_request_init(NULL, ch, 0, NULL TSRMLS_CC);
+
+       if (ptr) {
+               *ptr = o;
+       }
+
+       ov.handle = zend_objects_store_put(o, NULL, php_http_request_object_free, NULL TSRMLS_CC);
+       ov.handlers = &php_http_request_object_handlers;
+
+       return ov;
+}
+
+zend_object_value php_http_request_object_clone(zval *this_ptr TSRMLS_DC)
+{
+       zend_object_value new_ov;
+       php_http_request_object_t *new_obj, *old_obj = zend_object_store_get_object(this_ptr TSRMLS_CC);
+
+       new_ov = php_http_request_object_new_ex(old_obj->zo.ce, NULL, &new_obj TSRMLS_CC);
+       if (old_obj->request->ch) {
+               php_http_curl_init(php_http_curl_copy(old_obj->request->ch), new_obj->request TSRMLS_CC);
+       }
+
+       zend_objects_clone_members(&new_obj->zo, new_ov, &old_obj->zo, Z_OBJ_HANDLE_P(this_ptr) TSRMLS_CC);
+       /* FIXME */
+
+       return new_ov;
+}
+
+void php_http_request_object_free(void *object TSRMLS_DC)
+{
+       php_http_request_object_t *o = (php_http_request_object_t *) object;
+
+       php_http_request_free(&o->request);
+       zend_object_std_dtor((zend_object *) o TSRMLS_CC);
+       efree(o);
+}
+
+static inline void php_http_request_object_check_request_content_type(zval *this_ptr TSRMLS_DC)
+{
+       zval *ctype = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("contentType"), 0 TSRMLS_CC);
+
+       if (Z_STRLEN_P(ctype)) {
+               zval **headers, *opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC);
+
+               if (    (Z_TYPE_P(opts) == IS_ARRAY) &&
+                               (SUCCESS == zend_hash_find(Z_ARRVAL_P(opts), ZEND_STRS("headers"), (void *) &headers)) &&
+                               (Z_TYPE_PP(headers) == IS_ARRAY)) {
+                       zval **ct_header;
+
+                       /* only override if not already set */
+                       if ((SUCCESS != zend_hash_find(Z_ARRVAL_PP(headers), ZEND_STRS("Content-Type"), (void *) &ct_header))) {
+                               add_assoc_stringl(*headers, "Content-Type", Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1);
+                       } else
+                       /* or not a string, zero length string or a string of spaces */
+                       if ((Z_TYPE_PP(ct_header) != IS_STRING) || !Z_STRLEN_PP(ct_header)) {
+                               add_assoc_stringl(*headers, "Content-Type", Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1);
+                       } else {
+                               int i, only_space = 1;
+
+                               /* check for spaces only */
+                               for (i = 0; i < Z_STRLEN_PP(ct_header); ++i) {
+                                       if (!PHP_HTTP_IS_CTYPE(space, Z_STRVAL_PP(ct_header)[i])) {
+                                               only_space = 0;
+                                               break;
+                                       }
+                               }
+                               if (only_space) {
+                                       add_assoc_stringl(*headers, "Content-Type", Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1);
+                               }
+                       }
+               } else {
+                       zval *headers;
+
+                       MAKE_STD_ZVAL(headers);
+                       array_init(headers);
+                       add_assoc_stringl(headers, "Content-Type", Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1);
+                       zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "addheaders", NULL, headers);
+                       zval_ptr_dtor(&headers);
+               }
+       }
+}
+
+static inline zend_object_value php_http_request_object_message(zval *this_ptr, php_http_message_t *msg TSRMLS_DC)
+{
+       zend_object_value ov;
+       zval *zcn = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("messageClass"), 0 TSRMLS_CC);
+       zend_class_entry *class_entry;
+
+       if (Z_STRLEN_P(zcn)
+       &&      (class_entry = zend_fetch_class(Z_STRVAL_P(zcn), Z_STRLEN_P(zcn), 0 TSRMLS_CC))
+       &&      (SUCCESS == php_http_new(&ov, class_entry, (php_http_new_t) php_http_message_object_new_ex, php_http_message_class_entry, msg, NULL TSRMLS_CC))) {
+               return ov;
+       } else {
+               return php_http_message_object_new_ex(php_http_message_class_entry, msg, NULL TSRMLS_CC);
+       }
+}
+
+STATUS php_http_request_object_requesthandler(php_http_request_object_t *obj, zval *this_ptr TSRMLS_DC)
+{
+       STATUS status = SUCCESS;
+       zval *zurl, *zmeth, *zbody, *zqdata, *zoptions;
+       php_http_message_body_t *body = NULL;
+       php_url *tmp, qdu = {0};
+
+       php_http_request_reset(obj->request);
+       PHP_HTTP_CHECK_CURL_INIT(obj->request->ch, php_http_curl_init(NULL, obj->request TSRMLS_CC), return FAILURE);
+       php_http_request_object_check_request_content_type(getThis() TSRMLS_CC);
+
+       zmeth = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("method"), 0 TSRMLS_CC);
+       obj->request->meth = Z_LVAL_P(zmeth);
+
+       zurl = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("url"), 0 TSRMLS_CC);
+       zqdata = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), 0 TSRMLS_CC);
+       if (Z_STRLEN_P(zqdata)) {
+               qdu.query = Z_STRVAL_P(zqdata);
+       }
+       php_http_url(0, tmp = php_url_parse(Z_STRVAL_P(zurl)), &qdu, NULL, &obj->request->url, NULL TSRMLS_CC);
+       php_url_free(tmp);
+
+       zbody = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody"), 0 TSRMLS_CC);
+       if (Z_TYPE_P(zbody) == IS_OBJECT) {
+               body = ((php_http_message_body_object_t *)zend_object_store_get_object(zbody TSRMLS_CC))->body;
+       }
+       obj->request->body = body;
+
+       zoptions = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC);
+       php_http_request_prepare(obj->request, Z_ARRVAL_P(zoptions));
+
+       /* check if there's a onProgress method and add it as progress callback if one isn't already set */
+       if (zend_hash_exists(&Z_OBJCE_P(getThis())->function_table, ZEND_STRS("onprogress"))) {
+               zval **entry, *pcb;
+
+               if ((Z_TYPE_P(zoptions) != IS_ARRAY)
+               ||      (SUCCESS != zend_hash_find(Z_ARRVAL_P(zoptions), ZEND_STRS("onprogress"), (void *) &entry)
+               ||      (!zend_is_callable(*entry, 0, NULL TSRMLS_CC)))) {
+                       MAKE_STD_ZVAL(pcb);
+                       array_init(pcb);
+                       Z_ADDREF_P(getThis());
+                       add_next_index_zval(pcb, getThis());
+                       add_next_index_stringl(pcb, "onprogress", lenof("onprogress"), 1);
+                       php_http_request_set_progress_callback(obj->request, pcb);
+                       zval_ptr_dtor(&pcb);
+               }
+       }
+
+       return status;
+}
+
+STATUS php_http_request_object_responsehandler(php_http_request_object_t *obj, zval *this_ptr TSRMLS_DC)
+{
+       STATUS ret = SUCCESS;
+       zval *info;
+       php_http_message_t *msg;
+
+       /* always fetch info */
+       MAKE_STD_ZVAL(info);
+       array_init(info);
+       php_http_request_info(obj->request, Z_ARRVAL_P(info));
+       zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseInfo"), info TSRMLS_CC);
+       zval_ptr_dtor(&info);
+
+       /* update history * /
+       if (i_zend_is_true(zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("recordHistory"), 0 TSRMLS_CC))) {
+               zval *new_hist, *old_hist = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("history"), 0 TSRMLS_CC);
+               zend_object_value ov = php_http_request_object_message(getThis(), obj->request->message_parser.message TSRMLS_CC);
+
+               MAKE_STD_ZVAL(new_hist);
+               ZVAL_OBJVAL(new_hist, ov, 0);
+
+               if (Z_TYPE_P(old_hist) == IS_OBJECT) {
+                       php_http_message_object_prepend(new_hist, old_hist, 0 TSRMLS_CC);
+               }
+
+               zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("history"), new_hist TSRMLS_CC);
+               zval_ptr_dtor(&new_hist);
+       }
+*/
+//     if ((msg = obj->request->_current.request)) {
+//             /* update request message */
+//             zval *message;
+//
+//             MAKE_STD_ZVAL(message);
+//             ZVAL_OBJVAL(message, php_http_request_object_message(getThis(), msg TSRMLS_CC), 1);
+//             zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestMessage"), message TSRMLS_CC);
+//     }
+// fprintf(stderr, "RESPONSE MESSAGE: %p\n", obj->request->parser.msg);
+       if ((msg = obj->request->parser.msg)) {
+               /* update properties with response info */
+               zval *message;
+
+               zend_update_property_long(php_http_request_class_entry, getThis(), ZEND_STRL("responseCode"), msg->http.info.response.code TSRMLS_CC);
+               zend_update_property_string(php_http_request_class_entry, getThis(), ZEND_STRL("responseStatus"), STR_PTR(msg->http.info.response.status) TSRMLS_CC);
+
+               MAKE_STD_ZVAL(message);
+               ZVAL_OBJVAL(message, php_http_request_object_message(getThis(), msg TSRMLS_CC), 0);
+               zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), message TSRMLS_CC);
+               zval_ptr_dtor(&message);
+               obj->request->parser.msg = php_http_message_init(NULL, 0 TSRMLS_CC);
+       } else {
+               /* update properties with empty values */
+               zval *znull;
+
+               MAKE_STD_ZVAL(znull);
+               ZVAL_NULL(znull);
+               zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), znull TSRMLS_CC);
+               zval_ptr_dtor(&znull);
+
+               zend_update_property_long(php_http_request_class_entry, getThis(), ZEND_STRL("responseCode"), 0 TSRMLS_CC);
+               zend_update_property_string(php_http_request_class_entry, getThis(), ZEND_STRL("responseStatus"), "" TSRMLS_CC);
+       }
+
+       php_http_request_set_progress_callback(obj->request, NULL);
+
+       if (!EG(exception) && zend_hash_exists(&Z_OBJCE_P(getThis())->function_table, ZEND_STRS("onfinish"))) {
+               zval *param;
+
+               MAKE_STD_ZVAL(param);
+               ZVAL_BOOL(param, ret == SUCCESS);
+               with_error_handling(EH_NORMAL, NULL) {
+                       zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "onfinish", NULL, param);
+               } end_error_handling();
+               zval_ptr_dtor(&param);
+       }
+
+       return ret;
+}
+
+static int apply_pretty_key(void *pDest, int num_args, va_list args, zend_hash_key *hash_key)
+{
+       if (hash_key->arKey && hash_key->nKeyLength > 1) {
+               hash_key->h = zend_hash_func(php_http_pretty_key(hash_key->arKey, hash_key->nKeyLength - 1, 1, 0), hash_key->nKeyLength);
+       }
+       return ZEND_HASH_APPLY_KEEP;
+}
+
+static inline void php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAMETERS, char *key, size_t len, int overwrite, int prettify_keys)
+{
+       zval *old_opts, *new_opts, *opts = NULL, **entry = NULL;
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a/!", &opts)) {
+               RETURN_FALSE;
+       }
+
+       MAKE_STD_ZVAL(new_opts);
+       array_init(new_opts);
+       old_opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC);
+       if (Z_TYPE_P(old_opts) == IS_ARRAY) {
+               array_copy(Z_ARRVAL_P(old_opts), Z_ARRVAL_P(new_opts));
+       }
+
+       if (SUCCESS == zend_hash_find(Z_ARRVAL_P(new_opts), key, len, (void *) &entry)) {
+               if (overwrite) {
+                       zend_hash_clean(Z_ARRVAL_PP(entry));
+               }
+               if (opts && zend_hash_num_elements(Z_ARRVAL_P(opts))) {
+                       if (overwrite) {
+                               array_copy(Z_ARRVAL_P(opts), Z_ARRVAL_PP(entry));
+                       } else {
+                               array_join(Z_ARRVAL_P(opts), Z_ARRVAL_PP(entry), 0, prettify_keys ? ARRAY_JOIN_PRETTIFY : 0);
+                       }
+               }
+       } else if (opts) {
+               if (prettify_keys) {
+                       zend_hash_apply_with_arguments(Z_ARRVAL_P(opts) TSRMLS_CC, apply_pretty_key, 0, NULL);
+               }
+               Z_ADDREF_P(opts);
+               add_assoc_zval_ex(new_opts, key, len, opts);
+       }
+       zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), new_opts TSRMLS_CC);
+       zval_ptr_dtor(&new_opts);
+
+       RETURN_TRUE;
+}
+
+static inline void php_http_request_object_get_options_subr(INTERNAL_FUNCTION_PARAMETERS, char *key, size_t len)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               zval *opts, **options;
+
+               opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC);
+               array_init(return_value);
+
+               if (    (Z_TYPE_P(opts) == IS_ARRAY) &&
+                               (SUCCESS == zend_hash_find(Z_ARRVAL_P(opts), key, len, (void *) &options))) {
+                       convert_to_array(*options);
+                       array_copy(Z_ARRVAL_PP(options), Z_ARRVAL_P(return_value));
+               }
+       }
+}
+
+
+PHP_METHOD(HttpRequest, __construct)
+{
+       char *url_str = NULL;
+       int url_len;
+       long meth = -1;
+       zval *options = NULL;
+
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sla!", &url_str, &url_len, &meth, &options)) {
+                       if (url_str) {
+                               zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("url"), url_str, url_len TSRMLS_CC);
+                       }
+                       if (meth > -1) {
+                               zend_update_property_long(php_http_request_class_entry, getThis(), ZEND_STRL("method"), meth TSRMLS_CC);
+                       }
+                       if (options) {
+                               zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "setoptions", NULL, options);
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequest, setOptions)
+{
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       HashPosition pos;
+       zval *opts = NULL, *old_opts, *new_opts, *add_opts, **opt;
+
+       if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!/", &opts)) {
+               RETURN_FALSE;
+       }
+
+       MAKE_STD_ZVAL(new_opts);
+       array_init(new_opts);
+
+       if (!opts || !zend_hash_num_elements(Z_ARRVAL_P(opts))) {
+               zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), new_opts TSRMLS_CC);
+               zval_ptr_dtor(&new_opts);
+               RETURN_TRUE;
+       }
+
+       MAKE_STD_ZVAL(add_opts);
+       array_init(add_opts);
+       /* some options need extra attention -- thus cannot use array_merge() directly */
+       FOREACH_KEYVAL(pos, opts, key, opt) {
+               if (key.type == HASH_KEY_IS_STRING) {
+#define KEYMATCH(k, s) ((sizeof(s)==k.len) && !strcasecmp(k.str, s))
+                       if (KEYMATCH(key, "headers")) {
+                               zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "addheaders", NULL, *opt);
+                       } else if (KEYMATCH(key, "cookies")) {
+                               zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "addcookies", NULL, *opt);
+                       } else if (KEYMATCH(key, "ssl")) {
+                               zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "addssloptions", NULL, *opt);
+                       } else if (KEYMATCH(key, "url") || KEYMATCH(key, "uri")) {
+                               zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "seturl", NULL, *opt);
+                       } else if (KEYMATCH(key, "method")) {
+                               zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "setmethod", NULL, *opt);
+                       } else if (KEYMATCH(key, "flushcookies")) {
+                               if (i_zend_is_true(*opt)) {
+                                       php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                                       php_http_request_flush_cookies(obj->request);
+                               }
+                       } else if (KEYMATCH(key, "resetcookies")) {
+                               php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                               php_http_request_reset_cookies(obj->request, (zend_bool) i_zend_is_true(*opt));
+                       } else if (KEYMATCH(key, "enablecookies")) {
+                               php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                               php_http_request_enable_cookies(obj->request);
+                       } else if (KEYMATCH(key, "recordHistory")) {
+                               zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("recordHistory"), *opt TSRMLS_CC);
+                       } else if (KEYMATCH(key, "messageClass")) {
+                               zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "setmessageclass", NULL, *opt);
+                       } else if (Z_TYPE_PP(opt) == IS_NULL) {
+                               old_opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC);
+                               if (Z_TYPE_P(old_opts) == IS_ARRAY) {
+                                       zend_hash_del(Z_ARRVAL_P(old_opts), key.str, key.len);
+                               }
+                       } else {
+                               Z_ADDREF_P(*opt);
+                               add_assoc_zval_ex(add_opts, key.str, key.len, *opt);
+                       }
+               }
+       }
+
+       old_opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC);
+       if (Z_TYPE_P(old_opts) == IS_ARRAY) {
+               array_copy(Z_ARRVAL_P(old_opts), Z_ARRVAL_P(new_opts));
+       }
+       array_join(Z_ARRVAL_P(add_opts), Z_ARRVAL_P(new_opts), 0, 0);
+       zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), new_opts TSRMLS_CC);
+       zval_ptr_dtor(&new_opts);
+       zval_ptr_dtor(&add_opts);
+
+       RETURN_TRUE;
+}
+
+
+
+PHP_METHOD(HttpRequest, getOptions)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "options");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, setSslOptions)
+{
+       php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("ssl"), 1, 0);
+}
+
+PHP_METHOD(HttpRequest, addSslOptions)
+{
+       php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("ssl"), 0, 0);
+}
+
+PHP_METHOD(HttpRequest, getSslOptions)
+{
+       php_http_request_object_get_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("ssl"));
+}
+
+PHP_METHOD(HttpRequest, addHeaders)
+{
+       php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("headers"), 0, 1);
+}
+
+PHP_METHOD(HttpRequest, setHeaders)
+{
+       php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("headers"), 1, 1);
+}
+
+PHP_METHOD(HttpRequest, getHeaders)
+{
+       php_http_request_object_get_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("headers"));
+}
+
+PHP_METHOD(HttpRequest, setCookies)
+{
+       php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("cookies"), 1, 0);
+}
+
+PHP_METHOD(HttpRequest, addCookies)
+{
+       php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("cookies"), 0, 0);
+}
+
+PHP_METHOD(HttpRequest, getCookies)
+{
+       php_http_request_object_get_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("cookies"));
+}
+
+PHP_METHOD(HttpRequest, enableCookies)
+{
+       if (SUCCESS == zend_parse_parameters_none()){
+               php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_SUCCESS(php_http_request_enable_cookies(obj->request));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, resetCookies)
+{
+       zend_bool session_only = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &session_only)) {
+               php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+               RETURN_SUCCESS(php_http_request_reset_cookies(obj->request, session_only));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, flushCookies)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_SUCCESS(php_http_request_flush_cookies(obj->request));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, setUrl)
+{
+       char *url_str = NULL;
+       int url_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &url_len, &url_len)) {
+               zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("url"), url_str, url_len TSRMLS_CC);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getUrl)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "url");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, setMethod)
+{
+       long meth;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &meth)) {
+               zend_update_property_long(php_http_request_class_entry, getThis(), ZEND_STRL("method"), meth TSRMLS_CC);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getMethod)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "method");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, setContentType)
+{
+       char *ctype;
+       int ct_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &ctype, &ct_len)) {
+               if (ct_len) {
+                       PHP_HTTP_CHECK_CONTENT_TYPE(ctype, RETURN_FALSE);
+               }
+               zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("contentType"), ctype, ct_len TSRMLS_CC);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getContentType)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "contentType");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, setQueryData)
+{
+       zval *qdata = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!", &qdata)) {
+               if ((!qdata) || Z_TYPE_P(qdata) == IS_NULL) {
+                       zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), "", 0 TSRMLS_CC);
+               } else if ((Z_TYPE_P(qdata) == IS_ARRAY) || (Z_TYPE_P(qdata) == IS_OBJECT)) {
+                       char *query_data_str = NULL;
+                       size_t query_data_len;
+
+                       if (SUCCESS != php_http_url_encode_hash(HASH_OF(qdata), 0, NULL, 0, &query_data_str, &query_data_len TSRMLS_CC)) {
+                               RETURN_FALSE;
+                       }
+
+                       zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), query_data_str, query_data_len TSRMLS_CC);
+                       efree(query_data_str);
+               } else {
+                       zval *data = php_http_zsep(IS_STRING, qdata);
+
+                       zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), Z_STRVAL_P(data), Z_STRLEN_P(data) TSRMLS_CC);
+                       zval_ptr_dtor(&data);
+               }
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getQueryData)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "queryData");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, addQueryData)
+{
+       zval *qdata;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/", &qdata)) {
+               char *query_data_str = NULL;
+               size_t query_data_len = 0;
+               zval *old_qdata = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), 0 TSRMLS_CC);
+
+               if (SUCCESS != php_http_url_encode_hash(HASH_OF(qdata), 1, Z_STRVAL_P(old_qdata), Z_STRLEN_P(old_qdata), &query_data_str, &query_data_len TSRMLS_CC)) {
+                       RETURN_FALSE;
+               }
+
+               zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), query_data_str, query_data_len TSRMLS_CC);
+               efree(query_data_str);
+
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+
+}
+
+PHP_METHOD(HttpRequest, setBody)
+{
+       zval *body = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|O!", &body, php_http_message_body_class_entry)) {
+               if (body && Z_TYPE_P(body) != IS_NULL) {
+                       zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody"), body TSRMLS_CC);
+               } else {
+                       zend_update_property_null(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody") TSRMLS_CC);
+               }
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, addBody)
+{
+       zval *new_body;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &new_body, php_http_message_body_class_entry)) {
+               zval *old_body = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody"), 0 TSRMLS_CC);
+
+               if (Z_TYPE_P(old_body) == IS_OBJECT) {
+                       php_http_message_body_object_t *old_obj = zend_object_store_get_object(old_body TSRMLS_CC);
+                       php_http_message_body_object_t *new_obj = zend_object_store_get_object(new_body TSRMLS_CC);
+
+                       php_http_message_body_to_callback(old_obj->body, (php_http_pass_callback_t) php_http_message_body_append, new_obj->body, 0, 0);
+               } else {
+                       zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody"), new_body TSRMLS_CC);
+               }
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getBody)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "requestBody");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getResponseHeader)
+{
+       zval *header;
+       char *header_name = NULL;
+       int header_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &header_name, &header_len)) {
+               zval *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), 0 TSRMLS_CC);
+
+               if (Z_TYPE_P(message) == IS_OBJECT) {
+                       php_http_message_object_t *msg = zend_object_store_get_object(message TSRMLS_CC);
+
+                       if (header_len) {
+                               if ((header = php_http_message_header(msg->message, header_name, header_len + 1, 0))) {
+                                       RETURN_ZVAL(header, 1, 1);
+                               }
+                       } else {
+                               array_init(return_value);
+                               zend_hash_copy(Z_ARRVAL_P(return_value), &msg->message->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *));
+                               return;
+                       }
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getResponseCookies)
+{
+       long flags = 0;
+       zval *allowed_extras_array = NULL;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|la!", &flags, &allowed_extras_array)) {
+               int i = 0;
+               php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+               char **allowed_extras = NULL;
+               zval **header = NULL, **entry = NULL, *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), 0 TSRMLS_CC);
+               HashPosition pos, pos1, pos2;
+
+               if (Z_TYPE_P(message) == IS_OBJECT) {
+                       php_http_message_object_t *msg = zend_object_store_get_object(message TSRMLS_CC);
+
+                       array_init(return_value);
+
+                       if (allowed_extras_array) {
+                               allowed_extras = ecalloc(zend_hash_num_elements(Z_ARRVAL_P(allowed_extras_array)) + 1, sizeof(char *));
+                               FOREACH_VAL(pos, allowed_extras_array, entry) {
+                                       zval *data = php_http_zsep(IS_STRING, *entry);
+                                       allowed_extras[i++] = estrndup(Z_STRVAL_P(data), Z_STRLEN_P(data));
+                                       zval_ptr_dtor(&data);
+                               }
+                       }
+
+                       FOREACH_HASH_KEYVAL(pos1, &msg->message->hdrs, key, header) {
+                               if (key.type == HASH_KEY_IS_STRING && !strcasecmp(key.str, "Set-Cookie")) {
+                                       php_http_cookie_list_t *list;
+
+                                       if (Z_TYPE_PP(header) == IS_ARRAY) {
+                                               zval **single_header;
+
+                                               FOREACH_VAL(pos2, *header, single_header) {
+                                                       zval *data = php_http_zsep(IS_STRING, *single_header);
+
+                                                       if ((list = php_http_cookie_list_parse(NULL, Z_STRVAL_P(data), flags, allowed_extras TSRMLS_CC))) {
+                                                               zval *cookie;
+
+                                                               MAKE_STD_ZVAL(cookie);
+                                                               ZVAL_OBJVAL(cookie, php_http_cookie_object_new_ex(php_http_cookie_class_entry, list, NULL TSRMLS_CC), 0);
+                                                               add_next_index_zval(return_value, cookie);
+                                                       }
+                                                       zval_ptr_dtor(&data);
+                                               }
+                                       } else {
+                                               zval *data = php_http_zsep(IS_STRING, *header);
+                                               if ((list = php_http_cookie_list_parse(NULL, Z_STRVAL_P(data), flags, allowed_extras TSRMLS_CC))) {
+                                                       zval *cookie;
+
+                                                       MAKE_STD_ZVAL(cookie);
+                                                       ZVAL_OBJVAL(cookie, php_http_cookie_object_new_ex(php_http_cookie_class_entry, list, NULL TSRMLS_CC), 0);
+                                                       add_next_index_zval(return_value, cookie);
+                                               }
+                                               zval_ptr_dtor(&data);
+                                       }
+                               }
+                       }
+
+                       if (allowed_extras) {
+                               for (i = 0; allowed_extras[i]; ++i) {
+                                       efree(allowed_extras[i]);
+                               }
+                               efree(allowed_extras);
+                       }
+
+                       return;
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getResponseBody)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               zval *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), 0 TSRMLS_CC);
+               zval *body = zend_read_property(php_http_message_class_entry, message, ZEND_STRL("body"), 0 TSRMLS_CC);
+
+               RETURN_ZVAL(body, 1, 0);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getResponseCode)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "responseCode");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getResponseStatus)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "responseStatus");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getResponseInfo)
+{
+       char *info_name = NULL;
+       int info_len = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &info_name, &info_len)) {
+               zval **infop, *info = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseInfo"), 0 TSRMLS_CC);
+
+               if (Z_TYPE_P(info) != IS_ARRAY) {
+                       RETURN_FALSE;
+               }
+
+               if (info_len && info_name) {
+                       if (SUCCESS == zend_hash_find(Z_ARRVAL_P(info), php_http_pretty_key(info_name, info_len, 0, 0), info_len + 1, (void *) &infop)) {
+                               RETURN_ZVAL(*infop, 1, 0);
+                       } else {
+                               php_http_error(HE_NOTICE, PHP_HTTP_E_INVALID_PARAM, "Could not find response info named %s", info_name);
+                               RETURN_FALSE;
+                       }
+               } else {
+                       RETURN_ZVAL(info, 1, 0);
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getResponseMessage)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       zval *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), 0 TSRMLS_CC);
+
+                       if (Z_TYPE_P(message) == IS_OBJECT) {
+                               RETVAL_OBJECT(message, 1);
+                       } else {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "HttpRequest does not contain a response message");
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequest, getRequestMessage)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       zval *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestMessage"), 0 TSRMLS_CC);
+
+                       if (Z_TYPE_P(message) == IS_OBJECT) {
+                               RETVAL_OBJECT(message, 1);
+                       } else {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "HttpRequest does not contain a request message");
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequest, getHistory)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       zval *hist = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("history"), 0 TSRMLS_CC);
+
+                       if (Z_TYPE_P(hist) == IS_OBJECT) {
+                               RETVAL_OBJECT(hist, 1);
+                       } else {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "The history is empty");
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequest, clearHistory)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               zend_update_property_null(php_http_request_class_entry, getThis(), ZEND_STRL("history") TSRMLS_CC);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, getMessageClass)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               RETURN_PROP(php_http_request_class_entry, "messageClass");
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequest, setMessageClass)
+{
+       char *cn;
+       int cl;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &cn, &cl)) {
+               zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("messageClass"), cn, cl TSRMLS_CC);
+       }
+}
+
+PHP_METHOD(HttpRequest, send)
+{
+       RETVAL_FALSE;
+
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                       if (obj->pool) {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "Cannot perform HttpRequest::send() while attached to an HttpRequestPool");
+                       } else if (SUCCESS == php_http_request_object_requesthandler(obj, getThis() TSRMLS_CC)) {
+                               php_http_request_exec(obj->request);
+                               if (SUCCESS == php_http_request_object_responsehandler(obj, getThis() TSRMLS_CC)) {
+                                       RETVAL_PROP(php_http_request_class_entry, "responseMessage");
+                               } else {
+                                       php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Failed to handle response");
+                               }
+                       } else {
+                               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Failed to handle request");
+                       }
+               }
+       } end_error_handling();
+}
+
+PHP_MINIT_FUNCTION(http_request)
+{
+#ifdef PHP_HTTP_NEED_OPENSSL_TSL
+       /* mod_ssl, libpq or ext/curl might already have set thread lock callbacks */
+       if (!CRYPTO_get_id_callback()) {
+               int i, c = CRYPTO_num_locks();
+
+               php_http_openssl_tsl = malloc(c * sizeof(MUTEX_T));
+
+               for (i = 0; i < c; ++i) {
+                       php_http_openssl_tsl[i] = tsrm_mutex_alloc();
+               }
+
+               CRYPTO_set_id_callback(php_http_openssl_thread_id);
+               CRYPTO_set_locking_callback(php_http_openssl_thread_lock);
+       }
+#endif
+#ifdef PHP_HTTP_NEED_GNUTLS_TSL
+       gcry_control(GCRYCTL_SET_THREAD_CBS, &php_http_gnutls_tsl);
+#endif
+
+       if (CURLE_OK != curl_global_init(CURL_GLOBAL_ALL)) {
+               return FAILURE;
+       }
+
+       if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request"), safe_curl_init, safe_curl_dtor, safe_curl_copy)) {
+               return FAILURE;
+       }
+
+       PHP_HTTP_REGISTER_CLASS(http, Request, http_request, php_http_object_class_entry, 0);
+       php_http_request_class_entry->create_object = php_http_request_object_new;
+       memcpy(&php_http_request_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_request_object_handlers.clone_obj = php_http_request_object_clone;
+
+       zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("options"), ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("responseInfo"), ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("responseMessage"), ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_long(php_http_request_class_entry, ZEND_STRL("responseCode"), 0, ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("responseStatus"), "", ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("requestMessage"), ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_long(php_http_request_class_entry, ZEND_STRL("method"), PHP_HTTP_GET, ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("url"), "", ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("contentType"), "", ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("requestBody"), "", ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("queryData"), "", ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("history"), ZEND_ACC_PRIVATE TSRMLS_CC);
+       zend_declare_property_bool(php_http_request_class_entry, ZEND_STRL("recordHistory"), 0, ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("messageClass"), "", ZEND_ACC_PRIVATE TSRMLS_CC);
+
+       /*
+       * HTTP Protocol Version Constants
+       */
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("VERSION_1_0"), CURL_HTTP_VERSION_1_0 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("VERSION_1_1"), CURL_HTTP_VERSION_1_1 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("VERSION_NONE"), CURL_HTTP_VERSION_NONE TSRMLS_CC); /* to be removed */
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("VERSION_ANY"), CURL_HTTP_VERSION_NONE TSRMLS_CC);
+
+       /*
+       * SSL Version Constants
+       */
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("SSL_VERSION_TLSv1"), CURL_SSLVERSION_TLSv1 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("SSL_VERSION_SSLv2"), CURL_SSLVERSION_SSLv2 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("SSL_VERSION_SSLv3"), CURL_SSLVERSION_SSLv3 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("SSL_VERSION_ANY"), CURL_SSLVERSION_DEFAULT TSRMLS_CC);
+
+       /*
+       * DNS IPvX resolving
+       */
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("IPRESOLVE_V4"), CURL_IPRESOLVE_V4 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("IPRESOLVE_V6"), CURL_IPRESOLVE_V6 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("IPRESOLVE_ANY"), CURL_IPRESOLVE_WHATEVER TSRMLS_CC);
+
+       /*
+       * Auth Constants
+       */
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_BASIC"), CURLAUTH_BASIC TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_DIGEST"), CURLAUTH_DIGEST TSRMLS_CC);
+#if PHP_HTTP_CURL_VERSION(7,19,3)
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_DIGEST_IE"), CURLAUTH_DIGEST_IE TSRMLS_CC);
+#endif
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_NTLM"), CURLAUTH_NTLM TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_GSSNEG"), CURLAUTH_GSSNEGOTIATE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_ANY"), CURLAUTH_ANY TSRMLS_CC);
+
+       /*
+       * Proxy Type Constants
+       */
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_SOCKS4"), CURLPROXY_SOCKS4 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_SOCKS4A"), CURLPROXY_SOCKS5 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_SOCKS5_HOSTNAME"), CURLPROXY_SOCKS5 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_SOCKS5"), CURLPROXY_SOCKS5 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_HTTP"), CURLPROXY_HTTP TSRMLS_CC);
+#      if PHP_HTTP_CURL_VERSION(7,19,4)
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_HTTP_1_0"), CURLPROXY_HTTP_1_0 TSRMLS_CC);
+#      endif
+
+       /*
+       * Post Redirection Constants
+       */
+#if PHP_HTTP_CURL_VERSION(7,19,1)
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("POSTREDIR_301"), CURL_REDIR_POST_301 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("POSTREDIR_302"), CURL_REDIR_POST_302 TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("POSTREDIR_ALL"), CURL_REDIR_POST_ALL TSRMLS_CC);
+#endif
+
+       return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(http_request)
+{
+       curl_global_cleanup();
+#ifdef PHP_HTTP_NEED_OPENSSL_TSL
+       if (php_http_openssl_tsl) {
+               int i, c = CRYPTO_num_locks();
+
+               CRYPTO_set_id_callback(NULL);
+               CRYPTO_set_locking_callback(NULL);
+
+               for (i = 0; i < c; ++i) {
+                       tsrm_mutex_free(php_http_openssl_tsl[i]);
+               }
+
+               free(php_http_openssl_tsl);
+               php_http_openssl_tsl = NULL;
+       }
+#endif
+       return SUCCESS;
+}
+
+/*
+ * 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_request.h b/php_http_request.h
new file mode 100644 (file)
index 0000000..9e314ca
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_request_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_REQUEST_H
+#define PHP_HTTP_REQUEST_H
+
+#include "php_http_request_method.h"
+#include "php_http_request_pool.h"
+
+extern PHP_MINIT_FUNCTION(http_request);
+extern PHP_MSHUTDOWN_FUNCTION(http_request);
+
+typedef struct php_http_request_progress_state_counter {
+       double now;
+       double total;
+} php_http_request_progress_state_counter_t;
+
+typedef struct php_http_request_progress_state {
+       php_http_request_progress_state_counter_t ul;
+       php_http_request_progress_state_counter_t dl;
+} php_http_request_progress_state_t;
+
+typedef struct php_http_request {
+       CURL *ch;
+       char *url;
+       php_http_request_method_t meth;
+       php_http_message_body_t *body;
+       struct {
+               php_http_message_parser_t *ctx;
+               php_http_message_t *msg;
+               php_http_buffer *buf;
+       } parser;
+       
+       struct {
+               php_http_buffer cookies;
+               HashTable options;
+               struct curl_slist *headers;
+       } _cache;
+       
+       struct {
+               uint count;
+               double delay;
+       } _retry;
+
+       struct {
+               struct {
+                       struct {
+                               double now;
+                               double total;
+                       } ul;
+                       struct {
+                               double now;
+                               double total;
+                       } dl;
+               } state;
+               zval *callback;
+               unsigned in_cb:1;
+       } _progress;
+
+#ifdef ZTS
+       void ***ts;
+#endif
+       
+} php_http_request_t;
+
+/* CURLOPT_PRIVATE storage living as long as a CURL handle */
+typedef struct php_http_request_storage {
+       char *url;
+       char *cookiestore;
+       char errorbuffer[CURL_ERROR_SIZE];
+} php_http_request_storage_t;
+
+
+static inline php_http_request_storage_t *php_http_request_storage_get(CURL *ch)
+{
+       php_http_request_storage_t *st = NULL;
+       curl_easy_getinfo(ch, CURLINFO_PRIVATE, &st);
+       return st;
+}
+
+PHP_HTTP_API CURL *php_http_curl_init(CURL *ch, php_http_request_t *request TSRMLS_DC);
+PHP_HTTP_API void php_http_curl_free(CURL **ch TSRMLS_DC);
+PHP_HTTP_API CURL *php_http_curl_copy(CURL *ch TSRMLS_DC);
+
+#define PHP_HTTP_CHECK_CURL_INIT(ch, init, action) \
+       if ((!(ch)) && (!((ch) = init))) { \
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not initialize curl"); \
+               action; \
+       }
+
+
+PHP_HTTP_API php_http_request_t *php_http_request_init(php_http_request_t *request, CURL *ch, php_http_request_method_t meth, const char *url TSRMLS_DC);
+PHP_HTTP_API void php_http_request_dtor(php_http_request_t *request);
+PHP_HTTP_API void php_http_request_free(php_http_request_t **request);
+PHP_HTTP_API void php_http_request_reset(php_http_request_t *r);
+PHP_HTTP_API STATUS php_http_request_enable_cookies(php_http_request_t *request);
+PHP_HTTP_API STATUS php_http_request_reset_cookies(php_http_request_t *request, int session_only);
+PHP_HTTP_API STATUS php_http_request_flush_cookies(php_http_request_t *request);
+PHP_HTTP_API void php_http_request_defaults(php_http_request_t *request);
+PHP_HTTP_API STATUS php_http_request_prepare(php_http_request_t *request, HashTable *options);
+PHP_HTTP_API void php_http_request_exec(php_http_request_t *request);
+PHP_HTTP_API void php_http_request_info(php_http_request_t *request, HashTable *info);
+PHP_HTTP_API void php_http_request_set_progress_callback(php_http_request_t *request, zval *cb);
+
+
+typedef struct php_http_request_object {
+       zend_object zo;
+       php_http_request_t *request;
+       php_http_request_pool_t *pool;
+       php_http_request_datashare_t *share;
+} php_http_request_object_t;
+
+extern zend_class_entry *php_http_request_class_entry;
+extern zend_function_entry php_http_request_method_entry[];
+
+extern zend_object_value php_http_request_object_new(zend_class_entry *ce TSRMLS_DC);
+extern zend_object_value php_http_request_object_new_ex(zend_class_entry *ce, CURL *ch, php_http_request_object_t **ptr TSRMLS_DC);
+extern zend_object_value php_http_request_object_clone(zval *zobject TSRMLS_DC);
+extern void php_http_request_object_free(void *object TSRMLS_DC);
+
+extern STATUS php_http_request_object_requesthandler(php_http_request_object_t *obj, zval *this_ptr TSRMLS_DC);
+extern STATUS php_http_request_object_responsehandler(php_http_request_object_t *obj, zval *this_ptr TSRMLS_DC);
+
+PHP_METHOD(HttpRequest, __construct);
+PHP_METHOD(HttpRequest, setOptions);
+PHP_METHOD(HttpRequest, getOptions);
+PHP_METHOD(HttpRequest, addSslOptions);
+PHP_METHOD(HttpRequest, setSslOptions);
+PHP_METHOD(HttpRequest, getSslOptions);
+PHP_METHOD(HttpRequest, addHeaders);
+PHP_METHOD(HttpRequest, getHeaders);
+PHP_METHOD(HttpRequest, setHeaders);
+PHP_METHOD(HttpRequest, addCookies);
+PHP_METHOD(HttpRequest, getCookies);
+PHP_METHOD(HttpRequest, setCookies);
+PHP_METHOD(HttpRequest, enableCookies);
+PHP_METHOD(HttpRequest, resetCookies);
+PHP_METHOD(HttpRequest, flushCookies);
+PHP_METHOD(HttpRequest, setMethod);
+PHP_METHOD(HttpRequest, getMethod);
+PHP_METHOD(HttpRequest, setUrl);
+PHP_METHOD(HttpRequest, getUrl);
+PHP_METHOD(HttpRequest, setContentType);
+PHP_METHOD(HttpRequest, getContentType);
+PHP_METHOD(HttpRequest, setQueryData);
+PHP_METHOD(HttpRequest, getQueryData);
+PHP_METHOD(HttpRequest, addQueryData);
+PHP_METHOD(HttpRequest, getBody);
+PHP_METHOD(HttpRequest, setBody);
+PHP_METHOD(HttpRequest, addBody);
+PHP_METHOD(HttpRequest, send);
+PHP_METHOD(HttpRequest, getResponseData);
+PHP_METHOD(HttpRequest, getResponseHeader);
+PHP_METHOD(HttpRequest, getResponseCookies);
+PHP_METHOD(HttpRequest, getResponseCode);
+PHP_METHOD(HttpRequest, getResponseStatus);
+PHP_METHOD(HttpRequest, getResponseBody);
+PHP_METHOD(HttpRequest, getResponseInfo);
+PHP_METHOD(HttpRequest, getResponseMessage);
+PHP_METHOD(HttpRequest, getRawResponseMessage);
+PHP_METHOD(HttpRequest, getRequestMessage);
+PHP_METHOD(HttpRequest, getRawRequestMessage);
+PHP_METHOD(HttpRequest, getHistory);
+PHP_METHOD(HttpRequest, clearHistory);
+PHP_METHOD(HttpRequest, getMessageClass);
+PHP_METHOD(HttpRequest, setMessageClass);
+
+#endif
+
+/*
+ * 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_request_datashare.c b/php_http_request_datashare.c
new file mode 100644 (file)
index 0000000..c1df14b
--- /dev/null
@@ -0,0 +1,468 @@
+
+#include "php_http.h"
+
+static HashTable php_http_request_datashare_options;
+static php_http_request_datashare_t php_http_request_datashare_global;
+static int php_http_request_datashare_compare_handles(void *h1, void *h2);
+static void php_http_request_datashare_destroy_handles(void *el);
+
+#ifdef ZTS
+static void *php_http_request_datashare_locks_init(void);
+static void php_http_request_datashare_locks_dtor(void *l);
+static void php_http_request_datashare_lock_func(CURL *handle, curl_lock_data data, curl_lock_access locktype, void *userptr);
+static void php_http_request_datashare_unlock_func(CURL *handle, curl_lock_data data, void *userptr);
+#endif
+
+php_http_request_datashare_t *php_http_request_datashare_global_get(void)
+{
+       return &php_http_request_datashare_global;
+}
+
+PHP_HTTP_API php_http_request_datashare_t *php_http_request_datashare_init(php_http_request_datashare_t *share, zend_bool persistent TSRMLS_DC)
+{
+       zend_bool free_share;
+
+       if ((free_share = !share)) {
+               share = pemalloc(sizeof(php_http_request_datashare_t), persistent);
+       }
+       memset(share, 0, sizeof(php_http_request_datashare_t));
+
+       if (SUCCESS != php_http_persistent_handle_acquire(ZEND_STRL("http_request_datashare"), &share->ch TSRMLS_CC)) {
+               if (free_share) {
+                       pefree(share, persistent);
+               }
+               return NULL;
+       }
+
+       if (!(share->persistent = persistent)) {
+               share->handle.list = emalloc(sizeof(zend_llist));
+               zend_llist_init(share->handle.list, sizeof(zval *), ZVAL_PTR_DTOR, 0);
+#ifdef ZTS
+       } else {
+               if (SUCCESS == php_http_persistent_handle_acquire(ZEND_STRL("http_request_datashare_lock"), (void *) &share->handle.locks)) {
+                       curl_share_setopt(share->ch, CURLSHOPT_LOCKFUNC, php_http_request_datashare_lock_func);
+                       curl_share_setopt(share->ch, CURLSHOPT_UNLOCKFUNC, php_http_request_datashare_unlock_func);
+                       curl_share_setopt(share->ch, CURLSHOPT_USERDATA, share->handle.locks);
+               }
+#endif
+       }
+
+       TSRMLS_SET_CTX(share->ts);
+
+       return share;
+}
+
+PHP_HTTP_API STATUS php_http_request_datashare_attach(php_http_request_datashare_t *share, zval *request)
+{
+       CURLcode rc;
+       TSRMLS_FETCH_FROM_CTX(share->ts);
+       php_http_request_object_t *obj = zend_object_store_get_object(request TSRMLS_CC);
+
+       if (obj->share) {
+               if (obj->share == share)  {
+                       return SUCCESS;
+               } else if (SUCCESS != php_http_request_datashare_detach(obj->share, request)) {
+                       return FAILURE;
+               }
+       }
+
+       PHP_HTTP_CHECK_CURL_INIT(obj->request->ch, php_http_curl_init(obj->request->ch, obj->request TSRMLS_CC), return FAILURE);
+       if (CURLE_OK != (rc = curl_easy_setopt(obj->request->ch, CURLOPT_SHARE, share->ch))) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not attach HttpRequest object(#%d) to the HttpRequestDataShare: %s", Z_OBJ_HANDLE_P(request), curl_easy_strerror(rc));
+               return FAILURE;
+       }
+
+       obj->share = share;
+       Z_ADDREF_P(request);
+       zend_llist_add_element(PHP_HTTP_RSHARE_HANDLES(share), (void *) &request);
+
+       return SUCCESS;
+}
+
+PHP_HTTP_API STATUS php_http_request_datashare_detach(php_http_request_datashare_t *share, zval *request)
+{
+       CURLcode rc;
+       TSRMLS_FETCH_FROM_CTX(share->ts);
+       php_http_request_object_t *obj = zend_object_store_get_object(request TSRMLS_CC);
+
+       if (!obj->share) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "HttpRequest object(#%d) is not attached to any HttpRequestDataShare", Z_OBJ_HANDLE_P(request));
+       } else if (obj->share != share) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "HttpRequest object(#%d) is not attached to this HttpRequestDataShare", Z_OBJ_HANDLE_P(request));
+       } else if (CURLE_OK != (rc = curl_easy_setopt(obj->request->ch, CURLOPT_SHARE, NULL))) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not detach HttpRequest object(#%d) from the HttpRequestDataShare: %s", Z_OBJ_HANDLE_P(request), curl_share_strerror(rc));
+       } else {
+               obj->share = NULL;
+               zend_llist_del_element(PHP_HTTP_RSHARE_HANDLES(share), request, php_http_request_datashare_compare_handles);
+               return SUCCESS;
+       }
+       return FAILURE;
+}
+
+PHP_HTTP_API void php_http_request_datashare_detach_all(php_http_request_datashare_t *share)
+{
+       zval **r;
+
+       while ((r = zend_llist_get_first(PHP_HTTP_RSHARE_HANDLES(share)))) {
+               php_http_request_datashare_detach(share, *r);
+       }
+}
+
+PHP_HTTP_API void php_http_request_datashare_dtor(php_http_request_datashare_t *share)
+{
+       TSRMLS_FETCH_FROM_CTX(share->ts);
+
+       if (!share->persistent) {
+               zend_llist_destroy(share->handle.list);
+               efree(share->handle.list);
+       }
+       php_http_persistent_handle_release(ZEND_STRL("http_request_datashare"), &share->ch TSRMLS_CC);
+#ifdef ZTS
+       if (share->persistent) {
+               php_http_persistent_handle_release(ZEND_STRL("http_request_datashare_lock"), (void *) &share->handle.locks TSRMLS_CC);
+       }
+#endif
+}
+
+PHP_HTTP_API void php_http_request_datashare_free(php_http_request_datashare_t **share)
+{
+       php_http_request_datashare_dtor(*share);
+       pefree(*share, (*share)->persistent);
+       *share = NULL;
+}
+
+PHP_HTTP_API STATUS php_http_request_datashare_set(php_http_request_datashare_t *share, const char *option, size_t option_len, zend_bool enable)
+{
+       curl_lock_data *opt;
+       CURLSHcode rc;
+       TSRMLS_FETCH_FROM_CTX(share->ts);
+
+       if (SUCCESS == zend_hash_find(&php_http_request_datashare_options, (char *) option, option_len + 1, (void *) &opt)) {
+               if (CURLSHE_OK == (rc = curl_share_setopt(share->ch, enable ? CURLSHOPT_SHARE : CURLSHOPT_UNSHARE, *opt))) {
+                       return SUCCESS;
+               }
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not %s sharing of %s data: %s",  enable ? "enable" : "disable", option, curl_share_strerror(rc));
+       }
+       return FAILURE;
+}
+
+static int php_http_request_datashare_compare_handles(void *h1, void *h2)
+{
+       return (Z_OBJ_HANDLE_PP((zval **) h1) == Z_OBJ_HANDLE_P((zval *) h2));
+}
+
+static void php_http_request_datashare_destroy_handles(void *el)
+{
+       zval **r = (zval **) el;
+       TSRMLS_FETCH();
+
+       { /* gcc 2.95 needs these braces */
+               php_http_request_object_t *obj = zend_object_store_get_object(*r TSRMLS_CC);
+
+               curl_easy_setopt(obj->request->ch, CURLOPT_SHARE, NULL);
+               zval_ptr_dtor(r);
+       }
+}
+
+#ifdef ZTS
+static void *php_http_request_datashare_locks_init(void)
+{
+       int i;
+       php_http_request_datashare_lock_t *locks = pecalloc(CURL_LOCK_DATA_LAST, sizeof(php_http_request_datashare_lock_t), 1);
+
+       if (locks) {
+               for (i = 0; i < CURL_LOCK_DATA_LAST; ++i) {
+                       locks[i].mx = tsrm_mutex_alloc();
+               }
+       }
+
+       return locks;
+}
+
+static void php_http_request_datashare_locks_dtor(void *l)
+{
+       int i;
+       php_http_request_datashare_lock_t *locks = (php_http_request_datashare_lock_t *) l;
+
+       for (i = 0; i < CURL_LOCK_DATA_LAST; ++i) {
+               tsrm_mutex_free(locks[i].mx);
+       }
+       pefree(locks, 1);
+}
+
+static void php_http_request_datashare_lock_func(CURL *handle, curl_lock_data data, curl_lock_access locktype, void *userptr)
+{
+       php_http_request_datashare_lock_t *locks = (php_http_request_datashare_lock_t *) userptr;
+
+       /* TSRM can't distinguish shared/exclusive locks */
+       tsrm_mutex_lock(locks[data].mx);
+       locks[data].ch = handle;
+}
+
+static void php_http_request_datashare_unlock_func(CURL *handle, curl_lock_data data, void *userptr)
+{
+       php_http_request_datashare_lock_t *locks = (php_http_request_datashare_lock_t *) userptr;
+
+       if (locks[data].ch == handle) {
+               tsrm_mutex_unlock(locks[data].mx);
+       }
+}
+#endif
+
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpRequestDataShare, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpRequestDataShare, method, 0)
+#define PHP_HTTP_RSHARE_ME(method, visibility) PHP_ME(HttpRequestDataShare, method, PHP_HTTP_ARGS(HttpRequestDataShare, method), visibility)
+
+PHP_HTTP_EMPTY_ARGS(__destruct);
+PHP_HTTP_EMPTY_ARGS(count);
+
+PHP_HTTP_BEGIN_ARGS(attach, 1)
+       PHP_HTTP_ARG_OBJ(http\\Request, request, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_BEGIN_ARGS(detach, 1)
+       PHP_HTTP_ARG_OBJ(http\\Request, request, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(reset);
+
+PHP_HTTP_EMPTY_ARGS(getGlobalInstance);
+
+
+static zval *php_http_request_datashare_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC);
+static void php_http_request_datashare_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC);
+
+#define THIS_CE php_http_request_datashare_class_entry
+zend_class_entry *php_http_request_datashare_class_entry;
+zend_function_entry php_http_request_datashare_method_entry[] = {
+       PHP_HTTP_RSHARE_ME(__destruct, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
+       PHP_HTTP_RSHARE_ME(count, ZEND_ACC_PUBLIC)
+       PHP_HTTP_RSHARE_ME(attach, ZEND_ACC_PUBLIC)
+       PHP_HTTP_RSHARE_ME(detach, ZEND_ACC_PUBLIC)
+       PHP_HTTP_RSHARE_ME(reset, ZEND_ACC_PUBLIC)
+       PHP_HTTP_RSHARE_ME(getGlobalInstance, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+       EMPTY_FUNCTION_ENTRY
+};
+static zend_object_handlers php_http_request_datashare_object_handlers;
+
+zend_object_value php_http_request_datashare_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       return php_http_request_datashare_object_new_ex(ce, NULL, NULL TSRMLS_CC);
+}
+
+zend_object_value php_http_request_datashare_object_new_ex(zend_class_entry *ce, php_http_request_datashare_t *share, php_http_request_datashare_object_t **ptr TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_request_datashare_object_t *o;
+
+       o = ecalloc(1, sizeof(*o));
+       zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       if (share) {
+               o->share = share;
+       } else {
+               o->share = php_http_request_datashare_init(NULL, 0 TSRMLS_CC);
+       }
+
+       if (ptr) {
+               *ptr = o;
+       }
+
+       ov.handle = zend_objects_store_put(o, NULL, php_http_request_datashare_object_free, NULL TSRMLS_CC);
+       ov.handlers = &php_http_request_datashare_object_handlers;
+
+       return ov;
+}
+
+void php_http_request_datashare_object_free(void *object TSRMLS_DC)
+{
+       php_http_request_datashare_object_t *o = (php_http_request_datashare_object_t *) object;
+
+       if (!o->share->persistent) {
+               php_http_request_datashare_free(&o->share);
+       }
+       zend_object_std_dtor((zend_object *) o);
+       efree(o);
+}
+
+static zval *php_http_request_datashare_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC)
+{
+       if (type == BP_VAR_W && zend_get_property_info(THIS_CE, member, 1 TSRMLS_CC)) {
+               zend_error(E_ERROR, "Cannot access HttpRequestDataShare default properties by reference or array key/index");
+               return NULL;
+       }
+
+       return zend_get_std_object_handlers()->read_property(object, member, type, literal_key TSRMLS_CC);
+}
+
+static void php_http_request_datashare_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC)
+{
+       if (zend_get_property_info(THIS_CE, member, 1 TSRMLS_CC)) {
+               int status;
+               php_http_request_datashare_object_t *obj = zend_object_store_get_object(object TSRMLS_CC);
+
+               status = php_http_request_datashare_set(obj->share, Z_STRVAL_P(member), Z_STRLEN_P(member), (zend_bool) i_zend_is_true(value));
+               if (SUCCESS != status) {
+                       return;
+               }
+       }
+
+       zend_get_std_object_handlers()->write_property(object, member, value, literal_key TSRMLS_CC);
+}
+
+PHP_METHOD(HttpRequestDataShare, __destruct)
+{
+       php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       if (SUCCESS == zend_parse_parameters_none()) {
+               ; /* we always want to clean up */
+       }
+
+       php_http_request_datashare_detach_all(obj->share);
+}
+
+PHP_METHOD(HttpRequestDataShare, count)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_LONG(zend_llist_count(PHP_HTTP_RSHARE_HANDLES(obj->share)));
+       }
+       RETURN_FALSE;
+}
+
+
+PHP_METHOD(HttpRequestDataShare, attach)
+{
+       zval *request;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) {
+               php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_SUCCESS(php_http_request_datashare_attach(obj->share, request));
+       }
+       RETURN_FALSE;
+
+}
+
+PHP_METHOD(HttpRequestDataShare, detach)
+{
+       zval *request;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) {
+               php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_SUCCESS(php_http_request_datashare_detach(obj->share, request));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestDataShare, reset)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               php_http_request_datashare_detach_all(obj->share);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestDataShare, getGlobalInstance)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       zval *instance = *zend_std_get_static_property(THIS_CE, ZEND_STRL("instance"), 0, NULL TSRMLS_CC);
+
+                       if (Z_TYPE_P(instance) != IS_OBJECT) {
+                               MAKE_STD_ZVAL(instance);
+                               ZVAL_OBJVAL(instance, php_http_request_datashare_object_new_ex(THIS_CE, php_http_request_datashare_global_get(), NULL TSRMLS_CC), 1);
+                               zend_update_static_property(THIS_CE, ZEND_STRL("instance"), instance TSRMLS_CC);
+
+                               if (PHP_HTTP_G->request_datashare.cookie) {
+                                       zend_update_property_bool(THIS_CE, instance, ZEND_STRL("cookie"), PHP_HTTP_G->request_datashare.cookie TSRMLS_CC);
+                               }
+                               if (PHP_HTTP_G->request_datashare.dns) {
+                                       zend_update_property_bool(THIS_CE, instance, ZEND_STRL("dns"), PHP_HTTP_G->request_datashare.dns TSRMLS_CC);
+                               }
+                               if (PHP_HTTP_G->request_datashare.ssl) {
+                                       zend_update_property_bool(THIS_CE, instance, ZEND_STRL("ssl"), PHP_HTTP_G->request_datashare.ssl TSRMLS_CC);
+                               }
+                               if (PHP_HTTP_G->request_datashare.connect) {
+                                       zend_update_property_bool(THIS_CE, instance, ZEND_STRL("connect"), PHP_HTTP_G->request_datashare.connect TSRMLS_CC);
+                               }
+                       }
+
+                       RETVAL_ZVAL(instance, 0, 0);
+               }
+       }end_error_handling();
+}
+
+PHP_MINIT_FUNCTION(http_request_datashare)
+{
+       curl_lock_data val;
+
+       if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request_datashare"), curl_share_init, (php_http_persistent_handle_dtor_t) curl_share_cleanup, NULL)) {
+               return FAILURE;
+       }
+#ifdef ZTS
+       if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request_datashare_lock"), php_http_request_datashare_locks_init, php_http_request_datashare_locks_dtor, NULL)) {
+               return FAILURE;
+       }
+#endif
+
+       if (!php_http_request_datashare_init(&php_http_request_datashare_global, 1 TSRMLS_CC)) {
+               return FAILURE;
+       }
+
+       zend_hash_init(&php_http_request_datashare_options, 4, NULL, NULL, 1);
+#define ADD_DATASHARE_OPT(name, opt) \
+       val = opt; \
+       zend_hash_add(&php_http_request_datashare_options, name, sizeof(name), &val, sizeof(curl_lock_data), NULL)
+       ADD_DATASHARE_OPT("cookie", CURL_LOCK_DATA_COOKIE);
+       ADD_DATASHARE_OPT("dns", CURL_LOCK_DATA_DNS);
+       ADD_DATASHARE_OPT("ssl", CURL_LOCK_DATA_SSL_SESSION);
+       ADD_DATASHARE_OPT("connect", CURL_LOCK_DATA_CONNECT);
+
+       PHP_HTTP_REGISTER_CLASS(http\\request, DataShare, http_request_datashare, php_http_object_class_entry, 0);
+       php_http_request_datashare_class_entry->create_object = php_http_request_datashare_object_new;
+       memcpy(&php_http_request_datashare_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_request_datashare_object_handlers.clone_obj = NULL;
+       php_http_request_datashare_object_handlers.read_property = php_http_request_datashare_object_read_prop;
+       php_http_request_datashare_object_handlers.write_property = php_http_request_datashare_object_write_prop;
+       php_http_request_datashare_object_handlers.get_property_ptr_ptr = NULL;
+
+       zend_class_implements(php_http_request_datashare_class_entry TSRMLS_CC, 1, spl_ce_Countable);
+
+       zend_declare_property_null(THIS_CE, ZEND_STRL("instance"), (ZEND_ACC_STATIC|ZEND_ACC_PRIVATE) TSRMLS_CC);
+       zend_declare_property_bool(THIS_CE, ZEND_STRL("cookie"), 0, ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_bool(THIS_CE, ZEND_STRL("dns"), 0, ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_bool(THIS_CE, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_bool(THIS_CE, ZEND_STRL("connect"), 0, ZEND_ACC_PUBLIC TSRMLS_CC);
+
+       return SUCCESS;
+}
+
+PHP_MSHUTDOWN_FUNCTION(http_request_datashare)
+{
+       php_http_request_datashare_dtor(&php_http_request_datashare_global);
+       zend_hash_destroy(&php_http_request_datashare_options);
+
+       return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(http_request_datashare)
+{
+       zend_llist_init(&PHP_HTTP_G->request_datashare.handles, sizeof(zval *), php_http_request_datashare_destroy_handles, 0);
+
+       return SUCCESS;
+}
+
+PHP_RSHUTDOWN_FUNCTION(http_request_datashare)
+{
+       zend_llist_destroy(&PHP_HTTP_G->request_datashare.handles);
+
+       return SUCCESS;
+}
+
diff --git a/php_http_request_datashare.h b/php_http_request_datashare.h
new file mode 100644 (file)
index 0000000..fdc4950
--- /dev/null
@@ -0,0 +1,71 @@
+#ifndef PHP_HTTP_REQUEST_DATASHARE_H
+#define PHP_HTTP_REQUEST_DATASHARE_H
+
+#ifdef ZTS
+typedef struct php_http_request_datashare_lock {
+       CURL *ch;
+       MUTEX_T mx;
+} php_http_request_datashare_lock_t;
+#endif
+
+typedef union php_http_request_datashare_handle {
+       zend_llist *list;
+#ifdef ZTS
+       php_http_request_datashare_lock_t *locks;
+#endif
+} php_http_request_datashare_handle_t;
+
+typedef struct php_http_request_datashare_t {
+       CURLSH *ch;
+       php_http_request_datashare_handle_t handle;
+       unsigned persistent:1;
+#ifdef ZTS
+       void ***ts;
+#endif
+} php_http_request_datashare_t;
+
+struct php_http_request_datashare_globals {
+       zend_llist handles;
+       zend_bool cookie;
+       zend_bool dns;
+       zend_bool ssl;
+       zend_bool connect;
+};
+
+#define PHP_HTTP_RSHARE_HANDLES(s) ((s)->persistent ? &PHP_HTTP_G->request_datashare.handles : (s)->handle.list)
+
+extern php_http_request_datashare_t *php_http_request_datashare_global_get(void);
+
+extern PHP_MINIT_FUNCTION(http_request_datashare);
+extern PHP_MSHUTDOWN_FUNCTION(http_request_datashare);
+extern PHP_RINIT_FUNCTION(http_request_datashare);
+extern PHP_RSHUTDOWN_FUNCTION(http_request_datashare);
+
+PHP_HTTP_API php_http_request_datashare_t *php_http_request_datashare_init(php_http_request_datashare_t *share, zend_bool persistent TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_request_datashare_attach(php_http_request_datashare_t *share, zval *request);
+PHP_HTTP_API STATUS php_http_request_datashare_detach(php_http_request_datashare_t *share, zval *request);
+PHP_HTTP_API void php_http_request_datashare_detach_all(php_http_request_datashare_t *share);
+PHP_HTTP_API void php_http_request_datashare_dtor(php_http_request_datashare_t *share);
+PHP_HTTP_API void php_http_request_datashare_free(php_http_request_datashare_t **share);
+PHP_HTTP_API STATUS php_http_request_datashare_set(php_http_request_datashare_t *share, const char *option, size_t option_len, zend_bool enable);
+
+typedef struct php_http_request_datashare_object {
+       zend_object zo;
+       php_http_request_datashare_t *share;
+} php_http_request_datashare_object_t;
+
+extern zend_class_entry *php_http_request_datashare_class_entry;
+extern zend_function_entry php_http_request_datashare_method_entry[];
+
+extern zend_object_value php_http_request_datashare_object_new(zend_class_entry *ce TSRMLS_DC);
+extern zend_object_value php_http_request_datashare_object_new_ex(zend_class_entry *ce, php_http_request_datashare_t *share, php_http_request_datashare_object_t **ptr TSRMLS_DC);
+extern void php_http_request_datashare_object_free(void *object TSRMLS_DC);
+
+PHP_METHOD(HttpRequestDataShare, __destruct);
+PHP_METHOD(HttpRequestDataShare, count);
+PHP_METHOD(HttpRequestDataShare, attach);
+PHP_METHOD(HttpRequestDataShare, detach);
+PHP_METHOD(HttpRequestDataShare, reset);
+PHP_METHOD(HttpRequestDataShare, getGlobalInstance);
+
+#endif /* PHP_HTTP_REQUEST_DATASHARE_H */
diff --git a/php_http_request_info.c b/php_http_request_info.c
new file mode 100644 (file)
index 0000000..494a5dc
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_request_info.c 293136 2010-01-05 08:48:52Z mike $ */
+
+#include "php_http.h"
+
+PHP_HTTP_API void php_http_request_info(php_http_request_t *request, HashTable *info)
+{
+       char *c;
+       long l;
+       double d;
+       struct curl_slist *s, *p;
+       zval *subarray, array;
+       INIT_PZVAL_ARRAY(&array, info);
+       
+       /* BEGIN */
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_EFFECTIVE_URL, &c)) {
+               add_assoc_string_ex(&array, "effective_url", sizeof("effective_url"), c ? c : "", 1);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_RESPONSE_CODE, &l)) {
+               add_assoc_long_ex(&array, "response_code", sizeof("response_code"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_TOTAL_TIME, &d)) {
+               add_assoc_double_ex(&array, "total_time", sizeof("total_time"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_NAMELOOKUP_TIME, &d)) {
+               add_assoc_double_ex(&array, "namelookup_time", sizeof("namelookup_time"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONNECT_TIME, &d)) {
+               add_assoc_double_ex(&array, "connect_time", sizeof("connect_time"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_PRETRANSFER_TIME, &d)) {
+               add_assoc_double_ex(&array, "pretransfer_time", sizeof("pretransfer_time"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SIZE_UPLOAD, &d)) {
+               add_assoc_double_ex(&array, "size_upload", sizeof("size_upload"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SIZE_DOWNLOAD, &d)) {
+               add_assoc_double_ex(&array, "size_download", sizeof("size_download"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SPEED_DOWNLOAD, &d)) {
+               add_assoc_double_ex(&array, "speed_download", sizeof("speed_download"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SPEED_UPLOAD, &d)) {
+               add_assoc_double_ex(&array, "speed_upload", sizeof("speed_upload"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_HEADER_SIZE, &l)) {
+               add_assoc_long_ex(&array, "header_size", sizeof("header_size"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_REQUEST_SIZE, &l)) {
+               add_assoc_long_ex(&array, "request_size", sizeof("request_size"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SSL_VERIFYRESULT, &l)) {
+               add_assoc_long_ex(&array, "ssl_verifyresult", sizeof("ssl_verifyresult"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_FILETIME, &l)) {
+               add_assoc_long_ex(&array, "filetime", sizeof("filetime"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d)) {
+               add_assoc_double_ex(&array, "content_length_download", sizeof("content_length_download"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONTENT_LENGTH_UPLOAD, &d)) {
+               add_assoc_double_ex(&array, "content_length_upload", sizeof("content_length_upload"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_STARTTRANSFER_TIME, &d)) {
+               add_assoc_double_ex(&array, "starttransfer_time", sizeof("starttransfer_time"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONTENT_TYPE, &c)) {
+               add_assoc_string_ex(&array, "content_type", sizeof("content_type"), c ? c : "", 1);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_REDIRECT_TIME, &d)) {
+               add_assoc_double_ex(&array, "redirect_time", sizeof("redirect_time"), d);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_REDIRECT_COUNT, &l)) {
+               add_assoc_long_ex(&array, "redirect_count", sizeof("redirect_count"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_HTTP_CONNECTCODE, &l)) {
+               add_assoc_long_ex(&array, "connect_code", sizeof("connect_code"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_HTTPAUTH_AVAIL, &l)) {
+               add_assoc_long_ex(&array, "httpauth_avail", sizeof("httpauth_avail"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_PROXYAUTH_AVAIL, &l)) {
+               add_assoc_long_ex(&array, "proxyauth_avail", sizeof("proxyauth_avail"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_OS_ERRNO, &l)) {
+               add_assoc_long_ex(&array, "os_errno", sizeof("os_errno"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_NUM_CONNECTS, &l)) {
+               add_assoc_long_ex(&array, "num_connects", sizeof("num_connects"), l);
+       }
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SSL_ENGINES, &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, "ssl_engines", sizeof("ssl_engines"), subarray);
+               curl_slist_free_all(s);
+       }
+#if PHP_HTTP_CURL_VERSION(7,14,1)
+       if (CURLE_OK == curl_easy_getinfo(request->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);
+       }
+#endif
+#if PHP_HTTP_CURL_VERSION(7,18,2)
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_REDIRECT_URL, &c)) {
+               add_assoc_string_ex(&array, "redirect_url", sizeof("redirect_url"), c ? c : "", 1);
+       }
+#endif
+#if PHP_HTTP_CURL_VERSION(7,19,0)
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_PRIMARY_IP, &c)) {
+               add_assoc_string_ex(&array, "primary_ip", sizeof("primary_ip"), c ? c : "", 1);
+       }
+#endif
+#if PHP_HTTP_CURL_VERSION(7,19,0)
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_APPCONNECT_TIME, &d)) {
+               add_assoc_double_ex(&array, "appconnect_time", sizeof("appconnect_time"), d);
+       }
+#endif
+#if PHP_HTTP_CURL_VERSION(7,19,4)
+       if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONDITION_UNMET, &l)) {
+               add_assoc_long_ex(&array, "condition_unmet", sizeof("condition_unmet"), l);
+       }
+#endif
+/* END */
+#if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL)
+       {
+               int i;
+               zval *ci_array;
+               struct curl_certinfo *ci;
+               char *colon, *keyname;
+               
+               if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CERTINFO, &ci)) {
+                       MAKE_STD_ZVAL(ci_array);
+                       array_init(ci_array);
+                       
+                       for (i = 0; i < ci->num_of_certs; ++i) {
+                               s = ci->certinfo[i];
+                               
+                               MAKE_STD_ZVAL(subarray);
+                               array_init(subarray);
+                               for (p = s; p; p = p->next) {
+                                       if (p->data) {
+                                               if ((colon = strchr(p->data, ':'))) {
+                                                       keyname = estrndup(p->data, colon - p->data);
+                                                       add_assoc_string_ex(subarray, keyname, colon - p->data + 1, colon + 1, 1);
+                                                       efree(keyname);
+                                               } else {
+                                                       add_next_index_string(subarray, p->data, 1);
+                                               }
+                                       }
+                               }
+                               add_next_index_zval(ci_array, subarray);
+                       }
+                       add_assoc_zval_ex(&array, "certinfo", sizeof("certinfo"), ci_array);
+               }
+       }
+#endif
+       add_assoc_string_ex(&array, "error", sizeof("error"), php_http_request_storage_get(request->ch)->errorbuffer, 1);
+}
+
+
+/*
+ * 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_request_method.c b/php_http_request_method.c
new file mode 100644 (file)
index 0000000..298b867
--- /dev/null
@@ -0,0 +1,229 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_request_method_api.c 292841 2009-12-31 08:48:57Z mike $ */
+
+#include "php_http.h"
+
+static PHP_HTTP_STRLIST(php_http_request_methods) =
+       PHP_HTTP_STRLIST_ITEM("UNKNOWN")
+       /* HTTP/1.1 */
+       PHP_HTTP_STRLIST_ITEM("GET")
+       PHP_HTTP_STRLIST_ITEM("HEAD")
+       PHP_HTTP_STRLIST_ITEM("POST")
+       PHP_HTTP_STRLIST_ITEM("PUT")
+       PHP_HTTP_STRLIST_ITEM("DELETE")
+       PHP_HTTP_STRLIST_ITEM("OPTIONS")
+       PHP_HTTP_STRLIST_ITEM("TRACE")
+       PHP_HTTP_STRLIST_ITEM("CONNECT")
+       /* WebDAV - RFC 2518 */
+       PHP_HTTP_STRLIST_ITEM("PROPFIND")
+       PHP_HTTP_STRLIST_ITEM("PROPPATCH")
+       PHP_HTTP_STRLIST_ITEM("MKCOL")
+       PHP_HTTP_STRLIST_ITEM("COPY")
+       PHP_HTTP_STRLIST_ITEM("MOVE")
+       PHP_HTTP_STRLIST_ITEM("LOCK")
+       PHP_HTTP_STRLIST_ITEM("UNLOCK")
+       /* WebDAV Versioning - RFC 3253 */
+       PHP_HTTP_STRLIST_ITEM("VERSION-CONTROL")
+       PHP_HTTP_STRLIST_ITEM("REPORT")
+       PHP_HTTP_STRLIST_ITEM("CHECKOUT")
+       PHP_HTTP_STRLIST_ITEM("CHECKIN")
+       PHP_HTTP_STRLIST_ITEM("UNCHECKOUT")
+       PHP_HTTP_STRLIST_ITEM("MKWORKSPACE")
+       PHP_HTTP_STRLIST_ITEM("UPDATE")
+       PHP_HTTP_STRLIST_ITEM("LABEL")
+       PHP_HTTP_STRLIST_ITEM("MERGE")
+       PHP_HTTP_STRLIST_ITEM("BASELINE-CONTROL")
+       PHP_HTTP_STRLIST_ITEM("MKACTIVITY")
+       /* WebDAV Access Control - RFC 3744 */
+       PHP_HTTP_STRLIST_ITEM("ACL")
+       PHP_HTTP_STRLIST_STOP
+;
+
+PHP_HTTP_API const char *php_http_request_method_name(php_http_request_method_t meth)
+{
+       if (meth > PHP_HTTP_NO_REQUEST_METHOD && meth < PHP_HTTP_MAX_REQUEST_METHOD) {
+               return php_http_strlist_find(php_http_request_methods, 1, meth);
+       } else {
+               zval **val, *cmp, res;
+               HashPosition pos;
+               php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+
+               INIT_PZVAL(&res);
+               FOREACH_HASH_KEYVAL(pos, &php_http_request_class_entry->constants_table, key, val) {
+                       MAKE_STD_ZVAL(cmp);
+                       ZVAL_LONG(cmp, meth);
+                       is_equal_function(&res, *val, cmp TSRMLS_CC);
+                       zval_ptr_dtor(&cmp);
+
+                       if (Z_LVAL(res)) {
+                               return key.str;
+                       }
+               }
+       }
+       return NULL;
+}
+
+PHP_HTTP_API STATUS php_http_request_method_register(const char *meth_str, size_t meth_len, long *id TSRMLS_DC)
+{
+       long num = zend_hash_num_elements(&php_http_request_class_entry->constants_table);
+
+       if (SUCCESS == zend_declare_class_constant_long(php_http_request_method_class_entry, meth_str, meth_len, num TSRMLS_CC)) {
+               if (id) {
+                       *id = num;
+               }
+               return SUCCESS;
+       }
+       return FAILURE;
+}
+
+zend_class_entry *php_http_request_method_class_entry;
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpRequestMethod, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpRequestMethod, method, 0)
+#define PHP_HTTP_REQMETH_ME(method, visibility)        PHP_ME(HttpRequestMethod, method, PHP_HTTP_ARGS(HttpRequestMethod, method), visibility)
+
+#ifdef register
+#      undef register
+#endif
+
+PHP_HTTP_BEGIN_ARGS(__construct, 1)
+       PHP_HTTP_ARG_VAL(name, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(__toString);
+
+PHP_HTTP_EMPTY_ARGS(getId);
+
+PHP_HTTP_BEGIN_ARGS(exists, 1)
+       PHP_HTTP_ARG_VAL(method, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(register, 1)
+       PHP_HTTP_ARG_VAL(method, 0)
+PHP_HTTP_END_ARGS;
+
+zend_function_entry php_http_request_method_method_entry[] = {
+       PHP_HTTP_REQMETH_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+       PHP_HTTP_REQMETH_ME(__toString, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQMETH_ME(getId, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQMETH_ME(exists, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+       PHP_HTTP_REQMETH_ME(register, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_METHOD(HttpRequestMethod, __construct)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               char *meth_str;
+               int meth_len;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &meth_str, &meth_len)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_method)) {
+                               zval *zarg, *zret;
+
+                               if (SUCCESS == zend_get_parameters(ZEND_NUM_ARGS(), 1, &zarg)) {
+                                       if (zend_call_method_with_1_params(&getThis(), php_http_request_method_class_entry, NULL, "exists", &zret, zarg)) {
+                                               if (i_zend_is_true(zret)) {
+                                                       zend_update_property_stringl(php_http_request_method_class_entry, getThis(), ZEND_STRL("name"), meth_str, meth_len TSRMLS_CC);
+                                               } else {
+                                                       php_http_error(HE_THROW, PHP_HTTP_E_REQUEST_METHOD, "Unknown request method '%s'", meth_str);
+                                               }
+                                               zval_ptr_dtor(&zret);
+                                       }
+                               }
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequestMethod, __toString)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               zval *retval = php_http_zsep(IS_STRING, zend_read_property(php_http_request_method_class_entry, getThis(), ZEND_STRL("name"), 0 TSRMLS_CC));
+
+               RETURN_ZVAL(retval, 0, 0);
+       }
+       RETURN_EMPTY_STRING();
+}
+
+PHP_METHOD(HttpRequestMethod, getId)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               zval **data, *meth = php_http_zsep(IS_STRING, zend_read_property(php_http_request_method_class_entry, getThis(), ZEND_STRL("name"), 0 TSRMLS_CC));
+
+               if (SUCCESS == zend_hash_find(&php_http_request_method_class_entry->constants_table, Z_STRVAL_P(meth), Z_STRLEN_P(meth) + 1, (void *) &data)) {
+                       zval_ptr_dtor(&meth);
+                       RETURN_ZVAL(*data, 1, 0);
+               }
+               zval_ptr_dtor(&meth);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestMethod, exists)
+{
+       char *meth_str;
+       int meth_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &meth_str, &meth_len)) {
+               zval **data;
+
+               if (SUCCESS == zend_hash_find(&php_http_request_method_class_entry->constants_table, meth_str, meth_len + 1, (void *) &data)) {
+                       RETURN_ZVAL(*data, 1, 0);
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestMethod, register)
+{
+       char *meth_str;
+       int meth_len;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &meth_str, &meth_len)) {
+               RETURN_SUCCESS(zend_declare_class_constant_long(php_http_request_method_class_entry, meth_str, meth_len, zend_hash_num_elements(&php_http_request_class_entry->constants_table) TSRMLS_CC));
+       }
+       RETURN_FALSE;
+}
+
+PHP_MINIT_FUNCTION(http_request_method)
+{
+       php_http_strlist_iterator_t std;
+
+       PHP_HTTP_REGISTER_CLASS(http\\request, Method, http_request_method, php_http_object_class_entry, 0);
+
+       zend_declare_property_null(php_http_request_method_class_entry, ZEND_STRL("name"), ZEND_ACC_PROTECTED TSRMLS_CC);
+
+       php_http_strlist_iterator_init(&std, php_http_request_methods, 1);
+       do {
+               unsigned id;
+               const char *meth = php_http_strlist_iterator_this(&std, &id);
+
+               zend_declare_class_constant_long(php_http_request_method_class_entry, meth, strlen(meth), id TSRMLS_CC);
+       } while (*php_http_strlist_iterator_next(&std));
+
+       return SUCCESS;
+}
+
+
+/*
+ * 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_request_method.h b/php_http_request_method.h
new file mode 100644 (file)
index 0000000..6c29e80
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_request_method_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_REQUEST_METHOD_H
+#define PHP_HTTP_REQUEST_METHOD_H
+
+typedef enum php_http_request_method {
+       /* force the enum to be signed */
+       PHP_HTTP_NEG_REQUEST_METHOD     =-1,
+       PHP_HTTP_NO_REQUEST_METHOD      = 0,
+       /* HTTP/1.1 */
+       PHP_HTTP_GET                            = 1,
+       PHP_HTTP_HEAD                           = 2,
+       PHP_HTTP_POST                           = 3,
+       PHP_HTTP_PUT                            = 4,
+       PHP_HTTP_DELETE                         = 5,
+       PHP_HTTP_OPTIONS                        = 6,
+       PHP_HTTP_TRACE                          = 7,
+       PHP_HTTP_CONNECT                        = 8,
+       /* WebDAV - RFC 2518 */
+       PHP_HTTP_PROPFIND                       = 9,
+       PHP_HTTP_PROPPATCH                      = 10,
+       PHP_HTTP_MKCOL                          = 11,
+       PHP_HTTP_COPY                           = 12,
+       PHP_HTTP_MOVE                           = 13,
+       PHP_HTTP_LOCK                           = 14,
+       PHP_HTTP_UNLOCK                         = 15,
+       /* WebDAV Versioning - RFC 3253 */
+       PHP_HTTP_VERSION_CONTROL        = 16,
+       PHP_HTTP_REPORT                         = 17,
+       PHP_HTTP_CHECKOUT                       = 18,
+       PHP_HTTP_CHECKIN                        = 19,
+       PHP_HTTP_UNCHECKOUT                     = 20,
+       PHP_HTTP_MKWORKSPACE            = 21,
+       PHP_HTTP_UPDATE                         = 22,
+       PHP_HTTP_LABEL                          = 23,
+       PHP_HTTP_MERGE                          = 24,
+       PHP_HTTP_BASELINE_CONTROL       = 25,
+       PHP_HTTP_MKACTIVITY                     = 26,
+       /* WebDAV Access Control - RFC 3744 */
+       PHP_HTTP_ACL                            = 27,
+       PHP_HTTP_MAX_REQUEST_METHOD     = 28
+} php_http_request_method_t;
+
+PHP_HTTP_API const char *php_http_request_method_name(php_http_request_method_t meth);
+PHP_HTTP_API STATUS php_http_request_method_register(const char *meth_str, size_t meth_len, long *id TSRMLS_DC);
+
+extern zend_class_entry *php_http_request_method_class_entry;
+extern zend_function_entry php_http_request_method_method_entry[];
+
+extern PHP_METHOD(HttpRequestMethod, __construct);
+extern PHP_METHOD(HttpRequestMethod, __toString);
+extern PHP_METHOD(HttpRequestMethod, getId);
+
+extern PHP_METHOD(HttpRequestMethod, exists);
+extern PHP_METHOD(HttpRequestMethod, register);
+
+extern PHP_MINIT_FUNCTION(http_request_method);
+
+#endif
+
+/*
+ * 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_request_pool.c b/php_http_request_pool.c
new file mode 100644 (file)
index 0000000..8585d77
--- /dev/null
@@ -0,0 +1,958 @@
+
+#include "php_http.h"
+
+
+#ifndef PHP_HTTP_DEBUG_REQPOOLS
+#      define PHP_HTTP_DEBUG_REQPOOLS 0
+#endif
+
+#ifdef PHP_HTTP_HAVE_EVENT
+typedef struct php_http_request_pool_event {
+       struct event evnt;
+       php_http_request_pool_t *pool;
+} php_http_request_pool_event_t;
+
+static void php_http_request_pool_timeout_callback(int socket, short action, void *event_data);
+static void php_http_request_pool_event_callback(int socket, short action, void *event_data);
+static int php_http_request_pool_socket_callback(CURL *easy, curl_socket_t s, int action, void *, void *);
+static void php_http_request_pool_timer_callback(CURLM *multi, long timeout_ms, void *timer_data);
+#endif
+
+static int php_http_request_pool_compare_handles(void *h1, void *h2);
+
+PHP_HTTP_API php_http_request_pool_t *php_http_request_pool_init(php_http_request_pool_t *pool TSRMLS_DC)
+{
+       zend_bool free_pool;
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Initializing request pool %p\n", pool);
+#endif
+
+       if ((free_pool = (!pool))) {
+               pool = emalloc(sizeof(php_http_request_pool_t));
+               pool->ch = NULL;
+       }
+
+       if (SUCCESS != php_http_persistent_handle_acquire(ZEND_STRL("http_request_pool"), &pool->ch TSRMLS_CC)) {
+               if (free_pool) {
+                       efree(pool);
+               }
+               return NULL;
+       }
+
+       TSRMLS_SET_CTX(pool->ts);
+
+#ifdef PHP_HTTP_HAVE_EVENT
+       pool->timeout = ecalloc(1, sizeof(struct event));
+       curl_multi_setopt(pool->ch, CURLMOPT_SOCKETDATA, pool);
+       curl_multi_setopt(pool->ch, CURLMOPT_SOCKETFUNCTION, php_http_request_pool_socket_callback);
+       curl_multi_setopt(pool->ch, CURLMOPT_TIMERDATA, pool);
+       curl_multi_setopt(pool->ch, CURLMOPT_TIMERFUNCTION, php_http_request_pool_timer_callback);
+#endif
+
+       pool->unfinished = 0;
+       zend_llist_init(&pool->finished, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
+       zend_llist_init(&pool->handles, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Initialized request pool %p\n", pool);
+#endif
+
+       return pool;
+}
+
+PHP_HTTP_API STATUS php_http_request_pool_attach(php_http_request_pool_t *pool, zval *request)
+{
+#ifdef ZTS
+       TSRMLS_FETCH_FROM_CTX(pool->ts);
+#endif
+       php_http_request_object_t *req = zend_object_store_get_object(request TSRMLS_CC);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Attaching HttpRequest(#%d) %p to pool %p\n", Z_OBJ_HANDLE_P(request), req, pool);
+#endif
+
+       if (req->pool) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is already member of %s HttpRequestPool", Z_OBJ_HANDLE_P(request), req->pool == pool ? "this" : "another");
+       } else if (SUCCESS != php_http_request_object_requesthandler(req, request)) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not initialize HttpRequest object(#%d) for attaching to the HttpRequestPool", Z_OBJ_HANDLE_P(request));
+       } else {
+               CURLMcode code = curl_multi_add_handle(pool->ch, req->request->ch);
+
+               if (CURLM_OK != code) {
+                       php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "Could not attach HttpRequest object(#%d) to the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request), curl_multi_strerror(code));
+               } else {
+                       req->pool = pool;
+
+                       Z_ADDREF_P(request);
+                       zend_llist_add_element(&pool->handles, &request);
+                       ++pool->unfinished;
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+                       fprintf(stderr, "> %d HttpRequests attached to pool %p\n", zend_llist_count(&pool->handles), pool);
+#endif
+                       return SUCCESS;
+               }
+       }
+       return FAILURE;
+}
+
+PHP_HTTP_API STATUS php_http_request_pool_detach(php_http_request_pool_t *pool, zval *request)
+{
+       CURLMcode code;
+#ifdef ZTS
+       TSRMLS_FETCH_FROM_CTX(pool->ts);
+#endif
+       php_http_request_object_t *req = zend_object_store_get_object(request TSRMLS_CC);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Detaching HttpRequest(#%d) %p from pool %p\n", Z_OBJ_HANDLE_P(request), req, pool);
+#endif
+
+       if (!req->pool) {
+               /* not attached to any pool */
+#if PHP_HTTP_DEBUG_REQPOOLS
+               fprintf(stderr, "HttpRequest object(#%d) %p is not attached to any HttpRequestPool\n", Z_OBJ_HANDLE_P(request), req);
+#endif
+       } else if (req->pool != pool) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is not attached to this HttpRequestPool", Z_OBJ_HANDLE_P(request));
+       } else if (req->request->_progress.in_cb) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "HttpRequest object(#%d) cannot be detached from the HttpRequestPool while executing the progress callback", Z_OBJ_HANDLE_P(request));
+       } else if (CURLM_OK != (code = curl_multi_remove_handle(pool->ch, req->request->ch))) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "Could not detach HttpRequest object(#%d) from the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request), curl_multi_strerror(code));
+       } else {
+               req->pool = NULL;
+               zend_llist_del_element(&pool->finished, request, php_http_request_pool_compare_handles);
+               zend_llist_del_element(&pool->handles, request, php_http_request_pool_compare_handles);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+               fprintf(stderr, "> %d HttpRequests remaining in pool %p\n", zend_llist_count(&pool->handles), pool);
+#endif
+
+               return SUCCESS;
+       }
+       return FAILURE;
+}
+
+PHP_HTTP_API void php_http_request_pool_apply(php_http_request_pool_t *pool, php_http_request_pool_apply_func_t cb)
+{
+       int count = zend_llist_count(&pool->handles);
+
+       if (count) {
+               int i = 0;
+               zend_llist_position pos;
+               zval **handle, **handles = emalloc(count * sizeof(zval *));
+
+               for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) {
+                       handles[i++] = *handle;
+               }
+
+               /* should never happen */
+               if (i != count) {
+                       zend_error(E_ERROR, "number of fetched request handles do not match overall count");
+                       count = i;
+               }
+
+               for (i = 0; i < count; ++i) {
+                       if (cb(pool, handles[i])) {
+                               break;
+                       }
+               }
+               efree(handles);
+       }
+}
+
+PHP_HTTP_API void php_http_request_pool_apply_with_arg(php_http_request_pool_t *pool, php_http_request_pool_apply_with_arg_func_t cb, void *arg)
+{
+       int count = zend_llist_count(&pool->handles);
+
+       if (count) {
+               int i = 0;
+               zend_llist_position pos;
+               zval **handle, **handles = emalloc(count * sizeof(zval *));
+
+               for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) {
+                       handles[i++] = *handle;
+               }
+
+               /* should never happen */
+               if (i != count) {
+                       zend_error(E_ERROR, "number of fetched request handles do not match overall count");
+                       count = i;
+               }
+
+               for (i = 0; i < count; ++i) {
+                       if (cb(pool, handles[i], arg)) {
+                               break;
+                       }
+               }
+               efree(handles);
+       }
+}
+
+PHP_HTTP_API void php_http_request_pool_detach_all(php_http_request_pool_t *pool)
+{
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Detaching %d requests from pool %p\n", zend_llist_count(&pool->handles), pool);
+#endif
+       php_http_request_pool_apply(pool, php_http_request_pool_detach);
+}
+
+PHP_HTTP_API STATUS php_http_request_pool_send(php_http_request_pool_t *pool)
+{
+       TSRMLS_FETCH_FROM_CTX(pool->ts);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Attempt to send %d requests of pool %p\n", zend_llist_count(&pool->handles), pool);
+#endif
+
+#ifdef PHP_HTTP_HAVE_EVENT
+       if (pool->useevents) {
+               do {
+#if PHP_HTTP_DEBUG_REQPOOLS
+                       fprintf(stderr, "& Starting event dispatcher of pool %p\n", pool);
+#endif
+                       event_base_dispatch(PHP_HTTP_G->request_pool.event_base);
+               } while (pool->unfinished);
+       } else
+#endif
+       {
+               while (php_http_request_pool_perform(pool)) {
+                       if (SUCCESS != php_http_request_pool_select(pool, NULL)) {
+#ifdef PHP_WIN32
+                               /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */
+                               php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError());
+#else
+                               php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, strerror(errno));
+#endif
+                               return FAILURE;
+                       }
+               }
+       }
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Finished sending %d HttpRequests of pool %p (still unfinished: %d)\n", zend_llist_count(&pool->handles), pool, pool->unfinished);
+#endif
+
+       return SUCCESS;
+}
+
+PHP_HTTP_API void php_http_request_pool_dtor(php_http_request_pool_t *pool)
+{
+       TSRMLS_FETCH_FROM_CTX(pool->ts);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Destructing request pool %p\n", pool);
+#endif
+
+#ifdef PHP_HTTP_HAVE_EVENT
+       efree(pool->timeout);
+#endif
+
+       php_http_request_pool_detach_all(pool);
+
+       pool->unfinished = 0;
+       zend_llist_clean(&pool->finished);
+       zend_llist_clean(&pool->handles);
+       php_http_persistent_handle_release(ZEND_STRL("php_http_request_pool_t"), &pool->ch TSRMLS_CC);
+}
+
+PHP_HTTP_API void php_http_request_pool_free(php_http_request_pool_t **pool) {
+       if (*pool) {
+               php_http_request_pool_dtor(*pool);
+               efree(*pool);
+               *pool = NULL;
+       }
+}
+
+#ifdef PHP_WIN32
+#      define SELECT_ERROR SOCKET_ERROR
+#else
+#      define SELECT_ERROR -1
+#endif
+
+PHP_HTTP_API STATUS php_http_request_pool_select(php_http_request_pool_t *pool, struct timeval *custom_timeout)
+{
+       int MAX;
+       fd_set R, W, E;
+       struct timeval timeout;
+
+#ifdef PHP_HTTP_HAVE_EVENT
+       if (pool->useevents) {
+               TSRMLS_FETCH_FROM_CTX(pool->ts);
+               php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented; use HttpRequest callbacks");
+               return FAILURE;
+       }
+#endif
+
+       if (custom_timeout && timerisset(custom_timeout)) {
+               timeout = *custom_timeout;
+       } else {
+               php_http_request_pool_timeout(pool, &timeout);
+       }
+
+       FD_ZERO(&R);
+       FD_ZERO(&W);
+       FD_ZERO(&E);
+
+       if (CURLM_OK == curl_multi_fdset(pool->ch, &R, &W, &E, &MAX)) {
+               if (MAX == -1) {
+                       php_http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / PHP_HTTP_MCROSEC));
+                       return SUCCESS;
+               } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) {
+                       return SUCCESS;
+               }
+       }
+       return FAILURE;
+}
+
+PHP_HTTP_API int php_http_request_pool_perform(php_http_request_pool_t *pool)
+{
+       TSRMLS_FETCH_FROM_CTX(pool->ts);
+
+#ifdef PHP_HTTP_HAVE_EVENT
+       if (pool->useevents) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented; use HttpRequest callbacks");
+               return FAILURE;
+       }
+#endif
+
+       while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(pool->ch, &pool->unfinished));
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "%u unfinished requests of pool %p remaining\n", pool->unfinished, pool);
+#endif
+
+       php_http_request_pool_responsehandler(pool);
+
+       return pool->unfinished;
+}
+
+void php_http_request_pool_responsehandler(php_http_request_pool_t *pool)
+{
+       CURLMsg *msg;
+       int remaining = 0;
+       TSRMLS_FETCH_FROM_CTX(pool->ts);
+
+       do {
+               msg = curl_multi_info_read(pool->ch, &remaining);
+               if (msg && CURLMSG_DONE == msg->msg) {
+                       if (CURLE_OK != msg->data.result) {
+                               php_http_request_storage_t *st = php_http_request_storage_get(msg->easy_handle);
+                               php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "%s; %s (%s)", curl_easy_strerror(msg->data.result), STR_PTR(st->errorbuffer), STR_PTR(st->url));
+                       }
+                       php_http_request_pool_apply_with_arg(pool, php_http_request_pool_apply_responsehandler, msg->easy_handle);
+               }
+       } while (remaining);
+}
+
+int php_http_request_pool_apply_responsehandler(php_http_request_pool_t *pool, zval *req, void *ch)
+{
+#ifdef ZTS
+       TSRMLS_FETCH_FROM_CTX(pool->ts);
+#endif
+       php_http_request_object_t *obj = zend_object_store_get_object(req TSRMLS_CC);
+
+       if ((!ch) || obj->request->ch == (CURL *) ch) {
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+               fprintf(stderr, "Fetching data from HttpRequest(#%d) %p of pool %p\n", Z_OBJ_HANDLE_P(req), obj, obj->pool);
+#endif
+
+               Z_ADDREF_P(req);
+               zend_llist_add_element(&obj->pool->finished, &req);
+               php_http_request_object_responsehandler(obj, req);
+               return 1;
+       }
+       return 0;
+}
+
+struct timeval *php_http_request_pool_timeout(php_http_request_pool_t *pool, struct timeval *timeout)
+{
+#ifdef HAVE_CURL_MULTI_TIMEOUT
+       long max_tout = 1000;
+
+       if ((CURLM_OK == curl_multi_timeout(pool->ch, &max_tout)) && (max_tout > 0)) {
+               timeout->tv_sec = max_tout / 1000;
+               timeout->tv_usec = (max_tout % 1000) * 1000;
+       } else {
+#endif
+               timeout->tv_sec = 0;
+               timeout->tv_usec = 1000;
+#ifdef HAVE_CURL_MULTI_TIMEOUT
+       }
+#endif
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+       fprintf(stderr, "Calculating timeout (%lu, %lu) of pool %p\n", (ulong) timeout->tv_sec, (ulong) timeout->tv_usec, pool);
+#endif
+
+       return timeout;
+}
+
+/*#*/
+
+static int php_http_request_pool_compare_handles(void *h1, void *h2)
+{
+       return (Z_OBJ_HANDLE_PP((zval **) h1) == Z_OBJ_HANDLE_P((zval *) h2));
+}
+
+#ifdef PHP_HTTP_HAVE_EVENT
+
+static void php_http_request_pool_timeout_callback(int socket, short action, void *event_data)
+{
+       php_http_request_pool_t *pool = event_data;
+
+       if (pool->useevents) {
+               CURLMcode rc;
+               TSRMLS_FETCH_FROM_CTX(pool->ts);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+               fprintf(stderr, "Timeout occurred of pool %p\n", pool);
+#endif
+
+               while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket(pool->ch, CURL_SOCKET_TIMEOUT, &pool->unfinished)));
+
+               if (CURLM_OK != rc) {
+                       php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, curl_multi_strerror(rc));
+               }
+
+               php_http_request_pool_responsehandler(pool);
+       }
+}
+
+static void php_http_request_pool_event_callback(int socket, short action, void *event_data)
+{
+       php_http_request_pool_event_t *ev = event_data;
+       php_http_request_pool_t *pool = ev->pool;
+
+       if (pool->useevents) {
+               CURLMcode rc = CURLE_OK;
+               TSRMLS_FETCH_FROM_CTX(ev->pool->ts);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+               {
+                       static const char event_strings[][20] = {"NONE","TIMEOUT","READ","TIMEOUT|READ","WRITE","TIMEOUT|WRITE","READ|WRITE","TIMEOUT|READ|WRITE","SIGNAL"};
+                       fprintf(stderr, "Event on socket %d (%s) event %p of pool %p\n", socket, event_strings[action], ev, pool);
+               }
+#endif
+
+               /* don't use 'ev' below this loop as it might 've been freed in the socket callback */
+               do {
+#ifdef HAVE_CURL_MULTI_SOCKET_ACTION
+                       switch (action & (EV_READ|EV_WRITE)) {
+                               case EV_READ:
+                                       rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_IN, &pool->unfinished);
+                                       break;
+                               case EV_WRITE:
+                                       rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_OUT, &pool->unfinished);
+                                       break;
+                               case EV_READ|EV_WRITE:
+                                       rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_IN|CURL_CSELECT_OUT, &pool->unfinished);
+                                       break;
+                               default:
+                                       php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "Unknown event %d", (int) action);
+                                       return;
+                       }
+#else
+                       rc = curl_multi_socket(pool->ch, socket, &pool->unfinished);
+#endif
+               } while (CURLM_CALL_MULTI_PERFORM == rc);
+
+               switch (rc) {
+                       case CURLM_BAD_SOCKET:
+#if 0
+                               fprintf(stderr, "!!! Bad socket: %d (%d)\n", socket, (int) action);
+#endif
+                       case CURLM_OK:
+                               break;
+                       default:
+                               php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, curl_multi_strerror(rc));
+                               break;
+               }
+
+               php_http_request_pool_responsehandler(pool);
+
+               /* remove timeout if there are no transfers left */
+               if (!pool->unfinished && event_initialized(pool->timeout) && event_pending(pool->timeout, EV_TIMEOUT, NULL)) {
+                       event_del(pool->timeout);
+#if PHP_HTTP_DEBUG_REQPOOLS
+                       fprintf(stderr, "Removed timeout of pool %p\n", pool);
+#endif
+               }
+       }
+}
+
+static int php_http_request_pool_socket_callback(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data)
+{
+       php_http_request_pool_t *pool = socket_data;
+
+       if (pool->useevents) {
+               int events = EV_PERSIST;
+               php_http_request_pool_event_t *ev = assign_data;
+               TSRMLS_FETCH_FROM_CTX(pool->ts);
+
+               if (!ev) {
+                       ev = ecalloc(1, sizeof(php_http_request_pool_event_t));
+                       ev->pool = pool;
+                       curl_multi_assign(pool->ch, sock, ev);
+                       event_base_set(PHP_HTTP_G->request_pool.event_base, &ev->evnt);
+               } else {
+                       event_del(&ev->evnt);
+               }
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+               {
+                       static const char action_strings[][8] = {"NONE", "IN", "OUT", "INOUT", "REMOVE"};
+                       php_http_request_t *r;
+                       curl_easy_getinfo(easy, CURLINFO_PRIVATE, &r);
+                       fprintf(stderr, "Callback on socket %2d (%8s) event %p of pool %p (%d)\n", (int) sock, action_strings[action], ev, pool, pool->unfinished);
+               }
+#endif
+
+               switch (action) {
+                       case CURL_POLL_IN:
+                               events |= EV_READ;
+                               break;
+                       case CURL_POLL_OUT:
+                               events |= EV_WRITE;
+                               break;
+                       case CURL_POLL_INOUT:
+                               events |= EV_READ|EV_WRITE;
+                               break;
+
+                       case CURL_POLL_REMOVE:
+                               efree(ev);
+                       case CURL_POLL_NONE:
+                               return 0;
+
+                       default:
+                               php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "Unknown socket action %d", action);
+                               return -1;
+               }
+
+               event_set(&ev->evnt, sock, events, php_http_request_pool_event_callback, ev);
+               event_add(&ev->evnt, NULL);
+       }
+
+       return 0;
+}
+
+static void php_http_request_pool_timer_callback(CURLM *multi, long timeout_ms, void *timer_data)
+{
+       php_http_request_pool_t *pool = timer_data;
+
+       if (pool->useevents) {
+               TSRMLS_FETCH_FROM_CTX(pool->ts);
+               struct timeval timeout;
+
+               if (!event_initialized(pool->timeout)) {
+                       event_set(pool->timeout, -1, 0, php_http_request_pool_timeout_callback, pool);
+                       event_base_set(PHP_HTTP_G->request_pool.event_base, pool->timeout);
+               } else if (event_pending(pool->timeout, EV_TIMEOUT, NULL)) {
+                       event_del(pool->timeout);
+               }
+
+               if (timeout_ms > 0) {
+                       timeout.tv_sec = timeout_ms / 1000;
+                       timeout.tv_usec = (timeout_ms % 1000) * 1000;
+               } else {
+                       php_http_request_pool_timeout(pool, &timeout);
+               }
+
+               event_add(pool->timeout, &timeout);
+
+#if PHP_HTTP_DEBUG_REQPOOLS
+               fprintf(stderr, "Updating timeout %lu (%lu, %lu) of pool %p\n", (ulong) timeout_ms, (ulong) timeout.tv_sec, (ulong) timeout.tv_usec, pool);
+#endif
+       }
+}
+#endif /* HAVE_EVENT */
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpRequestPool, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpRequestPool, method, 0)
+#define PHP_HTTP_REQPOOL_ME(method, visibility)        PHP_ME(HttpRequestPool, method, PHP_HTTP_ARGS(HttpRequestPool, method), visibility)
+
+PHP_HTTP_EMPTY_ARGS(__construct);
+
+PHP_HTTP_EMPTY_ARGS(__destruct);
+PHP_HTTP_EMPTY_ARGS(reset);
+
+PHP_HTTP_BEGIN_ARGS(attach, 1)
+       PHP_HTTP_ARG_OBJ(http\\Request, request, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(detach, 1)
+       PHP_HTTP_ARG_OBJ(http\\Request, request, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(send);
+PHP_HTTP_EMPTY_ARGS(socketPerform);
+PHP_HTTP_BEGIN_ARGS(socketSelect, 0)
+       PHP_HTTP_ARG_VAL(timeout, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_EMPTY_ARGS(valid);
+PHP_HTTP_EMPTY_ARGS(current);
+PHP_HTTP_EMPTY_ARGS(key);
+PHP_HTTP_EMPTY_ARGS(next);
+PHP_HTTP_EMPTY_ARGS(rewind);
+
+PHP_HTTP_EMPTY_ARGS(count);
+
+PHP_HTTP_EMPTY_ARGS(getAttachedRequests);
+PHP_HTTP_EMPTY_ARGS(getFinishedRequests);
+
+PHP_HTTP_BEGIN_ARGS(enablePipelining, 0)
+       PHP_HTTP_ARG_VAL(enable, 0)
+PHP_HTTP_END_ARGS;
+
+PHP_HTTP_BEGIN_ARGS(enableEvents, 0)
+       PHP_HTTP_ARG_VAL(enable, 0)
+PHP_HTTP_END_ARGS;
+
+zend_class_entry *php_http_request_pool_class_entry;
+zend_function_entry php_http_request_pool_method_entry[] = {
+       PHP_HTTP_REQPOOL_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+       PHP_HTTP_REQPOOL_ME(__destruct, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR)
+       PHP_HTTP_REQPOOL_ME(attach, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(detach, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(send, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(reset, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQPOOL_ME(socketPerform, ZEND_ACC_PROTECTED)
+       PHP_HTTP_REQPOOL_ME(socketSelect, ZEND_ACC_PROTECTED)
+
+       /* implements Iterator */
+       PHP_HTTP_REQPOOL_ME(valid, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(current, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(key, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(next, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(rewind, ZEND_ACC_PUBLIC)
+
+       /* implmenents Countable */
+       PHP_HTTP_REQPOOL_ME(count, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQPOOL_ME(getAttachedRequests, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(getFinishedRequests, ZEND_ACC_PUBLIC)
+
+       PHP_HTTP_REQPOOL_ME(enablePipelining, ZEND_ACC_PUBLIC)
+       PHP_HTTP_REQPOOL_ME(enableEvents, ZEND_ACC_PUBLIC)
+
+       EMPTY_FUNCTION_ENTRY
+};
+static zend_object_handlers php_http_request_pool_object_handlers;
+
+zend_object_value php_http_request_pool_object_new(zend_class_entry *ce TSRMLS_DC)
+{
+       zend_object_value ov;
+       php_http_request_pool_object_t *o;
+
+       o = ecalloc(1, sizeof(php_http_request_pool_object_t));
+       zend_object_std_init((zend_object *) o, ce TSRMLS_CC);
+       object_properties_init((zend_object *) o, ce);
+
+       php_http_request_pool_init(&o->pool TSRMLS_CC);
+
+       ov.handle = zend_objects_store_put(o, NULL, php_http_request_pool_object_free, NULL TSRMLS_CC);
+       ov.handlers = &php_http_request_pool_object_handlers;
+
+       return ov;
+}
+
+void php_http_request_pool_object_free(void *object TSRMLS_DC)
+{
+       php_http_request_pool_object_t *o = (php_http_request_pool_object_t *) object;
+
+       php_http_request_pool_dtor(&o->pool);
+       zend_object_std_dtor((zend_object *) o TSRMLS_CC);
+       efree(o);
+}
+
+static void php_http_request_pool_object_llist2array(zval **req, zval *array TSRMLS_DC)
+{
+       Z_ADDREF_P(*req);
+       add_next_index_zval(array, *req);
+}
+
+/* ### USERLAND ### */
+
+PHP_METHOD(HttpRequestPool, __construct)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               int argc;
+               zval ***argv;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "*", &argv, &argc)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) {
+                               int i;
+                               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                               for (i = 0; i < argc; ++i) {
+                                       if (Z_TYPE_PP(argv[i]) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(argv[i]), php_http_request_class_entry TSRMLS_CC)) {
+                                               php_http_request_pool_attach(&obj->pool, *(argv[i]));
+                                       }
+                               }
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequestPool, __destruct)
+{
+       php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+       if (SUCCESS != zend_parse_parameters_none()) {
+               ; /* we always want to clean up */
+       }
+
+       php_http_request_pool_detach_all(&obj->pool);
+}
+
+PHP_METHOD(HttpRequestPool, reset)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               obj->iterator.pos = 0;
+               php_http_request_pool_detach_all(&obj->pool);
+               RETURN_TRUE;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, attach)
+{
+       RETVAL_FALSE;
+
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               zval *request;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+                               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                               if (obj->iterator.pos > 0 && obj->iterator.pos < zend_llist_count(&obj->pool.handles)) {
+                                       php_http_error(HE_THROW, PHP_HTTP_E_REQUEST_POOL, "Cannot attach to the HttpRequestPool while the iterator is active");
+                               } else {
+                                       RETVAL_SUCCESS(php_http_request_pool_attach(&obj->pool, request));
+                               }
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequestPool, detach)
+{
+       RETVAL_FALSE;
+
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               zval *request;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) {
+                               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                               obj->iterator.pos = -1;
+                               RETVAL_SUCCESS(php_http_request_pool_detach(&obj->pool, request));
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequestPool, send)
+{
+       RETVAL_FALSE;
+
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               if (SUCCESS == zend_parse_parameters_none()) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) {
+                               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+                               RETVAL_SUCCESS(php_http_request_pool_send(&obj->pool));
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpRequestPool, socketPerform)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (0 < php_http_request_pool_perform(&obj->pool)) {
+                       RETURN_TRUE;
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, socketSelect)
+{
+       double timeout = 0;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|d", &timeout)) {
+               struct timeval timeout_val;
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               timeout_val.tv_sec = (time_t) timeout;
+               timeout_val.tv_usec = PHP_HTTP_USEC(timeout) % PHP_HTTP_MCROSEC;
+
+               RETURN_SUCCESS(php_http_request_pool_select(&obj->pool, timeout ? &timeout_val : NULL));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, valid)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_BOOL(obj->iterator.pos >= 0 && obj->iterator.pos < zend_llist_count(&obj->pool.handles));
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, current)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (obj->iterator.pos < zend_llist_count(&obj->pool.handles)) {
+                       long pos = 0;
+                       zval **current = NULL;
+                       zend_llist_position lpos;
+
+                       for (   current = zend_llist_get_first_ex(&obj->pool.handles, &lpos);
+                                       current && obj->iterator.pos != pos++;
+                                       current = zend_llist_get_next_ex(&obj->pool.handles, &lpos));
+                       if (current) {
+                               RETURN_OBJECT(*current, 1);
+                       }
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, key)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_LONG(obj->iterator.pos);
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, next)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               ++obj->iterator.pos;
+       }
+}
+
+PHP_METHOD(HttpRequestPool, rewind)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               obj->iterator.pos = 0;
+       }
+}
+
+PHP_METHOD(HttpRequestPool, count)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               RETURN_LONG((long) zend_llist_count(&obj->pool.handles));
+       }
+}
+
+PHP_METHOD(HttpRequestPool, getAttachedRequests)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_init(return_value);
+               zend_llist_apply_with_argument(&obj->pool.handles,
+                       (llist_apply_with_arg_func_t) php_http_request_pool_object_llist2array,
+                       return_value TSRMLS_CC);
+               return;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, getFinishedRequests)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               array_init(return_value);
+               zend_llist_apply_with_argument(&obj->pool.finished,
+                               (llist_apply_with_arg_func_t) php_http_request_pool_object_llist2array,
+                               return_value TSRMLS_CC);
+               return;
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, enablePipelining)
+{
+       zend_bool enable = 1;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &enable)) {
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               if (CURLM_OK == curl_multi_setopt(obj->pool.ch, CURLMOPT_PIPELINING, (long) enable)) {
+                       RETURN_TRUE;
+               }
+       }
+       RETURN_FALSE;
+}
+
+PHP_METHOD(HttpRequestPool, enableEvents)
+{
+       zend_bool enable = 1;
+
+       if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &enable)) {
+#if PHP_HTTP_HAVE_EVENT
+               php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC);
+
+               obj->pool.useevents = enable;
+               RETURN_TRUE;
+#endif
+       }
+       RETURN_FALSE;
+}
+
+PHP_MINIT_FUNCTION(http_request_pool)
+{
+       if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request_pool"), curl_multi_init, (php_http_persistent_handle_dtor_t) curl_multi_cleanup, NULL TSRMLS_CC)) {
+               return FAILURE;
+       }
+
+       PHP_HTTP_REGISTER_CLASS(http\\request, Pool, http_request_pool, php_http_object_class_entry, 0);
+       php_http_request_pool_class_entry->create_object = php_http_request_pool_object_new;
+       memcpy(&php_http_request_pool_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers));
+       php_http_request_pool_object_handlers.clone_obj = NULL;
+
+       zend_class_implements(php_http_request_pool_class_entry TSRMLS_CC, 2, spl_ce_Countable, zend_ce_iterator);
+
+       return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(http_request_pool)
+{
+#ifdef PHP_HTTP_HAVE_EVENT
+       if (!PHP_HTTP_G->request_pool.event_base && !(PHP_HTTP_G->request_pool.event_base = event_init())) {
+               return FAILURE;
+       }
+#endif
+
+       return SUCCESS;
+}
+
+
diff --git a/php_http_request_pool.h b/php_http_request_pool.h
new file mode 100644 (file)
index 0000000..f7cedb6
--- /dev/null
@@ -0,0 +1,83 @@
+
+#ifndef PHP_HTTP_REQUESTPOOL_H
+#define PHP_HTTP_REQUESTPOOL_H
+
+struct php_http_request_pool_globals {
+       void *event_base;
+};
+
+typedef struct php_http_request_pool {
+       CURLM *ch;
+       zend_llist finished;
+       zend_llist handles;
+       int unfinished; /* int because of curl_multi_perform() */
+#ifdef ZTS
+       void ***ts;
+#endif
+#ifdef PHP_HTTP_HAVE_EVENT
+       struct event *timeout;
+       unsigned useevents:1;
+       unsigned runsocket:1;
+#endif
+} php_http_request_pool_t;
+
+typedef int (*php_http_request_pool_apply_func_t)(php_http_request_pool_t *pool, zval *request);
+typedef int (*php_http_request_pool_apply_with_arg_func_t)(php_http_request_pool_t *pool, zval *request, void *arg);
+
+#ifdef PHP_HTTP_HAVE_EVENT
+PHP_RINIT_FUNCTION(php_http_request_pool);
+#endif
+
+extern struct timeval *php_http_request_pool_timeout(php_http_request_pool_t *pool, struct timeval *timeout);
+extern void php_http_request_pool_responsehandler(php_http_request_pool_t *pool);
+extern int php_http_request_pool_apply_responsehandler(php_http_request_pool_t *pool, zval *req, void *ch);
+
+PHP_HTTP_API php_http_request_pool_t *php_http_request_pool_init(php_http_request_pool_t *pool TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_request_pool_attach(php_http_request_pool_t *pool, zval *request);
+PHP_HTTP_API STATUS php_http_request_pool_detach(php_http_request_pool_t *pool, zval *request);
+PHP_HTTP_API void php_http_request_pool_apply(php_http_request_pool_t *pool, php_http_request_pool_apply_func_t cb);
+PHP_HTTP_API void php_http_request_pool_apply_with_arg(php_http_request_pool_t *pool, php_http_request_pool_apply_with_arg_func_t cb, void *arg);
+PHP_HTTP_API void php_http_request_pool_detach_all(php_http_request_pool_t *pool);
+PHP_HTTP_API STATUS php_http_request_pool_send(php_http_request_pool_t *pool);
+PHP_HTTP_API STATUS php_http_request_pool_select(php_http_request_pool_t *pool, struct timeval *custom_timeout);
+PHP_HTTP_API int php_http_request_pool_perform(php_http_request_pool_t *pool);
+PHP_HTTP_API void php_http_request_pool_dtor(php_http_request_pool_t *pool);
+PHP_HTTP_API void php_http_request_pool_free(php_http_request_pool_t **pool);
+
+typedef struct php_http_request_pool_object {
+       zend_object zo;
+       php_http_request_pool_t pool;
+       struct {
+               long pos;
+       } iterator;
+} php_http_request_pool_object_t;
+
+extern zend_class_entry *php_http_request_pool_class_entry;
+extern zend_function_entry php_http_request_pool_method_entry[];
+
+extern zend_object_value php_http_request_pool_object_new(zend_class_entry *ce TSRMLS_DC);
+extern void php_http_request_pool_object_free(void *object TSRMLS_DC);
+
+PHP_METHOD(HttpRequestPool, __construct);
+PHP_METHOD(HttpRequestPool, __destruct);
+PHP_METHOD(HttpRequestPool, attach);
+PHP_METHOD(HttpRequestPool, detach);
+PHP_METHOD(HttpRequestPool, send);
+PHP_METHOD(HttpRequestPool, reset);
+PHP_METHOD(HttpRequestPool, socketPerform);
+PHP_METHOD(HttpRequestPool, socketSelect);
+PHP_METHOD(HttpRequestPool, valid);
+PHP_METHOD(HttpRequestPool, current);
+PHP_METHOD(HttpRequestPool, key);
+PHP_METHOD(HttpRequestPool, next);
+PHP_METHOD(HttpRequestPool, rewind);
+PHP_METHOD(HttpRequestPool, count);
+PHP_METHOD(HttpRequestPool, getAttachedRequests);
+PHP_METHOD(HttpRequestPool, getFinishedRequests);
+PHP_METHOD(HttpRequestPool, enablePipelining);
+PHP_METHOD(HttpRequestPool, enableEvents);
+
+PHP_MINIT_FUNCTION(http_request_pool);
+PHP_RINIT_FUNCTION(http_request_pool);
+
+#endif /* PHP_HTTP_REQUESTPOOL_H */
diff --git a/php_http_strlist.c b/php_http_strlist.c
new file mode 100644 (file)
index 0000000..a00f201
--- /dev/null
@@ -0,0 +1,80 @@
+
+#include "php_http.h"
+
+PHP_HTTP_API 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;
+}
+
+PHP_HTTP_API const char *php_http_strlist_iterator_this(php_http_strlist_iterator_t *iter, unsigned *id)
+{
+       if (id) {
+               *id = iter->major * iter->factor + iter->minor;
+       }
+
+       return iter->p;
+}
+
+PHP_HTTP_API 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;
+               }
+       }
+
+    return iter->p;
+}
+
+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)
+{
+       if (*iter) {
+               efree(*iter);
+               *iter = NULL;
+       }
+}
+
+PHP_HTTP_API const char *php_http_strlist_find(const char list[], unsigned factor, unsigned item)
+{
+       unsigned M = 0, m = 0, major = (item / factor) - 1, minor = (item % factor);
+       const char *p = &list[0];
+
+    while (*p && major != M++) {
+        while (*p) {
+            while (*p) {
+                ++p;
+            }
+            ++p;
+        }
+        ++p;
+    }
+
+    while (*p && minor != m++) {
+        while (*p) {
+            ++p;
+        }
+        ++p;
+    }
+
+    return p;
+}
diff --git a/php_http_strlist.h b/php_http_strlist.h
new file mode 100644 (file)
index 0000000..4bddbb2
--- /dev/null
@@ -0,0 +1,27 @@
+#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 */
diff --git a/php_http_url.c b/php_http_url.c
new file mode 100644 (file)
index 0000000..8675fa6
--- /dev/null
@@ -0,0 +1,578 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: http_url_api.c 292841 2009-12-31 08:48:57Z mike $ */
+
+#include "php_http.h"
+
+static inline char *localhostname(void)
+{
+       char hostname[1024] = {0};
+       
+#ifdef PHP_WIN32
+       if (SUCCESS == gethostname(hostname, lenof(hostname))) {
+               return estrdup(hostname);
+       }
+#elif defined(HAVE_GETHOSTNAME)
+       if (SUCCESS == gethostname(hostname, lenof(hostname))) {
+#      if defined(HAVE_GETDOMAINNAME)
+               size_t hlen = strlen(hostname);
+               if (hlen <= lenof(hostname) - lenof("(none)")) {
+                       hostname[hlen++] = '.';
+                       if (SUCCESS == getdomainname(&hostname[hlen], lenof(hostname) - hlen)) {
+                               if (!strcmp(&hostname[hlen], "(none)")) {
+                                       hostname[hlen - 1] = '\0';
+                               }
+                               return estrdup(hostname);
+                       }
+               }
+#      endif
+               if (strcmp(hostname, "(none)")) {
+                       return estrdup(hostname);
+               }
+       }
+#endif
+       return estrndup("localhost", lenof("localhost"));
+}
+
+PHP_HTTP_API char *php_http_url_absolute(const char *url, int flags TSRMLS_DC)
+{
+       char *abs = NULL;
+       php_url *purl = NULL;
+       
+       if (url) {
+               purl = php_url_parse(abs = estrdup(url));
+               STR_SET(abs, NULL);
+               if (!purl) {
+                       php_http_error(HE_WARNING, PHP_HTTP_E_URL, "Could not parse URL (%s)", url);
+                       return NULL;
+               }
+       }
+       
+       php_http_url(flags, purl, NULL, NULL, &abs, NULL TSRMLS_CC);
+       
+       if (purl) {
+               php_url_free(purl);
+       }
+       
+       return abs;
+}
+
+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)
+{
+#if defined(HAVE_GETSERVBYPORT) || defined(HAVE_GETSERVBYNAME)
+       struct servent *se;
+#endif
+       php_url *url = ecalloc(1, sizeof(php_url));
+
+#define __URLSET(u,n) \
+       ((u)&&(u)->n)
+#define __URLCPY(n) \
+       url->n = __URLSET(new_url,n) ? estrdup(new_url->n) : (__URLSET(old_url,n) ? estrdup(old_url->n) : NULL)
+       
+       if (!(flags & PHP_HTTP_URL_STRIP_PORT)) {
+               url->port = __URLSET(new_url, port) ? new_url->port : ((old_url) ? old_url->port : 0);
+       }
+       if (!(flags & PHP_HTTP_URL_STRIP_USER)) {
+               __URLCPY(user);
+       }
+       if (!(flags & PHP_HTTP_URL_STRIP_PASS)) {
+               __URLCPY(pass);
+       }
+       
+       __URLCPY(scheme);
+       __URLCPY(host);
+       
+       if (!(flags & PHP_HTTP_URL_STRIP_PATH)) {
+               if ((flags & PHP_HTTP_URL_JOIN_PATH) && __URLSET(old_url, path) && __URLSET(new_url, path) && *new_url->path != '/') {
+                       size_t old_path_len = strlen(old_url->path), new_path_len = strlen(new_url->path);
+                       
+                       url->path = ecalloc(1, old_path_len + new_path_len + 1 + 1);
+                       
+                       strcat(url->path, old_url->path);
+                       if (url->path[old_path_len - 1] != '/') {
+                               php_dirname(url->path, old_path_len);
+                               strcat(url->path, "/");
+                       }
+                       strcat(url->path, new_url->path);
+               } else {
+                       __URLCPY(path);
+               }
+       }
+       if (!(flags & PHP_HTTP_URL_STRIP_QUERY)) {
+               if ((flags & PHP_HTTP_URL_JOIN_QUERY) && __URLSET(new_url, query) && __URLSET(old_url, query)) {
+                       zval qarr, qstr;
+                       
+                       INIT_PZVAL(&qstr);
+                       INIT_PZVAL(&qarr);
+                       array_init(&qarr);
+                       
+                       ZVAL_STRING(&qstr, old_url->query, 0);
+                       php_http_querystring_modify(&qarr, &qstr TSRMLS_CC);
+                       ZVAL_STRING(&qstr, new_url->query, 0);
+                       php_http_querystring_modify(&qarr, &qstr TSRMLS_CC);
+                       
+                       ZVAL_NULL(&qstr);
+                       php_http_querystring_update(&qarr, &qstr TSRMLS_CC);
+                       url->query = Z_STRVAL(qstr);
+                       zval_dtor(&qarr);
+               } else {
+                       __URLCPY(query);
+               }
+       }
+       if (!(flags & PHP_HTTP_URL_STRIP_FRAGMENT)) {
+               __URLCPY(fragment);
+       }
+       
+       if (!url->scheme) {
+               if (flags & PHP_HTTP_URL_FROM_ENV) {
+                       zval *https = php_http_env_get_server_var(ZEND_STRL("HTTPS"), 1 TSRMLS_CC);
+                       if (https && !strcasecmp(Z_STRVAL_P(https), "ON")) {
+                               url->scheme = estrndup("https", lenof("https"));
+                       } else switch (url->port) {
+                               case 443:
+                                       url->scheme = estrndup("https", lenof("https"));
+                                       break;
+
+#ifndef HAVE_GETSERVBYPORT
+                               default:
+#endif
+                               case 80:
+                               case 0:
+                                       url->scheme = estrndup("http", lenof("http"));
+                                       break;
+                       
+#ifdef HAVE_GETSERVBYPORT
+                               default:
+                                       if ((se = getservbyport(htons(url->port), "tcp")) && se->s_name) {
+                                               url->scheme = estrdup(se->s_name);
+                                       } else {
+                                               url->scheme = estrndup("http", lenof("http"));
+                                       }
+                                       break;
+#endif
+                       }
+               } else {
+                       url->scheme = estrndup("http", lenof("http"));
+               }
+       }
+
+       if (!url->host) {
+               if (flags & PHP_HTTP_URL_FROM_ENV) {
+                       zval *zhost;
+                       
+                       if ((((zhost = php_http_env_get_server_var(ZEND_STRL("HTTP_HOST"), 1 TSRMLS_CC)) ||
+                                       (zhost = php_http_env_get_server_var(ZEND_STRL("SERVER_NAME"), 1 TSRMLS_CC)))) && Z_STRLEN_P(zhost)) {
+                               url->host = estrndup(Z_STRVAL_P(zhost), Z_STRLEN_P(zhost));
+                       } else {
+                               url->host = localhostname();
+                       }
+               } else {
+                       url->host = estrndup("localhost", lenof("localhost"));
+               }
+       }
+       
+       if (!url->path) {
+               if ((flags & PHP_HTTP_URL_FROM_ENV) && SG(request_info).request_uri && SG(request_info).request_uri[0]) {
+                       const char *q = strchr(SG(request_info).request_uri, '?');
+                       
+                       if (q) {
+                               url->path = estrndup(SG(request_info).request_uri, q - SG(request_info).request_uri);
+                       } else {
+                               url->path = estrdup(SG(request_info).request_uri);
+                       }
+               } else {
+                       url->path = estrndup("/", 1);
+               }
+       } else if (url->path[0] != '/') {
+               if ((flags & PHP_HTTP_URL_FROM_ENV) && SG(request_info).request_uri && SG(request_info).request_uri[0]) {
+                       size_t ulen = strlen(SG(request_info).request_uri);
+                       size_t plen = strlen(url->path);
+                       char *path;
+                       
+                       if (SG(request_info).request_uri[ulen-1] != '/') {
+                               for (--ulen; ulen && SG(request_info).request_uri[ulen - 1] != '/'; --ulen);
+                       }
+                       
+                       path = emalloc(ulen + plen + 1);
+                       memcpy(path, SG(request_info).request_uri, ulen);
+                       memcpy(path + ulen, url->path, plen);
+                       path[ulen + plen] = '\0';
+                       STR_SET(url->path, path);
+               } else {
+                       size_t plen = strlen(url->path);
+                       char *path = emalloc(plen + 1 + 1);
+                       
+                       path[0] = '/';
+                       memcpy(&path[1], url->path, plen + 1);
+                       STR_SET(url->path, path);
+               }
+       }
+       /* replace directory references if path is not a single slash */
+       if (url->path[0] && (url->path[0] != '/' || url->path[1])) {
+               char *ptr, *end = url->path + strlen(url->path) + 1;
+                       
+               for (ptr = strstr(url->path, "/."); ptr; ptr = strstr(ptr, "/.")) {
+                       switch (ptr[2]) {
+                               case '\0':
+                                       ptr[1] = '\0';
+                                       break;
+                               
+                               case '/':
+                                       memmove(&ptr[1], &ptr[3], end - &ptr[3]);
+                                       break;
+                                       
+                               case '.':
+                                       if (ptr[3] == '/') {
+                                               char *pos = &ptr[4];
+                                               while (ptr != url->path) {
+                                                       if (*--ptr == '/') {
+                                                               break;
+                                                       }
+                                               }
+                                               memmove(&ptr[1], pos, end - pos);
+                                               break;
+                                       } else if (!ptr[3]) {
+                                               /* .. at the end */
+                                               ptr[1] = '\0';
+                                       }
+                                       /* fallthrough */
+                               
+                               default:
+                                       /* something else */
+                                       ++ptr;
+                                       break;
+                       }
+               }
+       }
+       
+       if (url->port) {
+               if (    ((url->port == 80) && !strcmp(url->scheme, "http"))
+                       ||      ((url->port ==443) && !strcmp(url->scheme, "https"))
+#ifdef HAVE_GETSERVBYNAME
+                       ||      ((se = getservbyname(url->scheme, "tcp")) && se->s_port && 
+                                       (url->port == ntohs(se->s_port)))
+#endif
+               ) {
+                       url->port = 0;
+               }
+       }
+       
+       if (url_str) {
+               size_t len;
+               
+               *url_str = emalloc(PHP_HTTP_URL_MAXLEN + 1);
+               
+               **url_str = '\0';
+               strlcat(*url_str, url->scheme, PHP_HTTP_URL_MAXLEN);
+               strlcat(*url_str, "://", PHP_HTTP_URL_MAXLEN);
+               
+               if (url->user && *url->user) {
+                       strlcat(*url_str, url->user, PHP_HTTP_URL_MAXLEN);
+                       if (url->pass && *url->pass) {
+                               strlcat(*url_str, ":", PHP_HTTP_URL_MAXLEN);
+                               strlcat(*url_str, url->pass, PHP_HTTP_URL_MAXLEN);
+                       }
+                       strlcat(*url_str, "@", PHP_HTTP_URL_MAXLEN);
+               }
+               
+               strlcat(*url_str, url->host, PHP_HTTP_URL_MAXLEN);
+               
+               if (url->port) {
+                       char port_str[8];
+                       
+                       snprintf(port_str, sizeof(port_str), "%d", (int) url->port);
+                       strlcat(*url_str, ":", PHP_HTTP_URL_MAXLEN);
+                       strlcat(*url_str, port_str, PHP_HTTP_URL_MAXLEN);
+               }
+               
+               strlcat(*url_str, url->path, PHP_HTTP_URL_MAXLEN);
+               
+               if (url->query && *url->query) {
+                       strlcat(*url_str, "?", PHP_HTTP_URL_MAXLEN);
+                       strlcat(*url_str, url->query, PHP_HTTP_URL_MAXLEN);
+               }
+               
+               if (url->fragment && *url->fragment) {
+                       strlcat(*url_str, "#", PHP_HTTP_URL_MAXLEN);
+                       strlcat(*url_str, url->fragment, PHP_HTTP_URL_MAXLEN);
+               }
+               
+               if (PHP_HTTP_URL_MAXLEN == (len = strlen(*url_str))) {
+                       php_http_error(HE_NOTICE, PHP_HTTP_E_URL, "Length of URL exceeds PHP_HTTP_URL_MAXLEN");
+               }
+               if (url_len) {
+                       *url_len = len;
+               }
+       }
+       
+       if (url_ptr) {
+               *url_ptr = url;
+       } else {
+               php_url_free(url);
+       }
+}
+
+PHP_HTTP_API STATUS php_http_url_encode_hash(HashTable *hash, zend_bool override_argsep,       char *pre_encoded_data, size_t pre_encoded_len, char **encoded_data, size_t *encoded_len TSRMLS_DC)
+{
+       char *arg_sep;
+       size_t arg_sep_len;
+       php_http_buffer *qstr = php_http_buffer_new();
+
+       if (override_argsep || !(arg_sep_len = strlen(arg_sep = INI_STR("arg_separator.output")))) {
+               arg_sep = PHP_HTTP_URL_ARGSEP;
+               arg_sep_len = lenof(PHP_HTTP_URL_ARGSEP);
+       }
+
+       if (pre_encoded_len && pre_encoded_data) {
+               php_http_buffer_append(qstr, pre_encoded_data, pre_encoded_len);
+       }
+
+       if (SUCCESS != php_http_url_encode_hash_recursive(hash, qstr, arg_sep, arg_sep_len, NULL, 0)) {
+               php_http_buffer_free(&qstr);
+               return FAILURE;
+       }
+
+       php_http_buffer_data(qstr, encoded_data, encoded_len);
+       php_http_buffer_free(&qstr);
+
+       return SUCCESS;
+}
+
+PHP_HTTP_API STATUS php_http_url_encode_hash_recursive(HashTable *ht, php_http_buffer *str, const char *arg_sep, size_t arg_sep_len, const char *prefix, size_t prefix_len TSRMLS_DC)
+{
+       php_http_array_hashkey_t key = php_http_array_hashkey_init(0);
+       zval **data = NULL;
+       HashPosition pos;
+
+       if (!ht || !str) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Invalid parameters");
+               return FAILURE;
+       }
+       if (ht->nApplyCount > 0) {
+               return SUCCESS;
+       }
+       
+       FOREACH_HASH_KEYVAL(pos, ht, key, data) {
+               char *encoded_key;
+               int encoded_len;
+               php_http_buffer new_prefix;
+               
+               if (!data || !*data) {
+                       php_http_buffer_dtor(str);
+                       return FAILURE;
+               }
+               
+               if (key.type == HASH_KEY_IS_STRING) {
+                       if (!*key.str) {
+                               /* only public properties */
+                               continue;
+                       }
+                       if (key.len && key.str[key.len - 1] == '\0') {
+                               --key.len;
+                       }
+                       encoded_key = php_url_encode(key.str, key.len, &encoded_len);
+               } else {
+                       encoded_len = spprintf(&encoded_key, 0, "%ld", key.num);
+               }
+               
+               {
+                       php_http_buffer_init(&new_prefix);
+                       if (prefix && prefix_len) {
+                               php_http_buffer_append(&new_prefix, prefix, prefix_len);
+                               php_http_buffer_appends(&new_prefix, "%5B");
+                       }
+                       
+                       php_http_buffer_append(&new_prefix, encoded_key, encoded_len);
+                       efree(encoded_key);
+                       
+                       if (prefix && prefix_len) {
+                               php_http_buffer_appends(&new_prefix, "%5D");
+                       }
+                       php_http_buffer_fix(&new_prefix);
+               }
+               
+               if (Z_TYPE_PP(data) == IS_ARRAY || Z_TYPE_PP(data) == IS_OBJECT) {
+                       STATUS status;
+                       ++ht->nApplyCount;
+                       status = php_http_url_encode_hash_recursive(HASH_OF(*data), str, arg_sep, arg_sep_len, PHP_HTTP_BUFFER_VAL(&new_prefix), PHP_HTTP_BUFFER_LEN(&new_prefix) TSRMLS_CC);
+                       --ht->nApplyCount;
+                       if (SUCCESS != status) {
+                               php_http_buffer_dtor(&new_prefix);
+                               php_http_buffer_dtor(str);
+                               return FAILURE;
+                       }
+               } else {
+                       zval *val = php_http_zsep(IS_STRING, *data);
+                       
+                       if (PHP_HTTP_BUFFER_LEN(str)) {
+                               php_http_buffer_append(str, arg_sep, arg_sep_len);
+                       }
+                       php_http_buffer_append(str, PHP_HTTP_BUFFER_VAL(&new_prefix), PHP_HTTP_BUFFER_LEN(&new_prefix));
+                       php_http_buffer_appends(str, "=");
+                       
+                       if (Z_STRLEN_P(val) && Z_STRVAL_P(val)) {
+                               char *encoded_val;
+                               int encoded_len;
+                               
+                               encoded_val = php_url_encode(Z_STRVAL_P(val), Z_STRLEN_P(val), &encoded_len);
+                               php_http_buffer_append(str, encoded_val, encoded_len);
+                               efree(encoded_val);
+                       }
+                       
+                       zval_ptr_dtor(&val);
+               }
+               php_http_buffer_dtor(&new_prefix);
+       }
+       return SUCCESS;
+}
+
+#define PHP_HTTP_BEGIN_ARGS(method, req_args)  PHP_HTTP_BEGIN_ARGS_EX(HttpUrl, method, 0, req_args)
+#define PHP_HTTP_EMPTY_ARGS(method)                            PHP_HTTP_EMPTY_ARGS_EX(HttpUrl, method, 0)
+#define PHP_HTTP_URL_ME(method, visibility)    PHP_ME(HttpUrl, method, PHP_HTTP_ARGS(HttpUrl, method), visibility)
+
+PHP_HTTP_BEGIN_ARGS(__construct, 0)
+       PHP_HTTP_ARG_VAL(old_url, 0)
+       PHP_HTTP_ARG_VAL(new_url, 0)
+       PHP_HTTP_ARG_VAL(flags, 0)
+PHP_HTTP_END_ARGS;
+PHP_HTTP_EMPTY_ARGS(toString);
+
+zend_class_entry *php_http_url_class_entry;
+zend_function_entry php_http_url_method_entry[] = {
+       PHP_HTTP_URL_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR)
+       PHP_HTTP_URL_ME(toString, ZEND_ACC_PUBLIC)
+       ZEND_MALIAS(HttpUrl, __toString, toString, PHP_HTTP_ARGS(HttpUrl, toString), ZEND_ACC_PUBLIC)
+       EMPTY_FUNCTION_ENTRY
+};
+
+PHP_METHOD(HttpUrl, __construct)
+{
+       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) {
+               zval *new_url = NULL, *old_url = NULL;
+               long flags = PHP_HTTP_URL_FROM_ENV;
+
+               if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z!z!l", &old_url, &new_url, &flags)) {
+                       with_error_handling(EH_THROW, PHP_HTTP_EX_CE(url)) {
+                               php_url *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(NULL, HASH_OF(new_url) TSRMLS_CC);
+                                                       break;
+                                               default: {
+                                                       zval *cpy = php_http_zsep(IS_STRING, new_url);
+
+                                                       new_purl = php_url_parse(Z_STRVAL_P(new_url));
+                                                       zval_ptr_dtor(&cpy);
+                                                       break;
+                                               }
+                                       }
+                                       if (!new_purl) {
+                                               return;
+                                       }
+                               }
+                               if (old_url) {
+                                       switch (Z_TYPE_P(old_url)) {
+                                               case IS_OBJECT:
+                                               case IS_ARRAY:
+                                                       old_purl = php_http_url_from_struct(NULL, HASH_OF(old_url) TSRMLS_CC);
+                                                       break;
+                                               default: {
+                                                       zval *cpy = php_http_zsep(IS_STRING, old_url);
+
+                                                       old_purl = php_url_parse(Z_STRVAL_P(old_url));
+                                                       zval_ptr_dtor(&cpy);
+                                                       break;
+                                               }
+                                       }
+                                       if (!old_purl) {
+                                               if (new_purl) {
+                                                       php_url_free(new_purl);
+                                               }
+                                               return;
+                                       }
+                               }
+
+                               php_http_url(flags, old_purl, new_purl, &res_purl, NULL, NULL TSRMLS_CC);
+                               php_http_url_to_struct(res_purl, getThis() TSRMLS_CC);
+
+                               php_url_free(res_purl);
+                               if (old_purl) {
+                                       php_url_free(old_purl);
+                               }
+                               if (new_purl) {
+                                       php_url_free(new_purl);
+                               }
+                       } end_error_handling();
+               }
+       } end_error_handling();
+}
+
+PHP_METHOD(HttpUrl, toString)
+{
+       if (SUCCESS == zend_parse_parameters_none()) {
+               php_url *purl;
+
+               if ((purl = php_http_url_from_struct(NULL, HASH_OF(getThis()) TSRMLS_CC))) {
+                       char *str;
+                       size_t len;
+
+                       php_http_url(0, purl, NULL, NULL, &str, &len TSRMLS_CC);
+                       php_url_free(purl);
+                       RETURN_STRINGL(str, len, 0);
+               }
+       }
+       RETURN_EMPTY_STRING();
+}
+
+PHP_MINIT_FUNCTION(http_url)
+{
+       PHP_HTTP_REGISTER_CLASS(http, Url, http_url, php_http_object_class_entry, 0);
+
+       zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("scheme"), ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("user"), ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("pass"), ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("host"), ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("port"), ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("path"), ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("query"), ZEND_ACC_PUBLIC TSRMLS_CC);
+       zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("fragment"), ZEND_ACC_PUBLIC TSRMLS_CC);
+
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("REPLACE"), PHP_HTTP_URL_REPLACE TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_PATH"), PHP_HTTP_URL_JOIN_PATH TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_QUERY"), PHP_HTTP_URL_JOIN_QUERY TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_USER"), PHP_HTTP_URL_STRIP_USER TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PASS"), PHP_HTTP_URL_STRIP_PASS TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_AUTH"), PHP_HTTP_URL_STRIP_AUTH TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PORT"), PHP_HTTP_URL_STRIP_PORT TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PATH"), PHP_HTTP_URL_STRIP_PATH TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_QUERY"), PHP_HTTP_URL_STRIP_QUERY TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_FRAGMENT"), PHP_HTTP_URL_STRIP_FRAGMENT TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_ALL"), PHP_HTTP_URL_STRIP_ALL TSRMLS_CC);
+       zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("FROM_ENV"), PHP_HTTP_URL_FROM_ENV TSRMLS_CC);
+
+       return SUCCESS;
+}
+
+
+/*
+ * Local variables:
+ * tab-width: 4
+ * c-basic-offset: 4
+ * End:
+ * vim600: noet sw=4 ts=4 fdm=marker
+ * vim<600: noet sw=4 ts=4
+ */
+
diff --git a/php_http_url.h b/php_http_url.h
new file mode 100644 (file)
index 0000000..bbb6177
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+    +--------------------------------------------------------------------+
+    | 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-2010, Michael Wallner <mike@php.net>            |
+    +--------------------------------------------------------------------+
+*/
+
+/* $Id: php_http_url_api.h 292841 2009-12-31 08:48:57Z mike $ */
+
+#ifndef PHP_HTTP_URL_H
+#define PHP_HTTP_URL_H
+
+#define PHP_HTTP_URL_REPLACE           0x000
+#define PHP_HTTP_URL_JOIN_PATH         0x001
+#define PHP_HTTP_URL_JOIN_QUERY                0x002
+#define PHP_HTTP_URL_STRIP_USER                0x004
+#define PHP_HTTP_URL_STRIP_PASS                0x008
+#define PHP_HTTP_URL_STRIP_AUTH                (PHP_HTTP_URL_STRIP_USER|PHP_HTTP_URL_STRIP_PASS)
+#define PHP_HTTP_URL_STRIP_PORT                0x020
+#define PHP_HTTP_URL_STRIP_PATH                0x040
+#define PHP_HTTP_URL_STRIP_QUERY       0x080
+#define PHP_HTTP_URL_STRIP_FRAGMENT    0x100
+#define PHP_HTTP_URL_STRIP_ALL ( \
+       PHP_HTTP_URL_STRIP_AUTH | \
+       PHP_HTTP_URL_STRIP_PORT | \
+       PHP_HTTP_URL_STRIP_PATH | \
+       PHP_HTTP_URL_STRIP_QUERY | \
+       PHP_HTTP_URL_STRIP_FRAGMENT \
+)
+#define PHP_HTTP_URL_FROM_ENV          0x1000
+
+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);
+PHP_HTTP_API char *php_http_url_absolute(const char *url, int flags TSRMLS_DC);
+
+PHP_HTTP_API STATUS php_http_url_encode_hash(HashTable *hash, zend_bool override_argsep, char *pre_encoded_data, size_t pre_encoded_len, char **encoded_data, size_t *encoded_len TSRMLS_DC);
+PHP_HTTP_API STATUS php_http_url_encode_hash_recursive(HashTable *ht, php_http_buffer *str, const char *arg_sep, size_t arg_sep_len, const char *prefix, size_t prefix_len TSRMLS_DC);
+
+static inline php_url *php_http_url_from_struct(php_url *url, HashTable *ht TSRMLS_DC)
+{
+       zval **e;
+       
+       if (!url) {
+               url = emalloc(sizeof(*url));
+       }
+       memset(url, 0, sizeof(*url));
+       
+       if (SUCCESS == zend_hash_find(ht, "scheme", sizeof("scheme"), (void *) &e)) {
+               zval *cpy = php_http_zsep(IS_STRING, *e);
+               url->scheme = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy));
+               zval_ptr_dtor(&cpy);
+       }
+       if (SUCCESS == zend_hash_find(ht, "user", sizeof("user"), (void *) &e)) {
+               zval *cpy = php_http_zsep(IS_STRING, *e);
+               url->user = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy));
+               zval_ptr_dtor(&cpy);
+       }
+       if (SUCCESS == zend_hash_find(ht, "pass", sizeof("pass"), (void *) &e)) {
+               zval *cpy = php_http_zsep(IS_STRING, *e);
+               url->pass = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy));
+               zval_ptr_dtor(&cpy);
+       }
+       if (SUCCESS == zend_hash_find(ht, "host", sizeof("host"), (void *) &e)) {
+               zval *cpy = php_http_zsep(IS_STRING, *e);
+               url->host = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy));
+               zval_ptr_dtor(&cpy);
+       }
+       if (SUCCESS == zend_hash_find(ht, "path", sizeof("path"), (void *) &e)) {
+               zval *cpy = php_http_zsep(IS_STRING, *e);
+               url->path = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy));
+               zval_ptr_dtor(&cpy);
+       }
+       if (SUCCESS == zend_hash_find(ht, "query", sizeof("query"), (void *) &e)) {
+               zval *cpy = php_http_zsep(IS_STRING, *e);
+               url->query = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy));
+               zval_ptr_dtor(&cpy);
+       }
+       if (SUCCESS == zend_hash_find(ht, "fragment", sizeof("fragment"), (void *) &e)) {
+               zval *cpy = php_http_zsep(IS_STRING, *e);
+               url->fragment = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy));
+               zval_ptr_dtor(&cpy);
+       }
+       if (SUCCESS == zend_hash_find(ht, "port", sizeof("port"), (void *) &e)) {
+               zval *cpy = php_http_zsep(IS_LONG, *e);
+               url->port = (unsigned short) Z_LVAL_P(cpy);
+               zval_ptr_dtor(&cpy);
+       }
+       
+       return url;
+}
+
+static inline HashTable *php_http_url_to_struct(php_url *url, zval *strct TSRMLS_DC)
+{
+       zval arr;
+       
+       if (strct) {
+               switch (Z_TYPE_P(strct)) {
+                       default:
+                               zval_dtor(strct);
+                               array_init(strct);
+                       case IS_ARRAY:
+                       case IS_OBJECT:
+                               INIT_PZVAL_ARRAY((&arr), HASH_OF(strct));
+               }
+       } else {
+               INIT_PZVAL(&arr);
+               array_init(&arr);
+       }
+       
+       if (url) {
+               if (url->scheme) {
+                       add_assoc_string(&arr, "scheme", url->scheme, 1);
+               }
+               if (url->user) {
+                       add_assoc_string(&arr, "user", url->user, 1);
+               }
+               if (url->pass) {
+                       add_assoc_string(&arr, "pass", url->pass, 1);
+               }
+               if (url->host) {
+                       add_assoc_string(&arr, "host", url->host, 1);
+               }
+               if (url->port) {
+                       add_assoc_long(&arr, "port", (long) url->port);
+               }
+               if (url->path) {
+                       add_assoc_string(&arr, "path", url->path, 1);
+               }
+               if (url->query) {
+                       add_assoc_string(&arr, "query", url->query, 1);
+               }
+               if (url->fragment) {
+                       add_assoc_string(&arr, "fragment", url->fragment, 1);
+               }
+       }
+       
+       return Z_ARRVAL(arr);
+}
+
+extern zend_class_entry *php_http_url_class_entry;
+extern zend_function_entry php_http_url_method_entry[];
+
+#define php_http_url_object_new php_http_object_new
+#define php_http_url_object_new_ex php_http_object_new_ex
+
+PHP_METHOD(HttpUrl, __construct);
+PHP_METHOD(HttpUrl, toString);
+
+extern PHP_MINIT_FUNCTION(http_url);
+
+#endif
+
+/*
+ * 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_version.c b/php_http_version.c
new file mode 100644 (file)
index 0000000..8c1637c
--- /dev/null
@@ -0,0 +1,59 @@
+#include "php_http.h"
+
+PHP_HTTP_API php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major, unsigned minor TSRMLS_DC)
+{
+       if (!v) {
+               v = emalloc(sizeof(*v));
+       }
+
+       v->major = major;
+       v->minor = minor;
+
+       return v;
+}
+
+PHP_HTTP_API php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str TSRMLS_DC)
+{
+       php_http_version_t tmp;
+       char separator = 0;
+
+       if (3 != sscanf(str, "HTTP/%u%c%u", &tmp.major, &separator, &tmp.minor)
+       &&      3 != sscanf(str, "%u%c%u", &tmp.major, &separator, &tmp.minor)) {
+               php_http_error(HE_WARNING, PHP_HTTP_E_MALFORMED_HEADERS, "Could not parse HTTP protocol version '%s'", str);
+               return NULL;
+       }
+
+       if (separator && separator != '.' && separator != ',') {
+               php_http_error(HE_NOTICE, PHP_HTTP_E_MALFORMED_HEADERS, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, str);
+       }
+
+       return php_http_version_init(v, tmp.major, tmp.minor TSRMLS_CC);
+}
+
+PHP_HTTP_API void php_http_version_to_string(php_http_version_t *v, char **str, size_t *len, const char *pre, const char *post TSRMLS_DC)
+{
+       *len = spprintf(str, 0, "%s%u.%u%s", pre ? pre : "", v->major, v->minor, post ? post : "");
+}
+
+PHP_HTTP_API void php_http_version_to_struct(php_http_version_t *v, HashTable *strct TSRMLS_DC)
+{
+       zval tmp;
+
+       INIT_PZVAL_ARRAY(&tmp, strct);
+       add_assoc_long(&tmp, "major", v->major);
+       add_assoc_long(&tmp, "minor", v->minor);
+}
+
+PHP_HTTP_API void php_http_version_dtor(php_http_version_t *v)
+{
+       (void) v;
+}
+
+PHP_HTTP_API void php_http_version_free(php_http_version_t **v)
+{
+       if (*v) {
+               php_http_version_dtor(*v);
+               efree(*v);
+               *v = NULL;
+       }
+}
diff --git a/php_http_version.h b/php_http_version.h
new file mode 100644 (file)
index 0000000..6f02ad7
--- /dev/null
@@ -0,0 +1,18 @@
+
+#ifndef PHP_HTTP_VERSION_H
+#define        PHP_HTTP_VERSION_H
+
+typedef struct php_http_version {
+       unsigned major;
+       unsigned minor;
+} php_http_version_t;
+
+PHP_HTTP_API php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major, unsigned minor TSRMLS_DC);
+PHP_HTTP_API php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str TSRMLS_DC);
+PHP_HTTP_API void php_http_version_to_string(php_http_version_t *v, char **str, size_t *len, const char *pre, const char *post TSRMLS_DC);
+PHP_HTTP_API void php_http_version_to_struct(php_http_version_t *v, HashTable *strct TSRMLS_DC);
+PHP_HTTP_API void php_http_version_dtor(php_http_version_t *v);
+PHP_HTTP_API void php_http_version_free(php_http_version_t **v);
+
+#endif /* PHP_HTTP_VERSION_H */
+