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);
+              &