From d3485e3b28336153dca690e872ffe1ddc60fedd2 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Tue, 7 Sep 2010 15:16:54 +0000 Subject: [PATCH 1/1] import 2.0 devl branch, suitable for PHP-trunk --- CREDITS | 2 + KnownIssues.txt | 29 + LICENSE | 23 + ThanksTo.txt | 20 + config.m4 | 5 + config9.m4 | 423 ++++++ php_http.c | 262 ++++ php_http.h | 174 +++ php_http_buffer.c | 424 ++++++ php_http_buffer.h | 229 ++++ php_http_cookie.c | 890 +++++++++++++ php_http_cookie.h | 104 ++ php_http_encoding.c | 1197 +++++++++++++++++ php_http_encoding.h | 238 ++++ php_http_env.c | 1704 ++++++++++++++++++++++++ php_http_env.h | 141 ++ php_http_etag.c | 84 ++ php_http_etag.h | 24 + php_http_exception.c | 123 ++ php_http_exception.h | 56 + php_http_filter.c | 437 +++++++ php_http_filter.h | 31 + php_http_header_parser.c | 225 ++++ php_http_header_parser.h | 39 + php_http_headers.c | 39 + php_http_headers.h | 32 + php_http_info.c | 178 +++ php_http_info.h | 77 ++ php_http_message.c | 1954 ++++++++++++++++++++++++++++ php_http_message.h | 147 +++ php_http_message_body.c | 687 ++++++++++ php_http_message_body.h | 81 ++ php_http_message_parser.c | 363 ++++++ php_http_message_parser.h | 40 + php_http_misc.c | 238 ++++ php_http_misc.h | 467 +++++++ php_http_negotiate.c | 180 +++ php_http_negotiate.h | 88 ++ php_http_object.c | 220 ++++ php_http_object.h | 53 + php_http_params.c | 277 ++++ php_http_params.h | 39 + php_http_persistent_handle.c | 385 ++++++ php_http_persistent_handle.h | 50 + php_http_property_proxy.c | 189 +++ php_http_property_proxy.h | 31 + php_http_querystring.c | 684 ++++++++++ php_http_querystring.h | 82 ++ php_http_request.c | 2362 ++++++++++++++++++++++++++++++++++ php_http_request.h | 189 +++ php_http_request_datashare.c | 468 +++++++ php_http_request_datashare.h | 71 + php_http_request_info.c | 191 +++ php_http_request_method.c | 229 ++++ php_http_request_method.h | 81 ++ php_http_request_pool.c | 958 ++++++++++++++ php_http_request_pool.h | 83 ++ php_http_strlist.c | 80 ++ php_http_strlist.h | 27 + php_http_url.c | 578 +++++++++ php_http_url.h | 165 +++ php_http_version.c | 59 + php_http_version.h | 18 + 63 files changed, 19024 insertions(+) create mode 100644 CREDITS create mode 100644 KnownIssues.txt create mode 100644 LICENSE create mode 100644 ThanksTo.txt create mode 100644 config.m4 create mode 100644 config9.m4 create mode 100644 php_http.c create mode 100644 php_http.h create mode 100644 php_http_buffer.c create mode 100644 php_http_buffer.h create mode 100644 php_http_cookie.c create mode 100644 php_http_cookie.h create mode 100644 php_http_encoding.c create mode 100644 php_http_encoding.h create mode 100644 php_http_env.c create mode 100644 php_http_env.h create mode 100644 php_http_etag.c create mode 100644 php_http_etag.h create mode 100644 php_http_exception.c create mode 100644 php_http_exception.h create mode 100644 php_http_filter.c create mode 100644 php_http_filter.h create mode 100644 php_http_header_parser.c create mode 100644 php_http_header_parser.h create mode 100644 php_http_headers.c create mode 100644 php_http_headers.h create mode 100644 php_http_info.c create mode 100644 php_http_info.h create mode 100644 php_http_message.c create mode 100644 php_http_message.h create mode 100644 php_http_message_body.c create mode 100644 php_http_message_body.h create mode 100644 php_http_message_parser.c create mode 100644 php_http_message_parser.h create mode 100644 php_http_misc.c create mode 100644 php_http_misc.h create mode 100644 php_http_negotiate.c create mode 100644 php_http_negotiate.h create mode 100644 php_http_object.c create mode 100644 php_http_object.h create mode 100644 php_http_params.c create mode 100644 php_http_params.h create mode 100644 php_http_persistent_handle.c create mode 100644 php_http_persistent_handle.h create mode 100644 php_http_property_proxy.c create mode 100644 php_http_property_proxy.h create mode 100644 php_http_querystring.c create mode 100644 php_http_querystring.h create mode 100644 php_http_request.c create mode 100644 php_http_request.h create mode 100644 php_http_request_datashare.c create mode 100644 php_http_request_datashare.h create mode 100644 php_http_request_info.c create mode 100644 php_http_request_method.c create mode 100644 php_http_request_method.h create mode 100644 php_http_request_pool.c create mode 100644 php_http_request_pool.h create mode 100644 php_http_strlist.c create mode 100644 php_http_strlist.h create mode 100644 php_http_url.c create mode 100644 php_http_url.h create mode 100644 php_http_version.c create mode 100644 php_http_version.h diff --git a/CREDITS b/CREDITS new file mode 100644 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 index 0000000..94a0a94 --- /dev/null +++ b/KnownIssues.txt @@ -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 index 0000000..b3f886e --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2004-2010, Michael Wallner . +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 index 0000000..8dbd1d8 --- /dev/null +++ b/ThanksTo.txt @@ -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 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 index 0000000..001d4f4 --- /dev/null +++ b/config9.m4 @@ -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 + 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 + 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 index 0000000..273ef7b --- /dev/null +++ b/php_http.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http.c 300300 2010-06-09 07:29:35Z mike $ */ + +#include "php_http.h" + +#include
+#include +#include + +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 index 0000000..67dcc35 --- /dev/null +++ b/php_http.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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
+#include
+#include
+#include
+#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +#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 +#elif defined(HAVE_NETDB_H) +# define PHP_HTTP_HAVE_NETDB +# include +# ifdef HAVE_UNISTD_H +# include +# endif +#endif + +#ifdef PHP_HTTP_HAVE_EVENT +# include +#endif + +#include +#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 +# else /* !PHP_WIN32 */ +# if defined(PHP_HTTP_HAVE_OPENSSL) +# define PHP_HTTP_NEED_OPENSSL_TSL +# include +# elif defined(PHP_HTTP_HAVE_GNUTLS) +# define PHP_HTTP_NEED_GNUTLS_TSL +# include +# 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 +#include +#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 index 0000000..e65a8f8 --- /dev/null +++ b/php_http_buffer.c @@ -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 index 0000000..57729bb --- /dev/null +++ b/php_http_buffer.h @@ -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 index 0000000..1ac08c2 --- /dev/null +++ b/php_http_cookie.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..1e2b0f7 --- /dev/null +++ b/php_http_cookie.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..34c6922 --- /dev/null +++ b/php_http_encoding.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 */ + 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 index 0000000..9e700a7 --- /dev/null +++ b/php_http_encoding.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..59442fc --- /dev/null +++ b/php_http_env.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..0d73aca --- /dev/null +++ b/php_http_env.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..1f11785 --- /dev/null +++ b/php_http_etag.c @@ -0,0 +1,84 @@ +#include "php_http.h" + +#include +#include +#include + +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 index 0000000..25b1426 --- /dev/null +++ b/php_http_etag.h @@ -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 index 0000000..9c55263 --- /dev/null +++ b/php_http_exception.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..1881190 --- /dev/null +++ b/php_http_exception.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..95b3cbf --- /dev/null +++ b/php_http_filter.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 */ + 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 = ¶ms; + 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 index 0000000..c8cb37d --- /dev/null +++ b/php_http_filter.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..a70d853 --- /dev/null +++ b/php_http_header_parser.c @@ -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 index 0000000..9d24be1 --- /dev/null +++ b/php_http_header_parser.h @@ -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 index 0000000..b5a700e --- /dev/null +++ b/php_http_headers.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..8cc0b8a --- /dev/null +++ b/php_http_headers.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..534eb2c --- /dev/null +++ b/php_http_info.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..2e47d19 --- /dev/null +++ b/php_http_info.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $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 index 0000000..5f9220e --- /dev/null +++ b/php_http_message.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_message_api.c 298689 2010-04-28 06:50:06Z mike $ */ + +#include "php_http.h" + +/* API */ + +PHP_HTTP_API zend_bool php_http_message_info_callback(php_http_message_t **message, HashTable **headers, php_http_info_t *info TSRMLS_DC) +{ + php_http_message_t *old = *message; + + /* advance message */ + if (old->type || zend_hash_num_elements(&old->hdrs) || PHP_HTTP_BUFFER_LEN(old)) { + (*message) = php_http_message_init(NULL, 0); + (*message)->parent = old; + (*headers) = &((*message)->hdrs); + } + + php_http_message_set_info(*message, info); + + return old != *message; +} + +PHP_HTTP_API php_http_message_t *php_http_message_init(php_http_message_t *message, php_http_message_type_t type TSRMLS_DC) +{ + if (!message) { + message = emalloc(sizeof(*message)); + } + memset(message, 0, sizeof(*message)); + TSRMLS_SET_CTX(message->ts); + + php_http_message_set_type(message, type); + zend_hash_init(&message->hdrs, 0, NULL, ZVAL_PTR_DTOR, 0); + php_http_message_body_init(&message->body, NULL TSRMLS_CC); + + return message; +} + +PHP_HTTP_API php_http_message_t *php_http_message_init_env(php_http_message_t *message, php_http_message_type_t type TSRMLS_DC) +{ + int free_msg = !message; + zval *sval, tval; + php_http_message_body_t *mbody; + + message = php_http_message_init(message, type TSRMLS_CC); + + switch (type) { + case PHP_HTTP_REQUEST: + if ((sval = php_http_env_get_server_var(ZEND_STRL("SERVER_PROTOCOL"), 1 TSRMLS_CC)) && !strncmp(Z_STRVAL_P(sval), "HTTP/", lenof("HTTP/"))) { + php_http_version_parse(&message->http.version, Z_STRVAL_P(sval)); + } else { + message->http.version.major = 1; + message->http.version.minor = 1; + } + if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_METHOD"), 1 TSRMLS_CC))) { + message->http.info.request.method = estrdup(Z_STRVAL_P(sval)); + } + if ((sval = php_http_env_get_server_var(ZEND_STRL("REQUEST_URI"), 1 TSRMLS_CC))) { + message->http.info.request.url = estrdup(Z_STRVAL_P(sval)); + } + + php_http_env_get_request_headers(&message->hdrs TSRMLS_CC); + + if ((mbody = php_http_env_get_request_body(TSRMLS_C))) { + php_http_message_body_dtor(&message->body); + php_http_message_body_copy(mbody, &message->body, 0 TSRMLS_CC); + } + break; + + case PHP_HTTP_RESPONSE: + if (!SG(sapi_headers).http_status_line || !php_http_info_parse((php_http_info_t *) &message->http, SG(sapi_headers).http_status_line TSRMLS_CC)) { + message->http.version.major = 1; + message->http.version.minor = 1; + switch ((message->http.info.response.code = SG(sapi_headers).http_response_code)) { + case 0: + message->http.info.response.code = 200; + case 200: + message->http.info.response.status = estrdup("Ok"); + break; + default: + message->http.info.response.status = estrdup(""); + break; + } + + } + + php_http_env_get_response_headers(&message->hdrs TSRMLS_CC); + + if (php_output_get_level()) { + if (php_output_get_status(TSRMLS_C) & PHP_OUTPUT_SENT) { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "Could not fetch response body, output has already been sent at %s:%d", php_output_get_start_filename(TSRMLS_C), php_output_get_start_lineno(TSRMLS_C)); + goto error; + } else if (SUCCESS != php_output_get_contents(&tval TSRMLS_CC)) { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "Could not fetch response body"); + goto error; + } else { + php_http_message_body_append(&message->body, Z_STRVAL(tval), Z_STRLEN(tval)); + zval_dtor(&tval); + } + } + break; + + default: + error: + if (free_msg) { + php_http_message_free(&message); + } else { + message = NULL; + } + break; + } + + return message; +} + +PHP_HTTP_API php_http_message_t *php_http_message_parse(php_http_message_t *msg, const char *str, size_t len TSRMLS_DC) +{ + php_http_message_parser_t p; + php_http_buffer buf; + + if (!msg) { + msg = php_http_message_init(NULL, 0 TSRMLS_CC); + } + php_http_buffer_from_string_ex(&buf, str, len); + php_http_message_parser_init(&p TSRMLS_CC); + php_http_message_parser_parse(&p, &buf, PHP_HTTP_MESSAGE_PARSER_CLEANUP, &msg); + php_http_message_parser_dtor(&p); + php_http_buffer_dtor(&buf); + + /* FIXME */ + return msg; +} + +PHP_HTTP_API zval *php_http_message_header(php_http_message_t *msg, char *key_str, size_t key_len, int join) +{ + zval *ret = NULL, **header; + char *key = php_http_pretty_key(estrndup(key_str, key_len), key_len, 1, 1); + + if (SUCCESS == zend_hash_find(&msg->hdrs, key, key_len + 1, (void *) &header)) { + if (join && Z_TYPE_PP(header) == IS_ARRAY) { + zval *header_str, **val; + HashPosition pos; + php_http_buffer str; + + php_http_buffer_init(&str); + MAKE_STD_ZVAL(header_str); + FOREACH_VAL(pos, *header, val) { + php_http_buffer_appendf(&str, PHP_HTTP_BUFFER_LEN(&str) ? ", %s":"%s", Z_STRVAL_PP(val)); + } + php_http_buffer_fix(&str); + ZVAL_STRINGL(header_str, PHP_HTTP_BUFFER_VAL(&str), PHP_HTTP_BUFFER_LEN(&str), 0); + ret = header_str; + } else { + ret = php_http_zsep(IS_STRING, *header); + } + } + + efree(key); + + return ret; +} + + +/* */ +PHP_HTTP_API void php_http_message_set_type(php_http_message_t *message, php_http_message_type_t type) +{ + /* just act if different */ + if (type != message->type) { + + /* free request info */ + switch (message->type = type) { + case PHP_HTTP_REQUEST: + STR_FREE(message->http.info.request.method); + STR_FREE(message->http.info.request.url); + break; + + case PHP_HTTP_RESPONSE: + STR_FREE(message->http.info.response.status); + break; + + default: + break; + } + + memset(&message->http, 0, sizeof(message->http)); + } +} + +PHP_HTTP_API void php_http_message_set_info(php_http_message_t *message, php_http_info_t *info) +{ + php_http_message_set_type(message, info->type); + message->http.version = info->http.version; + switch (message->type) { + case PHP_HTTP_REQUEST: + STR_SET(PHP_HTTP_INFO(message).request.url, PHP_HTTP_INFO(info).request.url ? estrdup(PHP_HTTP_INFO(info).request.url) : NULL); + STR_SET(PHP_HTTP_INFO(message).request.method, PHP_HTTP_INFO(info).request.method ? estrdup(PHP_HTTP_INFO(info).request.method) : NULL); + break; + + case PHP_HTTP_RESPONSE: + PHP_HTTP_INFO(message).response.code = PHP_HTTP_INFO(info).response.code; + STR_SET(PHP_HTTP_INFO(message).response.status, PHP_HTTP_INFO(info).response.status ? estrdup(PHP_HTTP_INFO(info).response.status) : NULL); + break; + + default: + break; + } +} + +static inline void message_headers(php_http_message_t *msg, php_http_buffer *str) +{ + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + HashPosition pos1; + zval **header; + + switch (msg->type) { + case PHP_HTTP_REQUEST: + php_http_buffer_appendf(str, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&msg->http, PHP_HTTP_CRLF)); + break; + + case PHP_HTTP_RESPONSE: + php_http_buffer_appendf(str, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&msg->http, PHP_HTTP_CRLF)); + break; + + default: + break; + } + + FOREACH_HASH_KEYVAL(pos1, &msg->hdrs, key, header) { + if (key.type == HASH_KEY_IS_STRING) { + HashPosition pos2; + zval **single_header; + + switch (Z_TYPE_PP(header)) { + case IS_BOOL: + php_http_buffer_appendf(str, "%s: %s" PHP_HTTP_CRLF, key.str, Z_BVAL_PP(header)?"true":"false"); + break; + + case IS_LONG: + php_http_buffer_appendf(str, "%s: %ld" PHP_HTTP_CRLF, key.str, Z_LVAL_PP(header)); + break; + + case IS_DOUBLE: + php_http_buffer_appendf(str, "%s: %F" PHP_HTTP_CRLF, key.str, Z_DVAL_PP(header)); + break; + + case IS_STRING: + if (Z_STRVAL_PP(header)[Z_STRLEN_PP(header)-1] == '\r') fprintf(stderr, "DOH!\n"); + php_http_buffer_appendf(str, "%s: %s" PHP_HTTP_CRLF, key.str, Z_STRVAL_PP(header)); + break; + + case IS_ARRAY: + FOREACH_VAL(pos2, *header, single_header) { + switch (Z_TYPE_PP(single_header)) { + case IS_BOOL: + php_http_buffer_appendf(str, "%s: %s" PHP_HTTP_CRLF, key.str, Z_BVAL_PP(single_header)?"true":"false"); + break; + + case IS_LONG: + php_http_buffer_appendf(str, "%s: %ld" PHP_HTTP_CRLF, key.str, Z_LVAL_PP(single_header)); + break; + + case IS_DOUBLE: + php_http_buffer_appendf(str, "%s: %F" PHP_HTTP_CRLF, key.str, Z_DVAL_PP(single_header)); + break; + + case IS_STRING: + php_http_buffer_appendf(str, "%s: %s" PHP_HTTP_CRLF, key.str, Z_STRVAL_PP(single_header)); + break; + } + } + break; + } + } + } +} + +PHP_HTTP_API void php_http_message_to_callback(php_http_message_t *msg, php_http_pass_callback_t cb, void *cb_arg) +{ + php_http_buffer str; + TSRMLS_FETCH_FROM_CTX(msg->ts); + + php_http_buffer_init_ex(&str, 0x1000, 0); + message_headers(msg, &str); + cb(cb_arg, PHP_HTTP_BUFFER_VAL(&str), PHP_HTTP_BUFFER_LEN(&str)); + php_http_buffer_dtor(&str); + + if (php_http_message_body_size(&msg->body)) { + cb(cb_arg, ZEND_STRL(PHP_HTTP_CRLF) TSRMLS_CC); + php_http_message_body_to_callback(&msg->body, cb, cb_arg, 0, 0); + cb(cb_arg, ZEND_STRL(PHP_HTTP_CRLF) TSRMLS_CC); + } +} + +PHP_HTTP_API void php_http_message_to_string(php_http_message_t *msg, char **string, size_t *length) +{ + php_http_buffer str; + char *data; + + php_http_buffer_init_ex(&str, 0x1000, 0); + message_headers(msg, &str); + if (php_http_message_body_size(&msg->body)) { + php_http_buffer_appends(&str, PHP_HTTP_CRLF); + php_http_message_body_to_callback(&msg->body, (php_http_pass_callback_t) php_http_buffer_append, &str, 0, 0); + php_http_buffer_appends(&str, PHP_HTTP_CRLF); + } + + data = php_http_buffer_data(&str, string, length); + if (!string) { + efree(data); + } + + php_http_buffer_dtor(&str); +} + +PHP_HTTP_API void php_http_message_serialize(php_http_message_t *message, char **string, size_t *length) +{ + char *buf; + size_t len; + php_http_buffer str; + + php_http_buffer_init(&str); + + do { + php_http_message_to_string(message, &buf, &len); + php_http_buffer_prepend(&str, buf, len); + efree(buf); + } while ((message = message->parent)); + + buf = php_http_buffer_data(&str, string, length); + if (!string) { + efree(buf); + } + + php_http_buffer_dtor(&str); +} + +PHP_HTTP_API php_http_message_t *php_http_message_reverse(php_http_message_t *msg) +{ + int i, c = 0; + + php_http_message_count(c, msg); + + if (c > 1) { + php_http_message_t *tmp = msg, **arr = ecalloc(c, sizeof(**arr)); + + for (i = 0; i < c; ++i) { + arr[i] = tmp; + tmp = tmp->parent; + } + arr[0]->parent = NULL; + for (i = 0; i < c-1; ++i) { + arr[i+1]->parent = arr[i]; + } + + msg = arr[c-1]; + efree(arr); + } + + return msg; +} + +PHP_HTTP_API php_http_message_t *php_http_message_interconnect(php_http_message_t *m1, php_http_message_t *m2) +{ + if (m1 && m2) { + int i = 0, c1 = 0, c2 = 0; + php_http_message_t *t1 = m1, *t2 = m2, *p1, *p2; + + php_http_message_count(c1, m1); + php_http_message_count(c2, m2); + + while (i++ < (c1 - c2)) { + t1 = t1->parent; + } + while (i++ <= c1) { + p1 = t1->parent; + p2 = t2->parent; + t1->parent = t2; + t2->parent = p1; + t1 = p1; + t2 = p2; + } + } else if (!m1 && m2) { + m1 = m2; + } + return m1; +} + +PHP_HTTP_API void php_http_message_to_struct(php_http_message_t *msg, zval *obj TSRMLS_DC) +{ + zval strct; + zval *headers; + char *version; + + INIT_PZVAL_ARRAY(&strct, HASH_OF(obj)); + + add_assoc_long(&strct, "type", msg->type); + spprintf(&version, 0 ,"%u.%u", msg->http.version.major, msg->http.version.minor); + add_assoc_string_ex(&strct, ZEND_STRL("httpVersion"), version, 0); + switch (msg->type) + { + case PHP_HTTP_RESPONSE: + add_assoc_long(&strct, "responseCode", msg->http.info.response.code); + add_assoc_string(&strct, "responseStatus", STR_PTR(msg->http.info.response.status), 1); + break; + + case PHP_HTTP_REQUEST: + add_assoc_string(&strct, "requestMethod", STR_PTR(msg->http.info.request.method), 1); + add_assoc_string(&strct, "requestUrl", STR_PTR(msg->http.info.request.url), 1); + break; + + case PHP_HTTP_NONE: + /* avoid compiler warning */ + break; + } + + MAKE_STD_ZVAL(headers); + array_init(headers); + zend_hash_copy(Z_ARRVAL_P(headers), &msg->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); + add_assoc_zval(&strct, "headers", headers); + + add_assoc_stringl(&strct, "body", PHP_HTTP_BUFFER_VAL(msg), PHP_HTTP_BUFFER_LEN(msg), 1); + + if (msg->parent) { + zval *parent; + + MAKE_STD_ZVAL(parent); + if (Z_TYPE_P(obj) == IS_ARRAY) { + array_init(parent); + } else { + object_init(parent); + } + add_assoc_zval(&strct, "parentMessage", parent); + php_http_message_to_struct(msg->parent, parent); + } else { + add_assoc_null(&strct, "parentMessage"); + } +} +/* +PHP_HTTP_API STATUS _http_message_send(http_message *message TSRMLS_DC) +{ + STATUS rs = FAILURE; + + switch (message->type) { + case PHP_HTTP_RESPONSE: + { + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + zval **val; + HashPosition pos; + + FOREACH_HASH_KEYVAL(pos, &message->hdrs, key, val) { + if (key.type == HASH_KEY_IS_STRING) { + http_send_header_zval_ex(key.str, key.len-1, val, 1); + } + } + rs = SUCCESS == http_send_status(message->http.info.response.code) && + SUCCESS == http_send_data(PHP_HTTP_BUFFER_VAL(message), PHP_HTTP_BUFFER_LEN(message)) ? + SUCCESS : FAILURE; + break; + } + + case PHP_HTTP_REQUEST: + { +#ifdef PHP_HTTP_HAVE_CURL + char *uri = NULL; + http_request request; + zval **zhost, *options, *headers; + + MAKE_STD_ZVAL(options); + MAKE_STD_ZVAL(headers); + array_init(options); + array_init(headers); + zend_hash_copy(Z_ARRVAL_P(headers), &message->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); + add_assoc_zval(options, "headers", headers); + + if (SUCCESS == zend_hash_find(&message->hdrs, "Host", sizeof("Host"), (void *) &zhost) && Z_TYPE_PP(zhost) == IS_STRING) { + char *colon = NULL; + php_url parts, *url = php_url_parse(message->http.info.request.url); + + memset(&parts, 0, sizeof(php_url)); + + if ((colon = strchr(Z_STRVAL_PP(zhost), ':'))) { + parts.port = atoi(colon + 1); + parts.host = estrndup(Z_STRVAL_PP(zhost), (Z_STRVAL_PP(zhost) - colon - 1)); + } else { + parts.host = estrndup(Z_STRVAL_PP(zhost), Z_STRLEN_PP(zhost)); + } + + http_build_url(PHP_HTTP_URL_REPLACE, url, &parts, NULL, &uri, NULL); + php_url_free(url); + efree(parts.host); + } else { + uri = http_absolute_url(message->http.info.request.url); + } + + if ((request.meth = http_request_method_exists(1, 0, message->http.info.request.method))) { + http_request_body body; + + http_request_init_ex(&request, NULL, request.meth, uri); + request.body = http_request_body_init_ex(&body, PHP_HTTP_REQUEST_BODY_CSTRING, PHP_HTTP_BUFFER_VAL(message), PHP_HTTP_BUFFER_LEN(message), 0); + if (SUCCESS == (rs = http_request_prepare(&request, Z_ARRVAL_P(options)))) { + http_request_exec(&request); + } + http_request_dtor(&request); + } else { + http_error_ex(HE_WARNING, PHP_HTTP_E_REQUEST_METHOD, + "Cannot send HttpMessage. Request method %s not supported", + message->http.info.request.method); + } + efree(uri); + zval_ptr_dtor(&options); +#else + http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "HTTP requests not supported - ext/http was not linked against libcurl."); +#endif + break; + } + + case PHP_HTTP_NONE: + default: + php_http_error(HE_WARNING, PHP_HTTP_E_MESSAGE_TYPE, "HttpMessage is neither of type PHP_HTTP_REQUEST nor PHP_HTTP_RESPONSE"); + break; + } + + return rs; +} +*/ + +PHP_HTTP_API php_http_message_t *php_http_message_copy(php_http_message_t *from, php_http_message_t *to) +{ + php_http_message_t *temp, *copy = NULL; + php_http_info_t info; + TSRMLS_FETCH_FROM_CTX(from->ts); + + if (from) { + info.type = from->type; + info.http = from->http; + + copy = temp = php_http_message_init(to, 0); + php_http_message_set_info(temp, &info); + zend_hash_copy(&temp->hdrs, &from->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); + php_http_message_body_copy(&from->body, &temp->body, 1); + + while (from->parent) { + info.type = from->parent->type; + info.http = from->parent->http; + + temp->parent = php_http_message_init(NULL, 0); + php_http_message_set_info(temp->parent, &info); + zend_hash_copy(&temp->parent->hdrs, &from->parent->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); + php_http_message_body_copy(&from->body, &temp->body, 1); + + temp = temp->parent; + from = from->parent; + } + } + + return copy; +} + +PHP_HTTP_API void php_http_message_dtor(php_http_message_t *message) +{ + if (message) { + zend_hash_destroy(&message->hdrs); + php_http_message_body_dtor(&message->body); + + switch (message->type) { + case PHP_HTTP_REQUEST: + STR_SET(message->http.info.request.method, NULL); + STR_SET(message->http.info.request.url, NULL); + break; + + case PHP_HTTP_RESPONSE: + STR_SET(message->http.info.response.status, NULL); + break; + + default: + break; + } + } +} + +PHP_HTTP_API void php_http_message_free(php_http_message_t **message) +{ + if (*message) { + if ((*message)->parent) { + php_http_message_free(&(*message)->parent); + } + php_http_message_dtor(*message); + efree(*message); + *message = NULL; + } +} + + +/* PHP */ + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpMessage, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpMessage, method, 0) +#define PHP_HTTP_MESSAGE_ME(method, visibility) PHP_ME(HttpMessage, method, PHP_HTTP_ARGS(HttpMessage, method), visibility) + +PHP_HTTP_BEGIN_ARGS(__construct, 0) + PHP_HTTP_ARG_VAL(message, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getBody); +PHP_HTTP_BEGIN_ARGS(setBody, 1) + PHP_HTTP_ARG_VAL(body, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(getHeader, 1) + PHP_HTTP_ARG_VAL(header, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(setHeader, 1) + PHP_HTTP_ARG_VAL(header, 0) + PHP_HTTP_ARG_VAL(value, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(addHeader, 2) + PHP_HTTP_ARG_VAL(header, 0) + PHP_HTTP_ARG_VAL(value, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getHeaders); +PHP_HTTP_BEGIN_ARGS(setHeaders, 1) + PHP_HTTP_ARG_VAL(headers, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(addHeaders, 1) + PHP_HTTP_ARG_VAL(headers, 0) + PHP_HTTP_ARG_VAL(append, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getType); +PHP_HTTP_BEGIN_ARGS(setType, 1) + PHP_HTTP_ARG_VAL(type, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getInfo); +PHP_HTTP_BEGIN_ARGS(setInfo, 1) + PHP_HTTP_ARG_VAL(http_info, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getResponseCode); +PHP_HTTP_BEGIN_ARGS(setResponseCode, 1) + PHP_HTTP_ARG_VAL(response_code, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getResponseStatus); +PHP_HTTP_BEGIN_ARGS(setResponseStatus, 1) + PHP_HTTP_ARG_VAL(response_status, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getRequestMethod); +PHP_HTTP_BEGIN_ARGS(setRequestMethod, 1) + PHP_HTTP_ARG_VAL(request_method, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getRequestUrl); +PHP_HTTP_BEGIN_ARGS(setRequestUrl, 1) + PHP_HTTP_ARG_VAL(url, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getHttpVersion); +PHP_HTTP_BEGIN_ARGS(setHttpVersion, 1) + PHP_HTTP_ARG_VAL(http_version, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(guessContentType, 1) + PHP_HTTP_ARG_VAL(magic_file, 0) + PHP_HTTP_ARG_VAL(magic_mode, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getParentMessage); +PHP_HTTP_EMPTY_ARGS(send); +PHP_HTTP_EMPTY_ARGS(__toString); +PHP_HTTP_BEGIN_ARGS(toString, 0) + PHP_HTTP_ARG_VAL(include_parent, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(toMessageTypeObject); + +PHP_HTTP_EMPTY_ARGS(count); + +PHP_HTTP_EMPTY_ARGS(serialize); +PHP_HTTP_BEGIN_ARGS(unserialize, 1) + PHP_HTTP_ARG_VAL(serialized, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(rewind); +PHP_HTTP_EMPTY_ARGS(valid); +PHP_HTTP_EMPTY_ARGS(key); +PHP_HTTP_EMPTY_ARGS(current); +PHP_HTTP_EMPTY_ARGS(next); + +PHP_HTTP_EMPTY_ARGS(detach); +PHP_HTTP_BEGIN_ARGS(prepend, 1) + PHP_HTTP_ARG_OBJ(http\\Message, message, 0) +PHP_HTTP_END_ARGS; +PHP_HTTP_EMPTY_ARGS(reverse); + +static zval *php_http_message_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC); +static void php_http_message_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC); +static zval **php_http_message_object_get_prop_ptr(zval *object, zval *member, const zend_literal *literal_key TSRMLS_DC); +static HashTable *php_http_message_object_get_props(zval *object TSRMLS_DC); + +zend_class_entry *php_http_message_class_entry; +zend_function_entry php_http_message_method_entry[] = { + PHP_HTTP_MESSAGE_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_HTTP_MESSAGE_ME(getBody, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setBody, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getHeader, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setHeader, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(addHeader, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getHeaders, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setHeaders, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(addHeaders, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getType, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setType, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getInfo, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setInfo, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getResponseCode, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setResponseCode, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getResponseStatus, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setResponseStatus, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getRequestMethod, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setRequestMethod, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getRequestUrl, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setRequestUrl, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getHttpVersion, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(setHttpVersion, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(getParentMessage, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(toString, ZEND_ACC_PUBLIC) + + /* implements Countable */ + PHP_HTTP_MESSAGE_ME(count, ZEND_ACC_PUBLIC) + + /* implements Serializable */ + PHP_HTTP_MESSAGE_ME(serialize, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(unserialize, ZEND_ACC_PUBLIC) + + /* implements Iterator */ + PHP_HTTP_MESSAGE_ME(rewind, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(valid, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(current, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(key, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(next, ZEND_ACC_PUBLIC) + + ZEND_MALIAS(HttpMessage, __toString, toString, PHP_HTTP_ARGS(HttpMessage, __toString), ZEND_ACC_PUBLIC) + + PHP_HTTP_MESSAGE_ME(detach, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(prepend, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_ME(reverse, ZEND_ACC_PUBLIC) + + EMPTY_FUNCTION_ENTRY +}; +static zend_object_handlers php_http_message_object_handlers; +static HashTable php_http_message_object_prophandlers; + +typedef void (*php_http_message_object_prophandler_func_t)(php_http_message_object_t *o, zval *v TSRMLS_DC); + +typedef struct php_http_message_object_prophandler { + php_http_message_object_prophandler_func_t read; + php_http_message_object_prophandler_func_t write; +} php_http_message_object_prophandler_t; + +static STATUS php_http_message_object_add_prophandler(const char *prop_str, size_t prop_len, php_http_message_object_prophandler_func_t read, php_http_message_object_prophandler_func_t write) { + php_http_message_object_prophandler_t h = { read, write }; + return zend_hash_add(&php_http_message_object_prophandlers, prop_str, prop_len + 1, (void *) &h, sizeof(h), NULL); +} +static int php_http_message_object_has_prophandler(const char *prop_str, size_t prop_len) { + return zend_hash_exists(&php_http_message_object_prophandlers, prop_str, prop_len + 1); +} +static STATUS php_http_message_object_get_prophandler(const char *prop_str, size_t prop_len, php_http_message_object_prophandler_t **handler) { + return zend_hash_find(&php_http_message_object_prophandlers, prop_str, prop_len + 1, (void *) handler); +} +static void php_http_message_object_prophandler_get_type(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + RETVAL_LONG(obj->message->type); +} +static void php_http_message_object_prophandler_set_type(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + zval *cpy = php_http_zsep(IS_LONG, value); + php_http_message_set_type(obj->message, Z_LVAL_P(cpy)); + zval_ptr_dtor(&cpy); +} +static void php_http_message_object_prophandler_get_request_method(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message) && obj->message->http.info.request.method) { + RETVAL_STRING(obj->message->http.info.request.method, 1); + } else { + RETVAL_NULL(); + } +} +static void php_http_message_object_prophandler_set_request_method(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message)) { + zval *cpy = php_http_zsep(IS_STRING, value); + STR_SET(obj->message->http.info.request.method, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy))); + zval_ptr_dtor(&cpy); + } +} +static void php_http_message_object_prophandler_get_request_url(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message) && obj->message->http.info.request.url) { + RETVAL_STRING(obj->message->http.info.request.url, 1); + } else { + RETVAL_NULL(); + } +} +static void php_http_message_object_prophandler_set_request_url(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + if (PHP_HTTP_MESSAGE_TYPE(REQUEST, obj->message)) { + zval *cpy = php_http_zsep(IS_STRING, value); + STR_SET(obj->message->http.info.request.url, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy))); + zval_ptr_dtor(&cpy); + } +} +static void php_http_message_object_prophandler_get_response_status(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message) && obj->message->http.info.response.status) { + RETVAL_STRING(obj->message->http.info.response.status, 1); + } else { + RETVAL_NULL(); + } +} +static void php_http_message_object_prophandler_set_response_status(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) { + zval *cpy = php_http_zsep(IS_STRING, value); + STR_SET(obj->message->http.info.response.status, estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy))); + zval_ptr_dtor(&cpy); + } +} +static void php_http_message_object_prophandler_get_response_code(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) { + RETVAL_LONG(obj->message->http.info.response.code); + } else { + RETVAL_NULL(); + } +} +static void php_http_message_object_prophandler_set_response_code(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + if (PHP_HTTP_MESSAGE_TYPE(RESPONSE, obj->message)) { + zval *cpy = php_http_zsep(IS_LONG, value); + obj->message->http.info.response.code = Z_LVAL_P(cpy); + zval_ptr_dtor(&cpy); + } +} +static void php_http_message_object_prophandler_get_http_version(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + char *version_str; + size_t version_len; + + php_http_version_to_string(&obj->message->http.version, &version_str, &version_len, NULL, NULL TSRMLS_CC); + RETVAL_STRINGL(version_str, version_len, 0); +} +static void php_http_message_object_prophandler_set_http_version(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + zval *cpy = php_http_zsep(IS_STRING, value); + php_http_version_parse(&obj->message->http.version, Z_STRVAL_P(cpy) TSRMLS_CC); + zval_ptr_dtor(&cpy); +} +static void php_http_message_object_prophandler_get_headers(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + array_init(return_value); + zend_hash_copy(Z_ARRVAL_P(return_value), &obj->message->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); +} +static void php_http_message_object_prophandler_set_headers(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + zval *cpy = php_http_zsep(IS_ARRAY, value); + + zend_hash_clean(&obj->message->hdrs); + zend_hash_copy(&obj->message->hdrs, Z_ARRVAL_P(cpy), (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); + zval_ptr_dtor(&cpy); +} +static void php_http_message_object_prophandler_get_body(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + if (obj->body.handle) { + RETVAL_OBJVAL(obj->body, 1); + } else { + RETVAL_NULL(); + } +} +static void php_http_message_object_prophandler_set_body(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + if (Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), php_http_message_body_class_entry TSRMLS_CC)) { + if (obj->body.handle) { + zend_objects_store_del_ref_by_handle(obj->body.handle TSRMLS_CC); + } + Z_OBJ_ADDREF_P(value); + obj->body = Z_OBJVAL_P(value); + } +} +static void php_http_message_object_prophandler_get_parent_message(php_http_message_object_t *obj, zval *return_value TSRMLS_DC) { + if (obj->message->parent) { + RETVAL_OBJVAL(obj->parent, 1); + } else { + RETVAL_NULL(); + } +} +static void php_http_message_object_prophandler_set_parent_message(php_http_message_object_t *obj, zval *value TSRMLS_DC) { + if (Z_TYPE_P(value) == IS_OBJECT && instanceof_function(Z_OBJCE_P(value), php_http_message_class_entry TSRMLS_CC)) { + if (obj->message->parent) { + zend_objects_store_del_ref_by_handle(obj->parent.handle TSRMLS_CC); + } + Z_OBJ_ADDREF_P(value); + obj->parent = Z_OBJVAL_P(value); + } +} + +PHP_MINIT_FUNCTION(http_message) +{ + PHP_HTTP_REGISTER_CLASS(http, Message, http_message, php_http_object_class_entry, 0); + php_http_message_class_entry->create_object = php_http_message_object_new; + memcpy(&php_http_message_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_http_message_object_handlers.clone_obj = php_http_message_object_clone; + php_http_message_object_handlers.read_property = php_http_message_object_read_prop; + php_http_message_object_handlers.write_property = php_http_message_object_write_prop; + php_http_message_object_handlers.get_properties = php_http_message_object_get_props; + php_http_message_object_handlers.get_property_ptr_ptr = php_http_message_object_get_prop_ptr; + + zend_class_implements(php_http_message_class_entry TSRMLS_CC, 3, spl_ce_Countable, zend_ce_serializable, zend_ce_iterator); + + zend_hash_init(&php_http_message_object_prophandlers, 9, NULL, NULL, 1); + zend_declare_property_long(php_http_message_class_entry, ZEND_STRL("type"), PHP_HTTP_NONE, ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("type"), php_http_message_object_prophandler_get_type, php_http_message_object_prophandler_set_type); + zend_declare_property_string(php_http_message_class_entry, ZEND_STRL("body"), "", ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("body"), php_http_message_object_prophandler_get_body, php_http_message_object_prophandler_set_body); + zend_declare_property_string(php_http_message_class_entry, ZEND_STRL("requestMethod"), "", ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("requestMethod"), php_http_message_object_prophandler_get_request_method, php_http_message_object_prophandler_set_request_method); + zend_declare_property_string(php_http_message_class_entry, ZEND_STRL("requestUrl"), "", ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("requestUrl"), php_http_message_object_prophandler_get_request_url, php_http_message_object_prophandler_set_request_url); + zend_declare_property_string(php_http_message_class_entry, ZEND_STRL("responseStatus"), "", ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("responseStatus"), php_http_message_object_prophandler_get_response_status, php_http_message_object_prophandler_set_response_status); + zend_declare_property_long(php_http_message_class_entry, ZEND_STRL("responseCode"), 0, ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("responseCode"), php_http_message_object_prophandler_get_response_code, php_http_message_object_prophandler_set_response_code); + zend_declare_property_null(php_http_message_class_entry, ZEND_STRL("httpVersion"), ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("httpVersion"), php_http_message_object_prophandler_get_http_version, php_http_message_object_prophandler_set_http_version); + zend_declare_property_null(php_http_message_class_entry, ZEND_STRL("headers"), ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("headers"), php_http_message_object_prophandler_get_headers, php_http_message_object_prophandler_set_headers); + zend_declare_property_null(php_http_message_class_entry, ZEND_STRL("parentMessage"), ZEND_ACC_PROTECTED TSRMLS_CC); + php_http_message_object_add_prophandler(ZEND_STRL("parentMessage"), php_http_message_object_prophandler_get_parent_message, php_http_message_object_prophandler_set_parent_message); + + zend_declare_class_constant_long(php_http_message_class_entry, ZEND_STRL("TYPE_NONE"), PHP_HTTP_NONE TSRMLS_CC); + zend_declare_class_constant_long(php_http_message_class_entry, ZEND_STRL("TYPE_REQUEST"), PHP_HTTP_REQUEST TSRMLS_CC); + zend_declare_class_constant_long(php_http_message_class_entry, ZEND_STRL("TYPE_RESPONSE"), PHP_HTTP_RESPONSE TSRMLS_CC); + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(http_message) +{ + zend_hash_destroy(&php_http_message_object_prophandlers); + + return SUCCESS; +} + +void php_http_message_object_reverse(zval *this_ptr, zval *return_value TSRMLS_DC) +{ + int i = 0; + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + /* count */ + php_http_message_count(i, obj->message); + + if (i > 1) { + zval o; + zend_object_value *ovalues = NULL; + php_http_message_object_t **objects = NULL; + int last = i - 1; + + objects = ecalloc(i, sizeof(**objects)); + ovalues = ecalloc(i, sizeof(*ovalues)); + + /* we are the first message */ + objects[0] = obj; + ovalues[0] = getThis()->value.obj; + + /* fetch parents */ + INIT_PZVAL(&o); + o.type = IS_OBJECT; + for (i = 1; obj->parent.handle; ++i) { + o.value.obj = obj->parent; + ovalues[i] = o.value.obj; + objects[i] = obj = zend_object_store_get_object(&o TSRMLS_CC); + } + + /* reorder parents */ + for (last = --i; i; --i) { + objects[i]->message->parent = objects[i-1]->message; + objects[i]->parent = ovalues[i-1]; + } + objects[0]->message->parent = NULL; + objects[0]->parent.handle = 0; + objects[0]->parent.handlers = NULL; + + /* add ref (why?) */ + Z_OBJ_ADDREF_P(getThis()); + RETVAL_OBJVAL(ovalues[last], 1); + + efree(objects); + efree(ovalues); + } else { + RETURN_ZVAL(getThis(), 1, 0); + } +} + +void php_http_message_object_prepend(zval *this_ptr, zval *prepend, zend_bool top TSRMLS_DC) +{ + zval m; + php_http_message_t *save_parent_msg = NULL; + zend_object_value save_parent_obj = {0, NULL}; + php_http_message_object_t *obj = zend_object_store_get_object(this_ptr TSRMLS_CC); + php_http_message_object_t *prepend_obj = zend_object_store_get_object(prepend TSRMLS_CC); + + INIT_PZVAL(&m); + m.type = IS_OBJECT; + + if (!top) { + save_parent_obj = obj->parent; + save_parent_msg = obj->message->parent; + } else { + /* iterate to the most parent object */ + while (obj->parent.handle) { + m.value.obj = obj->parent; + obj = zend_object_store_get_object(&m TSRMLS_CC); + } + } + + /* prepend */ + obj->parent = prepend->value.obj; + obj->message->parent = prepend_obj->message; + + /* add ref */ + zend_objects_store_add_ref(prepend TSRMLS_CC); + while (prepend_obj->parent.handle) { + m.value.obj = prepend_obj->parent; + zend_objects_store_add_ref(&m TSRMLS_CC); + prepend_obj = zend_object_store_get_object(&m TSRMLS_CC); + } + + if (!top) { + prepend_obj->parent = save_parent_obj; + prepend_obj->message->parent = save_parent_msg; + } +} + +zend_object_value php_http_message_object_new(zend_class_entry *ce TSRMLS_DC) +{ + return php_http_message_object_new_ex(ce, NULL, NULL TSRMLS_CC); +} + +zend_object_value php_http_message_object_new_ex(zend_class_entry *ce, php_http_message_t *msg, php_http_message_object_t **ptr TSRMLS_DC) +{ + zend_object_value ov; + php_http_message_object_t *o; + + o = ecalloc(1, sizeof(php_http_message_object_t)); + zend_object_std_init((zend_object *) o, ce TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + + if (ptr) { + *ptr = o; + } + + if (msg) { + o->message = msg; + if (msg->parent) { + o->parent = php_http_message_object_new_ex(ce, msg->parent, NULL TSRMLS_CC); + } + o->body = php_http_message_body_object_new_ex(php_http_message_body_class_entry, php_http_message_body_copy(&msg->body, NULL, 0), NULL TSRMLS_CC); + } + + ov.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_message_object_free, NULL TSRMLS_CC); + ov.handlers = &php_http_message_object_handlers; + + return ov; +} + +zend_object_value php_http_message_object_clone(zval *this_ptr TSRMLS_DC) +{ + zend_object_value new_ov; + php_http_message_object_t *new_obj = NULL; + php_http_message_object_t *old_obj = zend_object_store_get_object(this_ptr TSRMLS_CC); + + new_ov = php_http_message_object_new_ex(old_obj->zo.ce, php_http_message_copy(old_obj->message, NULL), &new_obj); + zend_objects_clone_members(&new_obj->zo, new_ov, &old_obj->zo, Z_OBJ_HANDLE_P(this_ptr) TSRMLS_CC); + + return new_ov; +} + +void php_http_message_object_free(void *object TSRMLS_DC) +{ + php_http_message_object_t *o = (php_http_message_object_t *) object; + + if (o->iterator) { + zval_ptr_dtor(&o->iterator); + o->iterator = NULL; + } + if (o->message) { + /* do NOT free recursivly */ + php_http_message_dtor(o->message); + efree(o->message); + o->message = NULL; + } + if (o->parent.handle) { + zend_objects_store_del_ref_by_handle(o->parent.handle TSRMLS_CC); + } + if (o->body.handle) { + zend_objects_store_del_ref_by_handle(o->body.handle TSRMLS_CC); + } + zend_object_std_dtor((zend_object *) o TSRMLS_CC); + efree(o); +} + + +static zval **php_http_message_object_get_prop_ptr(zval *object, zval *member, const zend_literal *literal_key TSRMLS_DC) { + php_http_message_object_prophandler_t *handler; + zval *copy = php_http_zsep(IS_STRING, member); + + if (SUCCESS == php_http_message_object_get_prophandler(Z_STRVAL_P(copy), Z_STRLEN_P(copy), &handler)) { + zval_ptr_dtor(©); + return &php_http_property_proxy_init(NULL, object, member TSRMLS_CC)->myself; + } + zval_ptr_dtor(©); + + return zend_get_std_object_handlers()->get_property_ptr_ptr(object, member, literal_key TSRMLS_CC); +} + +static zval *php_http_message_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC) +{ + php_http_message_object_t *obj = zend_object_store_get_object(object TSRMLS_CC); + php_http_message_object_prophandler_t *handler; + zval *return_value, *copy = php_http_zsep(IS_STRING, member); + + if (SUCCESS == php_http_message_object_get_prophandler(Z_STRVAL_P(copy), Z_STRLEN_P(copy), &handler)) { + if (type == BP_VAR_W) { + zval_ptr_dtor(©); + zend_error(E_ERROR, "Cannot access HttpMessage properties by reference or array key/index"); + return NULL; + } + + ALLOC_ZVAL(return_value); + Z_SET_REFCOUNT_P(return_value, 0); + Z_UNSET_ISREF_P(return_value); + + handler->read(obj, return_value TSRMLS_CC); + + } else { + return_value = zend_get_std_object_handlers()->read_property(object, member, type, literal_key TSRMLS_CC); + } + + zval_ptr_dtor(©); + + return return_value; +} + +static void php_http_message_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC) +{ + php_http_message_object_t *obj = zend_object_store_get_object(object TSRMLS_CC); + php_http_message_object_prophandler_t *handler; + zval *copy = php_http_zsep(IS_STRING, member); + + if (SUCCESS == php_http_message_object_get_prophandler(Z_STRVAL_P(copy), Z_STRLEN_P(copy), &handler)) { + handler->write(obj, value TSRMLS_CC); + } else { + zend_get_std_object_handlers()->write_property(object, member, value, literal_key TSRMLS_CC); + } + + zval_ptr_dtor(©); +} + + +static HashTable *php_http_message_object_get_props(zval *object TSRMLS_DC) +{ + zval *headers; + php_http_message_object_t *obj = zend_object_store_get_object(object TSRMLS_CC); + php_http_message_t *msg = obj->message; + HashTable *props = zend_get_std_object_handlers()->get_properties(object TSRMLS_CC); + zval array, *parent, *body; + char *version; + + INIT_PZVAL_ARRAY(&array, props); + +#define ASSOC_PROP(array, ptype, name, val) \ + { \ + char *m_prop_name; \ + int m_prop_len; \ + zend_mangle_property_name(&m_prop_name, &m_prop_len, "*", 1, name, lenof(name), 0); \ + add_assoc_ ##ptype## _ex(&array, m_prop_name, sizeof(name)+3, val); \ + efree(m_prop_name); \ + } +#define ASSOC_STRING(array, name, val) ASSOC_STRINGL(array, name, val, strlen(val)) +#define ASSOC_STRINGL(array, name, val, len) ASSOC_STRINGL_EX(array, name, val, len, 1) +#define ASSOC_STRINGL_EX(array, name, val, len, cpy) \ + { \ + char *m_prop_name; \ + int m_prop_len; \ + zend_mangle_property_name(&m_prop_name, &m_prop_len, "*", 1, name, lenof(name), 0); \ + add_assoc_stringl_ex(&array, m_prop_name, sizeof(name)+3, val, len, cpy); \ + efree(m_prop_name); \ + } + + ASSOC_PROP(array, long, "type", msg->type); + ASSOC_STRINGL_EX(array, "httpVersion", version, spprintf(&version, 0, "%u.%u", msg->http.version.major, msg->http.version.minor), 0); + + switch (msg->type) { + case PHP_HTTP_REQUEST: + ASSOC_PROP(array, long, "responseCode", 0); + ASSOC_STRINGL(array, "responseStatus", "", 0); + ASSOC_STRING(array, "requestMethod", STR_PTR(msg->http.info.request.method)); + ASSOC_STRING(array, "requestUrl", STR_PTR(msg->http.info.request.url)); + break; + + case PHP_HTTP_RESPONSE: + ASSOC_PROP(array, long, "responseCode", msg->http.info.response.code); + ASSOC_STRING(array, "responseStatus", STR_PTR(msg->http.info.response.status)); + ASSOC_STRINGL(array, "requestMethod", "", 0); + ASSOC_STRINGL(array, "requestUrl", "", 0); + break; + + case PHP_HTTP_NONE: + default: + ASSOC_PROP(array, long, "responseCode", 0); + ASSOC_STRINGL(array, "responseStatus", "", 0); + ASSOC_STRINGL(array, "requestMethod", "", 0); + ASSOC_STRINGL(array, "requestUrl", "", 0); + break; + } + + MAKE_STD_ZVAL(headers); + array_init(headers); + zend_hash_copy(Z_ARRVAL_P(headers), &msg->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); + ASSOC_PROP(array, zval, "headers", headers); + + MAKE_STD_ZVAL(body); + if (!obj->body.handle) { + php_http_new(&obj->body, php_http_message_body_class_entry, (php_http_new_t) php_http_message_body_object_new_ex, NULL, (void *) php_http_message_body_copy(&obj->message->body, NULL, 0), NULL TSRMLS_CC); + } + ZVAL_OBJVAL(body, obj->body, 1); + ASSOC_PROP(array, zval, "body", body); + + MAKE_STD_ZVAL(parent); + if (msg->parent) { + ZVAL_OBJVAL(parent, obj->parent, 1); + } else { + ZVAL_NULL(parent); + } + ASSOC_PROP(array, zval, "parentMessage", parent); + + return props; +} + +/* PHP */ + +PHP_METHOD(HttpMessage, __construct) +{ + int length = 0; + char *message = NULL; + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &message, &length) && message && length) { + php_http_message_t *msg = obj->message; + + php_http_message_dtor(msg); + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) { + if ((obj->message = php_http_message_parse(msg, message, length))) { + if (obj->message->parent) { + obj->parent = php_http_message_object_new_ex(Z_OBJCE_P(getThis()), obj->message->parent, NULL TSRMLS_CC); + } + } else { + obj->message = php_http_message_init(msg, 0 TSRMLS_CC); + } + } end_error_handling(); + } + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + } end_error_handling(); + +} + +PHP_METHOD(HttpMessage, getBody) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) { + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + if (obj->body.handle || SUCCESS == php_http_new(&obj->body, php_http_message_body_class_entry, (php_http_new_t) php_http_message_body_object_new_ex, NULL, (void *) php_http_message_body_copy(&obj->message->body, NULL, 0), NULL TSRMLS_CC)) { + RETVAL_OBJVAL(obj->body, 1); + } + } + } end_error_handling(); +} + +PHP_METHOD(HttpMessage, setBody) +{ + zval *zbody; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &zbody, php_http_message_body_class_entry)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + php_http_message_body_object_t *body_obj = zend_object_store_get_object(zbody TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + php_http_message_body_dtor(&obj->message->body); + php_http_message_body_init(&obj->message->body, php_http_message_body_stream(body_obj->body)); + Z_OBJ_ADDREF_P(zbody); + obj->body = Z_OBJVAL_P(zbody); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getHeader) +{ + char *header_str; + int header_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &header_str, &header_len)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + zval *header; + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + if ((header = php_http_message_header(obj->message, header_str, header_len, 0))) { + RETURN_ZVAL(header, 1, 1); + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getHeaders) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + array_init(return_value); + array_copy(&obj->message->hdrs, Z_ARRVAL_P(return_value)); + } +} + +PHP_METHOD(HttpMessage, setHeader) +{ + zval *zvalue = NULL; + char *name_str; + int name_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|z!", &name_str, &name_len, &zvalue)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + char *name = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + if (!zvalue) { + RETVAL_SUCCESS(zend_hash_del(&obj->message->hdrs, name, name_len + 1)); + } else { + Z_ADDREF_P(zvalue); + RETVAL_SUCCESS(zend_hash_update(&obj->message->hdrs, name, name_len + 1, &zvalue, sizeof(void *), NULL)); + } + efree(name); + return; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, setHeaders) +{ + zval *new_headers = NULL; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/!", &new_headers)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + zend_hash_clean(&obj->message->hdrs); + if (new_headers) { + array_join(Z_ARRVAL_P(new_headers), &obj->message->hdrs, 0, ARRAY_JOIN_PRETTIFY|ARRAY_JOIN_STRONLY); + } + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, addHeader) +{ + zval *zvalue; + char *name_str; + int name_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &name_str, &name_len, &zvalue)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + char *name = php_http_pretty_key(estrndup(name_str, name_len), name_len, 1, 1); + zval *header; + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + Z_ADDREF_P(zvalue); + if ((header = php_http_message_header(obj->message, name, name_len, 0))) { + convert_to_array(header); + RETVAL_SUCCESS(zend_hash_next_index_insert(Z_ARRVAL_P(header), &zvalue, sizeof(void *), NULL)); + } else { + RETVAL_SUCCESS(zend_hash_update(&obj->message->hdrs, name, name_len + 1, &zvalue, sizeof(void *), NULL)); + } + efree(name); + return; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, addHeaders) +{ + zval *new_headers; + zend_bool append = 0; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a|b", &new_headers, &append)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + array_join(Z_ARRVAL_P(new_headers), &obj->message->hdrs, append, ARRAY_JOIN_STRONLY|ARRAY_JOIN_PRETTIFY); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getType) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + RETURN_LONG(obj->message->type); + } +} + +PHP_METHOD(HttpMessage, setType) +{ + long type; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &type)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + php_http_message_set_type(obj->message, type); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getInfo) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + switch (obj->message->type) { + case PHP_HTTP_REQUEST: + Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_REQUEST_FMT_ARGS(&obj->message->http, "")); + break; + case PHP_HTTP_RESPONSE: + Z_STRLEN_P(return_value) = spprintf(&Z_STRVAL_P(return_value), 0, PHP_HTTP_INFO_RESPONSE_FMT_ARGS(&obj->message->http, "")); + break; + default: + RETURN_NULL(); + break; + } + Z_TYPE_P(return_value) = IS_STRING; + return; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, setInfo) +{ + char *str; + int len; + php_http_info_t inf; + + if ( SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len) + && php_http_info_parse(&inf, str TSRMLS_CC)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + php_http_message_set_info(obj->message, &inf); + php_http_info_dtor(&inf); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getHttpVersion) +{ + if (SUCCESS == zend_parse_parameters_none()) { + char *str; + size_t len; + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + php_http_version_to_string(&obj->message->http.version, &str, &len, NULL, NULL TSRMLS_CC); + RETURN_STRINGL(str, len, 0); + } + + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, setHttpVersion) +{ + char *v_str; + int v_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &v_str, &v_len)) { + php_http_version_t version; + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + if (php_http_version_parse(&version, v_str TSRMLS_CC)) { + obj->message->http.version = version; + RETURN_TRUE; + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getResponseCode) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + PHP_HTTP_MESSAGE_TYPE_CHECK(RESPONSE, obj->message, RETURN_FALSE); + RETURN_LONG(obj->message->http.info.response.code); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, setResponseCode) +{ + long code; + zend_bool strict = 1; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|b", &code, &strict)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + PHP_HTTP_MESSAGE_TYPE_CHECK(RESPONSE, obj->message, RETURN_FALSE); + if (strict && (code < 100 || code > 599)) { + php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Invalid response code (100-599): %ld", code); + RETURN_FALSE; + } + + obj->message->http.info.response.code = code; + RETURN_TRUE; + } + + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getResponseStatus) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + PHP_HTTP_MESSAGE_TYPE_CHECK(RESPONSE, obj->message, RETURN_FALSE); + if (obj->message->http.info.response.status) { + RETURN_STRING(obj->message->http.info.response.status, 1); + } else { + RETURN_EMPTY_STRING(); + } + } + + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, setResponseStatus) +{ + char *status; + int status_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &status, &status_len)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + PHP_HTTP_MESSAGE_TYPE_CHECK(RESPONSE, obj->message, RETURN_FALSE); + STR_SET(obj->message->http.info.response.status, estrndup(status, status_len)); + RETURN_TRUE; + } + + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getRequestMethod) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + PHP_HTTP_MESSAGE_TYPE_CHECK(REQUEST, obj->message, RETURN_FALSE); + if (obj->message->http.info.request.method) { + RETURN_STRING(obj->message->http.info.request.method, 1); + } else { + RETURN_EMPTY_STRING(); + } + } + + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, setRequestMethod) +{ + char *method; + int method_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &method, &method_len)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + PHP_HTTP_MESSAGE_TYPE_CHECK(REQUEST, obj->message, RETURN_FALSE); + if (method_len < 1) { + php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Cannot set HttpMessage::requestMethod to an empty string"); + RETURN_FALSE; + } + + STR_SET(obj->message->http.info.request.method, estrndup(method, method_len)); + RETURN_TRUE; + } + + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, getRequestUrl) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + PHP_HTTP_MESSAGE_TYPE_CHECK(REQUEST, obj->message, RETURN_FALSE); + if (obj->message->http.info.request.url) { + RETURN_STRING(obj->message->http.info.request.url, 1); + } else { + RETURN_EMPTY_STRING(); + } + } + + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, setRequestUrl) +{ + char *url_str; + int url_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &url_str, &url_len)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + PHP_HTTP_MESSAGE_TYPE_CHECK(REQUEST, obj->message, RETURN_FALSE); + if (url_len < 1) { + php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Cannot set HttpMessage::requestUrl to an empty string"); + RETURN_FALSE; + } + STR_SET(obj->message->http.info.request.url, estrndup(url_str, url_len)); + RETURN_TRUE; + } + + RETURN_FALSE; +} + + +PHP_METHOD(HttpMessage, getParentMessage) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) { + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + if (obj->message->parent) { + RETVAL_OBJVAL(obj->parent, 1); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "HttpMessage does not have a parent message"); + } + } + } end_error_handling(); +} + +PHP_METHOD(HttpMessage, toString) +{ + zend_bool include_parent = 0; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &include_parent)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + char *string; + size_t length; + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + if (include_parent) { + php_http_message_serialize(obj->message, &string, &length); + } else { + php_http_message_to_string(obj->message, &string, &length); + } + if (string) { + RETURN_STRINGL(string, length, 0); + } + } + RETURN_EMPTY_STRING(); +} + +PHP_METHOD(HttpMessage, serialize) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + char *string; + size_t length; + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + php_http_message_serialize(obj->message, &string, &length); + RETURN_STRINGL(string, length, 0); + } + RETURN_EMPTY_STRING(); +} + +PHP_METHOD(HttpMessage, unserialize) +{ + int length; + char *serialized; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &serialized, &length)) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + php_http_message_t *msg; + + if (obj->message) { + php_http_message_dtor(obj->message); + efree(obj->message); + } + if ((msg = php_http_message_parse(NULL, serialized, (size_t) length TSRMLS_CC))) { + obj->message = msg; + } else { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + php_http_error(HE_ERROR, PHP_HTTP_E_RUNTIME, "Could not unserialize HttpMessage"); + } + } +} + +PHP_METHOD(HttpMessage, detach) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(message)) { + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + RETVAL_OBJVAL(php_http_message_object_new_ex(obj->zo.ce, php_http_message_copy(obj->message, NULL), NULL TSRMLS_CC), 0); + } + } end_error_handling(); +} + +PHP_METHOD(HttpMessage, prepend) +{ + zval *prepend; + zend_bool top = 1; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O|b", &prepend, php_http_message_class_entry, &top)) { + php_http_message_t *msg[2]; + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + php_http_message_object_t *prepend_obj = zend_object_store_get_object(prepend TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + if (!prepend_obj->message) { + prepend_obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + /* safety check */ + for (msg[0] = obj->message; msg[0]; msg[0] = msg[0]->parent) { + for (msg[1] = prepend_obj->message; msg[1]; msg[1] = msg[1]->parent) { + if (msg[0] == msg[1]) { + php_http_error(HE_THROW, PHP_HTTP_E_INVALID_PARAM, "Cannot prepend a message located within the same message chain"); + return; + } + } + } + + php_http_message_object_prepend(getThis(), prepend, top TSRMLS_CC); + } +} + +PHP_METHOD(HttpMessage, reverse) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_reverse(getThis(), return_value TSRMLS_CC); + } +} + +PHP_METHOD(HttpMessage, count) +{ + if (SUCCESS == zend_parse_parameters_none()) { + long i = 0; + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (!obj->message) { + obj->message = php_http_message_init(NULL, 0 TSRMLS_CC); + } + + php_http_message_count(i, obj->message); + RETURN_LONG(i); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessage, rewind) +{ + if (SUCCESS == zend_parse_parameters_none()) { + zval *zobj = getThis(); + php_http_message_object_t *obj = zend_object_store_get_object(zobj TSRMLS_CC); + + if (obj->iterator) { + zval_ptr_dtor(&obj->iterator); + } + Z_ADDREF_P(zobj); + obj->iterator = zobj; + } +} + +PHP_METHOD(HttpMessage, valid) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_BOOL(obj->iterator != NULL); + } +} + +PHP_METHOD(HttpMessage, next) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (obj->iterator) { + php_http_message_object_t *itr = zend_object_store_get_object(obj->iterator TSRMLS_CC); + + if (itr && itr->parent.handle) { + zval *old = obj->iterator; + MAKE_STD_ZVAL(obj->iterator); + ZVAL_OBJVAL(obj->iterator, itr->parent, 1); + zval_ptr_dtor(&old); + } else { + zval_ptr_dtor(&obj->iterator); + obj->iterator = NULL; + } + } + } +} + +PHP_METHOD(HttpMessage, key) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_LONG(obj->iterator ? obj->iterator->value.obj.handle:0); + } +} + +PHP_METHOD(HttpMessage, current) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (obj->iterator) { + RETURN_ZVAL(obj->iterator, 1, 0); + } + } +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_message.h b/php_http_message.h new file mode 100644 index 0000000..47db451 --- /dev/null +++ b/php_http_message.h @@ -0,0 +1,147 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_message.h 292841 2009-12-31 08:48:57Z mike $ */ + +#ifndef PHP_HTTP_MESSAGE_H +#define PHP_HTTP_MESSAGE_H + +/* required minimum length of an HTTP message "HTTP/1.1" */ +#define PHP_HTTP_MESSAGE_MIN_SIZE 8 +#define PHP_HTTP_MESSAGE_TYPE(TYPE, msg) ((msg) && ((msg)->type == PHP_HTTP_ ##TYPE)) +#define PHP_HTTP_MESSAGE_TYPE_CHECK(type, msg, action) \ + if (!PHP_HTTP_MESSAGE_TYPE(type, (msg))) { \ + php_http_error(HE_NOTICE, PHP_HTTP_E_MESSAGE_TYPE, "HttpMessage is not of type "#type); \ + action; \ + } + +typedef php_http_info_type_t php_http_message_type_t; +typedef struct php_http_message php_http_message_t; + +struct php_http_message { + PHP_HTTP_INFO_IMPL(http, type) + HashTable hdrs; + php_http_message_body_t body; + php_http_message_t *parent; + void *opaque; +#ifdef ZTS + void ***ts; +#endif +}; + +PHP_HTTP_API zend_bool php_http_message_info_callback(php_http_message_t **message, HashTable **headers, php_http_info_t *info TSRMLS_DC); + +PHP_HTTP_API php_http_message_t *php_http_message_init(php_http_message_t *m, php_http_message_type_t t TSRMLS_DC); +PHP_HTTP_API php_http_message_t *php_http_message_init_env(php_http_message_t *m, php_http_message_type_t t TSRMLS_DC); +PHP_HTTP_API php_http_message_t *php_http_message_copy(php_http_message_t *from, php_http_message_t *to); +PHP_HTTP_API void php_http_message_dtor(php_http_message_t *message); +PHP_HTTP_API void php_http_message_free(php_http_message_t **message); + +PHP_HTTP_API void php_http_message_set_type(php_http_message_t *m, php_http_message_type_t t); +PHP_HTTP_API void php_http_message_set_info(php_http_message_t *message, php_http_info_t *info); + +PHP_HTTP_API zval *php_http_message_header(php_http_message_t *msg, char *key_str, size_t key_len, int join); + +PHP_HTTP_API void php_http_message_to_string(php_http_message_t *msg, char **string, size_t *length); +PHP_HTTP_API void php_http_message_to_struct(php_http_message_t *msg, zval *strct); +PHP_HTTP_API void php_http_message_to_callback(php_http_message_t *msg, php_http_pass_callback_t cb, void *cb_arg); + +PHP_HTTP_API void php_http_message_serialize(php_http_message_t *message, char **string, size_t *length); +PHP_HTTP_API php_http_message_t *php_http_message_reverse(php_http_message_t *msg); +PHP_HTTP_API php_http_message_t *php_http_message_interconnect(php_http_message_t *m1, php_http_message_t *m2); + +#define php_http_message_count(c, m) \ +{ \ + php_http_message_t *__tmp_msg = (m); \ + for (c = 0; __tmp_msg; __tmp_msg = __tmp_msg->parent, ++(c)); \ +} + +PHP_HTTP_API php_http_message_t *php_http_message_parse(php_http_message_t *msg, const char *str, size_t len TSRMLS_DC); + +/* PHP */ + +typedef struct php_http_message_object { + zend_object zo; + php_http_message_t *message; + zend_object_value parent, body; + zval *iterator; +} php_http_message_object_t; + +extern zend_class_entry *php_http_message_class_entry; +extern zend_function_entry http_message_method_entry[]; + +extern PHP_MINIT_FUNCTION(http_message); +extern PHP_MSHUTDOWN_FUNCTION(http_message); + +extern void php_http_message_object_prepend(zval *this_ptr, zval *prepend, zend_bool top /* = 1 */ TSRMLS_DC); +extern void php_http_message_object_reverse(zval *this_ptr, zval *return_value TSRMLS_DC); + +extern zend_object_value php_http_message_object_new(zend_class_entry *ce TSRMLS_DC); +extern zend_object_value php_http_message_object_new_ex(zend_class_entry *ce, php_http_message_t *msg, php_http_message_object_t **ptr TSRMLS_DC); +extern zend_object_value php_http_message_object_clone(zval *object TSRMLS_DC); +extern void php_http_message_object_free(void *object TSRMLS_DC); + +PHP_METHOD(HttpMessage, __construct); +PHP_METHOD(HttpMessage, getBody); +PHP_METHOD(HttpMessage, setBody); +PHP_METHOD(HttpMessage, getHeader); +PHP_METHOD(HttpMessage, setHeader); +PHP_METHOD(HttpMessage, addHeader); +PHP_METHOD(HttpMessage, getHeaders); +PHP_METHOD(HttpMessage, setHeaders); +PHP_METHOD(HttpMessage, addHeaders); +PHP_METHOD(HttpMessage, getType); +PHP_METHOD(HttpMessage, setType); +PHP_METHOD(HttpMessage, getInfo); +PHP_METHOD(HttpMessage, setInfo); +PHP_METHOD(HttpMessage, getResponseCode); +PHP_METHOD(HttpMessage, setResponseCode); +PHP_METHOD(HttpMessage, getResponseStatus); +PHP_METHOD(HttpMessage, setResponseStatus); +PHP_METHOD(HttpMessage, getRequestMethod); +PHP_METHOD(HttpMessage, setRequestMethod); +PHP_METHOD(HttpMessage, getRequestUrl); +PHP_METHOD(HttpMessage, setRequestUrl); +PHP_METHOD(HttpMessage, getHttpVersion); +PHP_METHOD(HttpMessage, setHttpVersion); +PHP_METHOD(HttpMessage, guessContentType); +PHP_METHOD(HttpMessage, getParentMessage); +PHP_METHOD(HttpMessage, send); +PHP_METHOD(HttpMessage, toString); +PHP_METHOD(HttpMessage, toMessageTypeObject); + +PHP_METHOD(HttpMessage, count); +PHP_METHOD(HttpMessage, serialize); +PHP_METHOD(HttpMessage, unserialize); +PHP_METHOD(HttpMessage, rewind); +PHP_METHOD(HttpMessage, valid); +PHP_METHOD(HttpMessage, current); +PHP_METHOD(HttpMessage, key); +PHP_METHOD(HttpMessage, next); + +PHP_METHOD(HttpMessage, factory); + +PHP_METHOD(HttpMessage, detach); +PHP_METHOD(HttpMessage, prepend); +PHP_METHOD(HttpMessage, reverse); + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_message_body.c b/php_http_message_body.c new file mode 100644 index 0000000..969f860 --- /dev/null +++ b/php_http_message_body.c @@ -0,0 +1,687 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_message_body.c 292841 2009-12-31 08:48:57Z mike $ */ + +#include "php_http.h" + +typedef struct curl_httppost *post_data[2]; + +static inline STATUS add_field(php_http_message_body_t *body, const char *name, const char *value_str, size_t value_len); +static inline STATUS add_file(php_http_message_body_t *body, const char *name, const char *path, const char *ctype); +static STATUS recursive_fields(post_data http_post_data, HashTable *fields, const char *prefix TSRMLS_DC); +static STATUS recursive_files(post_data http_post_data, HashTable *files, const char *prefix TSRMLS_DC); + +PHP_HTTP_API php_http_message_body_t *php_http_message_body_init(php_http_message_body_t *body, php_stream *stream TSRMLS_DC) +{ + if (!body) { + body = emalloc(sizeof(php_http_message_body_t)); + } + + if (stream) { + php_stream_auto_cleanup(stream); + body->stream_id = php_stream_get_resource_id(stream); + zend_list_addref(body->stream_id); + } else { + stream = php_stream_temp_new(); + php_stream_auto_cleanup(stream); + body->stream_id = php_stream_get_resource_id(stream); + } + TSRMLS_SET_CTX(body->ts); + + return body; +} + +PHP_HTTP_API php_http_message_body_t *php_http_message_body_copy(php_http_message_body_t *from, php_http_message_body_t *to, zend_bool dup_internal_stream_and_contents) +{ + if (!from) { + return NULL; + } else { + TSRMLS_FETCH_FROM_CTX(from->ts); + + if (dup_internal_stream_and_contents) { + to = php_http_message_body_init(to, NULL TSRMLS_CC); + php_http_message_body_to_stream(from, php_http_message_body_stream(to), 0, 0); + } else { + to = php_http_message_body_init(to, php_http_message_body_stream(from) TSRMLS_CC); + } + + return to; + } +} + +PHP_HTTP_API void php_http_message_body_dtor(php_http_message_body_t *body) +{ + if (body) { + /* NO FIXME: shows leakinfo in DEBUG mode */ + zend_list_delete(body->stream_id); + } +} + +PHP_HTTP_API void php_http_message_body_free(php_http_message_body_t **body) +{ + if (*body) { + php_http_message_body_dtor(*body); + efree(*body); + *body = NULL; + } +} + +PHP_HTTP_API php_stream_statbuf *php_http_message_body_stat(php_http_message_body_t *body) +{ + TSRMLS_FETCH_FROM_CTX(body->ts); + php_stream_stat(php_http_message_body_stream(body), &body->ssb); + return &body->ssb; +} + +PHP_HTTP_API char *php_http_message_body_etag(php_http_message_body_t *body) +{ + TSRMLS_FETCH_FROM_CTX(body->ts); + php_stream_statbuf *ssb = php_http_message_body_stat(body); + + /* real file or temp buffer ? */ + if (body->ssb.sb.st_mtime) { + char *etag; + + spprintf(&etag, 0, "%lx-%lx-%lx", ssb->sb.st_ino, ssb->sb.st_mtime, ssb->sb.st_size); + return etag; + } else { + void *ctx = php_http_etag_init(TSRMLS_C); + + php_http_message_body_to_callback(body, php_http_etag_update, ctx, 0, 0); + return php_http_etag_finish(ctx TSRMLS_CC); + } +} + +PHP_HTTP_API void php_http_message_body_to_string(php_http_message_body_t *body, char **buf, size_t *len, off_t offset, size_t forlen) +{ + TSRMLS_FETCH_FROM_CTX(body->ts); + php_stream *s = php_http_message_body_stream(body); + + php_stream_seek(s, offset, SEEK_SET); + if (!forlen) { + forlen = -1; + } + *len = php_stream_copy_to_mem(s, buf, forlen, 0); +} + +PHP_HTTP_API void php_http_message_body_to_stream(php_http_message_body_t *body, php_stream *dst, off_t offset, size_t forlen) +{ + TSRMLS_FETCH_FROM_CTX(body->ts); + php_stream *s = php_http_message_body_stream(body); + + php_stream_seek(s, offset, SEEK_SET); + if (!forlen) { + forlen = -1; + } + php_stream_copy_to_stream_ex(s, dst, forlen, NULL); +} + +PHP_HTTP_API void php_http_message_body_to_callback(php_http_message_body_t *body, php_http_pass_callback_t cb, void *cb_arg, off_t offset, size_t forlen) +{ + TSRMLS_FETCH_FROM_CTX(body->ts); + php_stream *s = php_http_message_body_stream(body); + + php_stream_seek(s, offset, SEEK_SET); + + if (!forlen) { + forlen = -1; + } + while (!php_stream_eof(s)) { + char buf[0x1000]; + size_t read = php_stream_read(s, buf, MIN(forlen, sizeof(buf))); + + if (read) { + cb(cb_arg, buf, read); + } + + if (read < MIN(forlen, sizeof(buf))) { + break; + } + + if (forlen && !(forlen -= read)) { + break; + } + } +} + +PHP_HTTP_API STATUS php_http_message_body_to_callback_in_chunks(php_http_message_body_t *body, php_http_pass_callback_t cb, void *cb_arg, HashTable *chunks) +{ + TSRMLS_FETCH_FROM_CTX(body->ts); + php_stream *s = php_http_message_body_stream(body); + HashPosition pos; + zval **chunk; + + FOREACH_HASH_VAL(pos, chunks, chunk) { + zval **begin, **end; + + if (IS_ARRAY == Z_TYPE_PP(chunk) + && SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(chunk), 0, (void *) &begin) + && IS_LONG == Z_TYPE_PP(begin) + && SUCCESS == zend_hash_index_find(Z_ARRVAL_PP(chunk), 1, (void *) &end) + && IS_LONG == Z_TYPE_PP(end) + ) { + if (SUCCESS != php_stream_seek(s, Z_LVAL_PP(begin), SEEK_SET)) { + return FAILURE; + } else { + long length = Z_LVAL_PP(end) - Z_LVAL_PP(begin) + 1; + + while (length > 0 && !php_stream_eof(s)) { + char buf[0x1000]; + size_t read = php_stream_read(s, buf, MIN(length, sizeof(buf))); + + if (read) { + cb(cb_arg, buf, read); + } + + if (read < MIN(length, sizeof(buf))) { + break; + } + + length -= read; + } + } + } + } + + return SUCCESS; +} + +PHP_HTTP_API size_t php_http_message_body_append(php_http_message_body_t *body, const char *buf, size_t len) +{ + TSRMLS_FETCH_FROM_CTX(body->ts); + php_stream *s = php_http_message_body_stream(body); + + php_stream_seek(s, 0, SEEK_END); + return php_stream_write(s, buf, len); +} + +PHP_HTTP_API STATUS php_http_message_body_add(php_http_message_body_t *body, HashTable *fields, HashTable *files) +{ + post_data http_post_data = {NULL, NULL}; + TSRMLS_FETCH_FROM_CTX(body->ts); + + if (fields && SUCCESS != recursive_fields(http_post_data, fields, NULL TSRMLS_CC)) { + return FAILURE; + } + if (files && (zend_hash_num_elements(files) > 0) && (SUCCESS != recursive_files(http_post_data, files, NULL TSRMLS_CC))) { + return FAILURE; + } + if (CURLE_OK != curl_formget(http_post_data[0], body, (curl_formget_callback) php_http_message_body_append)) { + return FAILURE; + } + return SUCCESS; +} + +PHP_HTTP_API STATUS php_http_message_body_add_field(php_http_message_body_t *body, const char *name, const char *value_str, size_t value_len) +{ + return add_field(body, name, value_str, value_len); +} + +PHP_HTTP_API STATUS php_http_message_body_add_file(php_http_message_body_t *body, const char *name, const char *path, const char *ctype) +{ + return add_file(body, name, path, ctype); +} + +static inline char *format_key(uint type, char *str, ulong num, const char *prefix, int numeric_key_for_empty_prefix) { + char *new_key = NULL; + + if (prefix && *prefix) { + if (type == HASH_KEY_IS_STRING) { + spprintf(&new_key, 0, "%s[%s]", prefix, str); + } else { + spprintf(&new_key, 0, "%s[%lu]", prefix, num); + } + } else if (type == HASH_KEY_IS_STRING) { + new_key = estrdup(str); + } else if (numeric_key_for_empty_prefix) { + spprintf(&new_key, 0, "%lu", num); + } + + return new_key; +} + +static inline STATUS add_field(php_http_message_body_t *body, const char *name, const char *value_str, size_t value_len) +{ + post_data http_post_data = {NULL, NULL}; + CURLcode err; + + err = curl_formadd(&http_post_data[0], &http_post_data[1], + CURLFORM_COPYNAME, name, + CURLFORM_COPYCONTENTS, value_str, + CURLFORM_CONTENTSLENGTH, (long) value_len, + CURLFORM_END + ); + + if (CURLE_OK != err) { + return FAILURE; + } + + err = curl_formget(http_post_data[0], body, (curl_formget_callback) php_http_message_body_append); + + if (CURLE_OK != err) { + curl_formfree(http_post_data[0]); + return FAILURE; + } + + curl_formfree(http_post_data[0]); + return SUCCESS; +} + +static inline STATUS add_file(php_http_message_body_t *body, const char *name, const char *path, const char *ctype) +{ + post_data http_post_data = {NULL, NULL}; + CURLcode err; + + err = curl_formadd(&http_post_data[0], &http_post_data[1], + CURLFORM_COPYNAME, name, + CURLFORM_FILE, path, + CURLFORM_CONTENTTYPE, ctype ? ctype : "application/octet-stream", + CURLFORM_END + ); + + if (CURLE_OK != err) { + return FAILURE; + } + + err = curl_formget(http_post_data[0], body, (curl_formget_callback) php_http_message_body_append); + + if (CURLE_OK != err) { + curl_formfree(http_post_data[0]); + return FAILURE; + } + + curl_formfree(http_post_data[0]); + return SUCCESS; +} + +static STATUS recursive_fields(post_data http_post_data, HashTable *fields, const char *prefix TSRMLS_DC) { + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + zval **data_ptr; + HashPosition pos; + char *new_key = NULL; + CURLcode err = 0; + + if (fields && !fields->nApplyCount) { + FOREACH_HASH_KEYVAL(pos, fields, key, data_ptr) { + if (key.type != HASH_KEY_IS_STRING || *key.str) { + new_key = format_key(key.type, key.str, key.num, prefix, 1); + + switch (Z_TYPE_PP(data_ptr)) { + case IS_ARRAY: + case IS_OBJECT: { + STATUS status; + + ++fields->nApplyCount; + status = recursive_fields(http_post_data, HASH_OF(*data_ptr), new_key TSRMLS_CC); + --fields->nApplyCount; + + if (SUCCESS != status) { + goto error; + } + break; + } + + default: { + zval *data = php_http_zsep(IS_STRING, *data_ptr); + + err = curl_formadd(&http_post_data[0], &http_post_data[1], + CURLFORM_COPYNAME, new_key, + CURLFORM_COPYCONTENTS, Z_STRVAL_P(data), + CURLFORM_CONTENTSLENGTH, (long) Z_STRLEN_P(data), + CURLFORM_END + ); + + zval_ptr_dtor(&data); + + if (CURLE_OK != err) { + goto error; + } + break; + } + } + STR_FREE(new_key); + } + } + } + + return SUCCESS; + +error: + if (new_key) { + efree(new_key); + } + if (http_post_data[0]) { + curl_formfree(http_post_data[0]); + } + if (err) { + php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not encode post fields: %s", curl_easy_strerror(err)); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not encode post fields: unknown error"); + } + return FAILURE; +} + +static STATUS recursive_files(post_data http_post_data, HashTable *files, const char *prefix TSRMLS_DC) { + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + zval **data_ptr; + HashPosition pos; + char *new_key = NULL; + CURLcode err = 0; + + if (files && !files->nApplyCount) { + FOREACH_HASH_KEYVAL(pos, files, key, data_ptr) { + zval **file_ptr, **type_ptr, **name_ptr; + + if (key.type != HASH_KEY_IS_STRING || *key.str) { + new_key = format_key(key.type, key.str, key.num, prefix, 0); + + if (Z_TYPE_PP(data_ptr) != IS_ARRAY && Z_TYPE_PP(data_ptr) != IS_OBJECT) { + if (new_key || key.type == HASH_KEY_IS_STRING) { + php_http_error(HE_NOTICE, PHP_HTTP_E_INVALID_PARAM, "Unrecognized type of post file array entry '%s'", new_key ? new_key : key.str); + } else { + php_http_error(HE_NOTICE, PHP_HTTP_E_INVALID_PARAM, "Unrecognized type of post file array entry '%lu'", key.num); + } + } else if ( SUCCESS != zend_hash_find(HASH_OF(*data_ptr), "name", sizeof("name"), (void *) &name_ptr) || + SUCCESS != zend_hash_find(HASH_OF(*data_ptr), "type", sizeof("type"), (void *) &type_ptr) || + SUCCESS != zend_hash_find(HASH_OF(*data_ptr), "file", sizeof("file"), (void *) &file_ptr)) { + STATUS status; + + ++files->nApplyCount; + status = recursive_files(http_post_data, HASH_OF(*data_ptr), new_key TSRMLS_CC); + --files->nApplyCount; + + if (SUCCESS != status) { + goto error; + } + } else { + const char *path; + zval *file = php_http_zsep(IS_STRING, *file_ptr); + zval *type = php_http_zsep(IS_STRING, *type_ptr); + zval *name = php_http_zsep(IS_STRING, *name_ptr); + + if (SUCCESS != php_check_open_basedir(Z_STRVAL_P(file) TSRMLS_CC)) { + goto error; + } + + /* this is blatant but should be sufficient for most cases */ + if (strncasecmp(Z_STRVAL_P(file), "file://", lenof("file://"))) { + path = Z_STRVAL_P(file); + } else { + path = Z_STRVAL_P(file) + lenof("file://"); + } + + if (new_key) { + char *tmp_key = format_key(HASH_KEY_IS_STRING, Z_STRVAL_P(name), 0, new_key, 0); + STR_SET(new_key, tmp_key); + } + + err = curl_formadd(&http_post_data[0], &http_post_data[1], + CURLFORM_COPYNAME, new_key ? new_key : Z_STRVAL_P(name), + CURLFORM_FILE, path, + CURLFORM_CONTENTTYPE, Z_STRVAL_P(type), + CURLFORM_END + ); + + zval_ptr_dtor(&file); + zval_ptr_dtor(&type); + zval_ptr_dtor(&name); + + if (CURLE_OK != err) { + goto error; + } + } + STR_FREE(new_key); + } + } + } + + return SUCCESS; + +error: + if (new_key) { + efree(new_key); + } + if (http_post_data[0]) { + curl_formfree(http_post_data[0]); + } + if (err) { + php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not encode post files: %s", curl_easy_strerror(err)); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_ENCODING, "Could not encode post files: unknown error"); + } + return FAILURE; +} + +/* PHP */ + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpMessageBody, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpMessageBody, method, 0) +#define PHP_HTTP_MESSAGE_BODY_ME(method, visibility) PHP_ME(HttpMessageBody, method, PHP_HTTP_ARGS(HttpMessageBody, method), visibility) + +PHP_HTTP_BEGIN_ARGS(__construct, 0) + PHP_HTTP_ARG_VAL(stream, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(__toString); + +PHP_HTTP_BEGIN_ARGS(toStream, 1) + PHP_HTTP_ARG_VAL(stream, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(toCallback, 1) + PHP_HTTP_ARG_VAL(callback, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(append, 1) + PHP_HTTP_ARG_VAL(string, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(add, 0) + PHP_HTTP_ARG_VAL(fields, 0) + PHP_HTTP_ARG_VAL(files, 0) +PHP_HTTP_END_ARGS; + + +zend_class_entry *php_http_message_body_class_entry; +zend_function_entry php_http_message_body_method_entry[] = { + PHP_HTTP_MESSAGE_BODY_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_HTTP_MESSAGE_BODY_ME(__toString, ZEND_ACC_PUBLIC) + PHP_MALIAS(HttpMessageBody, toString, __toString, args_for_HttpMessageBody___toString, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_BODY_ME(toStream, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_BODY_ME(toCallback, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_BODY_ME(append, ZEND_ACC_PUBLIC) + PHP_HTTP_MESSAGE_BODY_ME(add, ZEND_ACC_PUBLIC) + EMPTY_FUNCTION_ENTRY +}; +static zend_object_handlers php_http_message_body_object_handlers; + +PHP_MINIT_FUNCTION(http_message_body) +{ + PHP_HTTP_REGISTER_CLASS(http\\message, Body, http_message_body, php_http_object_class_entry, 0); + php_http_message_body_class_entry->create_object = php_http_message_body_object_new; + memcpy(&php_http_message_body_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_http_message_body_object_handlers.clone_obj = php_http_message_body_object_clone; + + return SUCCESS; +} + +zend_object_value php_http_message_body_object_new(zend_class_entry *ce TSRMLS_DC) +{ + return php_http_message_body_object_new_ex(ce, NULL, NULL TSRMLS_CC); +} + +zend_object_value php_http_message_body_object_new_ex(zend_class_entry *ce, php_http_message_body_t *body, php_http_message_body_object_t **ptr TSRMLS_DC) +{ + zend_object_value ov; + php_http_message_body_object_t *o; + + o = ecalloc(1, sizeof(php_http_message_body_object_t)); + zend_object_std_init((zend_object *) o, php_http_message_body_class_entry TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + + if (ptr) { + *ptr = o; + } + + if (body) { + o->body = body; + } + + ov.handle = zend_objects_store_put((zend_object *) o, NULL, php_http_message_body_object_free, NULL TSRMLS_CC); + ov.handlers = &php_http_message_body_object_handlers; + + return ov; +} + +zend_object_value php_http_message_body_object_clone(zval *object TSRMLS_DC) +{ + zend_object_value new_ov; + php_http_message_body_object_t *new_obj = NULL; + php_http_message_body_object_t *old_obj = zend_object_store_get_object(object TSRMLS_CC); + + new_ov = php_http_message_body_object_new_ex(old_obj->zo.ce, php_http_message_body_copy(old_obj->body, NULL, 1), &new_obj); + zend_objects_clone_members(&new_obj->zo, new_ov, &old_obj->zo, Z_OBJ_HANDLE_P(object) TSRMLS_CC); + + return new_ov; +} + +void php_http_message_body_object_free(void *object TSRMLS_DC) +{ + php_http_message_body_object_t *obj = object; + + php_http_message_body_free(&obj->body); + + zend_object_std_dtor((zend_object *) obj TSRMLS_CC); + efree(obj); +} + +PHP_METHOD(HttpMessageBody, __construct) +{ + php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + zval *zstream; + php_stream *stream; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &zstream)) { + php_stream_from_zval(stream, &zstream); + + if (stream) { + if (obj->body) { + php_http_message_body_dtor(obj->body); + } + obj->body = php_http_message_body_init(obj->body, stream TSRMLS_CC); + } + } + } end_error_handling(); +} + +PHP_METHOD(HttpMessageBody, __toString) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + char *str; + size_t len; + + php_http_message_body_to_string(obj->body, &str, &len, 0, 0); + if (str) { + RETURN_STRINGL(str, len, 0); + } + } + RETURN_EMPTY_STRING(); +} + +PHP_METHOD(HttpMessageBody, toStream) +{ + zval *zstream; + long offset = 0, forlen = 0; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r|ll", &zstream, &offset, &forlen)) { + php_stream *stream; + php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + php_stream_from_zval(stream, &zstream); + php_http_message_body_to_stream(obj->body, stream, offset, forlen); + RETURN_TRUE; + } + RETURN_FALSE; +} + +struct fcd { + zval *fcz; + zend_fcall_info *fci; + zend_fcall_info_cache *fcc; +}; + +static size_t pass(void *cb_arg, const char *str, size_t len TSRMLS_DC) +{ + struct fcd *fcd = cb_arg; + zval *zdata; + + MAKE_STD_ZVAL(zdata); + ZVAL_STRINGL(zdata, str, len, 1); + if (SUCCESS == zend_fcall_info_argn(fcd->fci TSRMLS_CC, 2, fcd->fcz, zdata)) { + zend_fcall_info_call(fcd->fci, fcd->fcc, NULL, NULL TSRMLS_CC); + zend_fcall_info_args_clear(fcd->fci, 0); + } + zval_ptr_dtor(&zdata); + return len; +} + +PHP_METHOD(HttpMessageBody, toCallback) +{ + struct fcd fcd; + long offset = 0, forlen = 0; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "f|ll", &fcd.fci, &fcd.fcc, &offset, &forlen)) { + php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + fcd.fcz = getThis(); + Z_ADDREF_P(fcd.fcz); + php_http_message_body_to_callback(obj->body, pass, &fcd, offset, forlen); + zval_ptr_dtor(&fcd.fcz); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessageBody, append) +{ + char *str; + int len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &str, &len)) { + php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_LONG(php_http_message_body_append(obj->body, str, len)); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpMessageBody, add) +{ + HashTable *fields = NULL, *files = NULL; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|hh", &fields, &files)) { + php_http_message_body_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_SUCCESS(php_http_message_body_add(obj->body, fields, files)); + } + RETURN_FALSE; +} +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/php_http_message_body.h b/php_http_message_body.h new file mode 100644 index 0000000..d253ff6 --- /dev/null +++ b/php_http_message_body.h @@ -0,0 +1,81 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_message_body_api.h 292841 2009-12-31 08:48:57Z mike $ */ + +#ifndef PHP_HTTP_MESSAGE_BODY_H +#define PHP_HTTP_MESSAGE_BODY_H + +typedef struct php_http_message_body { + int stream_id; + php_stream_statbuf ssb; +#ifdef ZTS + void ***ts; +#endif +} php_http_message_body_t; + +PHP_HTTP_API php_http_message_body_t *php_http_message_body_init(php_http_message_body_t *body, php_stream *stream TSRMLS_DC); +PHP_HTTP_API php_http_message_body_t *php_http_message_body_copy(php_http_message_body_t *from, php_http_message_body_t *to, zend_bool dup_internal_stream_and_contents TSRMLS_DC); +PHP_HTTP_API STATUS php_http_message_body_add(php_http_message_body_t *body, HashTable *fields, HashTable *files); +PHP_HTTP_API STATUS php_http_message_body_add_field(php_http_message_body_t *body, const char *name, const char *value_str, size_t value_len); +PHP_HTTP_API STATUS php_http_message_body_add_file(php_http_message_body_t *body, const char *name, const char *path, const char *ctype); +PHP_HTTP_API size_t php_http_message_body_append(php_http_message_body_t *body, const char *buf, size_t len); +PHP_HTTP_API void php_http_message_body_to_string(php_http_message_body_t *body, char **buf, size_t *len, off_t offset, size_t forlen); +PHP_HTTP_API void php_http_message_body_to_stream(php_http_message_body_t *body, php_stream *s, off_t offset, size_t forlen); +PHP_HTTP_API void php_http_message_body_to_callback(php_http_message_body_t *body, php_http_pass_callback_t cb, void *cb_arg, off_t offset, size_t forlen); +PHP_HTTP_API void php_http_message_body_dtor(php_http_message_body_t *body); +PHP_HTTP_API void php_http_message_body_free(php_http_message_body_t **body); +PHP_HTTP_API php_stream_statbuf *php_http_message_body_stat(php_http_message_body_t *body); +#define php_http_message_body_size(b) (php_http_message_body_stat((b))->sb.st_size) +#define php_http_message_body_mtime(b) (php_http_message_body_stat((b))->sb.st_mtime) +PHP_HTTP_API char *php_http_message_body_etag(php_http_message_body_t *body); + +static inline php_stream *php_http_message_body_stream(php_http_message_body_t *body) +{ + TSRMLS_FETCH_FROM_CTX(body->ts); + return zend_fetch_resource(NULL TSRMLS_CC, body->stream_id, "stream", NULL, 2, php_file_le_stream(), php_file_le_pstream()); +} + + +typedef struct php_http_message_body_object { + zend_object zo; + php_http_message_body_t *body; +} php_http_message_body_object_t; + +extern zend_class_entry *php_http_message_body_class_entry; +extern zend_function_entry php_http_message_body_method_entry[]; + +extern PHP_MINIT_FUNCTION(http_message_body); + +extern zend_object_value php_http_message_body_object_new(zend_class_entry *ce TSRMLS_DC); +extern zend_object_value php_http_message_body_object_new_ex(zend_class_entry *ce, php_http_message_body_t *body, php_http_message_body_object_t **ptr TSRMLS_DC); +extern zend_object_value php_http_message_body_object_clone(zval *object TSRMLS_DC); +extern void php_http_message_body_object_free(void *object TSRMLS_DC); + +PHP_METHOD(HttpMessageBody, __construct); +PHP_METHOD(HttpMessageBody, __toString); +PHP_METHOD(HttpMessageBody, toStream); +PHP_METHOD(HttpMessageBody, toCallback); +PHP_METHOD(HttpMessageBody, append); +PHP_METHOD(HttpMessageBody, add); + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_message_parser.c b/php_http_message_parser.c new file mode 100644 index 0000000..e2751a4 --- /dev/null +++ b/php_http_message_parser.c @@ -0,0 +1,363 @@ +#include "php_http.h" + +typedef struct php_http_message_parser_state_spec { + php_http_message_parser_state_t state; + unsigned need_data:1; +} php_http_message_parser_state_spec_t; + +static const php_http_message_parser_state_spec_t php_http_message_parser_states[] = { + {PHP_HTTP_MESSAGE_PARSER_STATE_START, 1}, + {PHP_HTTP_MESSAGE_PARSER_STATE_HEADER, 1}, + {PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE, 0}, + {PHP_HTTP_MESSAGE_PARSER_STATE_BODY, 0}, + {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB, 1}, + {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH, 1}, + {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED, 1}, + {PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE, 0}, + {PHP_HTTP_MESSAGE_PARSER_STATE_DONE, 0} +}; + +PHP_HTTP_API php_http_message_parser_t *php_http_message_parser_init(php_http_message_parser_t *parser TSRMLS_DC) +{ + if (!parser) { + parser = emalloc(sizeof(*parser)); + } + memset(parser, 0, sizeof(*parser)); + + TSRMLS_SET_CTX(parser->ts); + + php_http_header_parser_init(&parser->header TSRMLS_CC); + zend_stack_init(&parser->stack); + + return parser; +} + +PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_push(php_http_message_parser_t *parser, unsigned argc, ...) +{ + va_list va_args; + unsigned i; + va_start(va_args, argc); + php_http_message_parser_state_t state; + + for (i = 0; i < argc; ++i) { + state = va_arg(va_args, php_http_message_parser_state_t); + zend_stack_push(&parser->stack, &state, sizeof(state)); + } + va_end(va_args); + + return state; +} + +PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_is(php_http_message_parser_t *parser) +{ + php_http_message_parser_state_t *state; + + if (SUCCESS == zend_stack_top(&parser->stack, (void *) &state)) { + return *state; + } + return PHP_HTTP_MESSAGE_PARSER_STATE_START; +} + +PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_pop(php_http_message_parser_t *parser) +{ + php_http_message_parser_state_t state, *state_ptr; + if (SUCCESS == zend_stack_top(&parser->stack, (void *) &state_ptr)) { + state = *state_ptr; + zend_stack_del_top(&parser->stack); + return state; + } + return PHP_HTTP_MESSAGE_PARSER_STATE_START; +} + +PHP_HTTP_API void php_http_message_parser_dtor(php_http_message_parser_t *parser) +{ + php_http_header_parser_dtor(&parser->header); + zend_stack_destroy(&parser->stack); + if (parser->dechunk) { + php_http_encoding_stream_free(&parser->dechunk TSRMLS_CC); + } + if (parser->inflate) { + php_http_encoding_stream_free(&parser->inflate TSRMLS_CC); + } +} + +PHP_HTTP_API void php_http_message_parser_free(php_http_message_parser_t **parser) +{ + if (*parser) { + php_http_message_parser_dtor(*parser); + efree(*parser); + *parser = NULL; + } +} + + +PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_parser_t *parser, php_http_buffer *buffer, unsigned flags, php_http_message_t **message) +{ + TSRMLS_FETCH_FROM_CTX(parser->ts); + char *str = NULL; + size_t len = 0; + size_t cut = 0; + + while (buffer->used || !php_http_message_parser_states[php_http_message_parser_state_is(parser)].need_data) { +#if 0 + const char *state[] = {"START", "HEADER", "HEADER_DONE", "BODY", "BODY_DUMB", "BODY_LENGTH", "BODY_CHUNK", "BODY_DONE", "DONE"}; + fprintf(stderr, "#MP: %s (%d) %zu\n", + state[php_http_message_parser_state_is(parser)], (*message)->type, buffer->used); +#endif + + switch (php_http_message_parser_state_pop(parser)) + { + case PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE: + return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE); + + case PHP_HTTP_MESSAGE_PARSER_STATE_START: + { + char *ptr = buffer->data; + + while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) { + ++ptr; + } + + php_http_buffer_cut(buffer, 0, ptr - buffer->data); + + if (buffer->used) { + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER); + } + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER: + { + unsigned header_parser_flags = (flags & PHP_HTTP_MESSAGE_PARSER_CLEANUP) ? PHP_HTTP_HEADER_PARSER_CLEANUP : 0; + + switch (php_http_header_parser_parse(&parser->header, buffer, header_parser_flags, &(*message)->hdrs, (php_http_info_callback_t) php_http_message_info_callback, message)) { + case PHP_HTTP_HEADER_PARSER_STATE_FAILURE: + return PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE; + + case PHP_HTTP_HEADER_PARSER_STATE_DONE: + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE); + break; + + default: + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_HEADER); + if (buffer->used) { + return PHP_HTTP_MESSAGE_PARSER_STATE_HEADER; + } + } + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE: + { + zval *h, **h_cl = NULL, **h_cr = NULL, **h_te = NULL; + + if ((h = php_http_message_header(*message, ZEND_STRL("Transfer-Encoding"), 1))) { + zend_hash_update(&(*message)->hdrs, "X-Original-Transfer-Encoding", sizeof("X-Original-Transfer-Encoding"), &h, sizeof(zval *), (void *) &h_te); + zend_hash_del(&(*message)->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding")); + } + if ((h = php_http_message_header(*message, ZEND_STRL("Content-Length"), 1))) { + zend_hash_update(&(*message)->hdrs, "X-Original-Content-Length", sizeof("X-Original-Content-Length"), &h, sizeof(zval *), (void *) &h_cl); + } + if ((h = php_http_message_header(*message, ZEND_STRL("Content-Range"), 1))) { + zend_hash_update(&(*message)->hdrs, "X-Original-Content-Range", sizeof("X-Original-Content-Range"), &h, sizeof(zval *), (void *) &h_cr); + zend_hash_del(&(*message)->hdrs, "Content-Range", sizeof("Content-Range")); + } + + if ((h = php_http_message_header(*message, ZEND_STRL("Content-Encoding"), 1))) { + if (strstr(Z_STRVAL_P(h), "gzip") || strstr(Z_STRVAL_P(h), "x-gzip") || strstr(Z_STRVAL_P(h), "deflate")) { + parser->inflate = php_http_encoding_stream_init(parser->inflate, php_http_encoding_stream_get_inflate_ops(), 0 TSRMLS_CC); + zend_hash_update(&(*message)->hdrs, "X-Original-Content-Encoding", sizeof("X-Original-Content-Encoding"), &h, sizeof(zval *), NULL); + zend_hash_del(&(*message)->hdrs, "Content-Encoding", sizeof("Content-Encoding")); + } else { + zval_ptr_dtor(&h); + } + } + + /* default */ + MAKE_STD_ZVAL(h); + ZVAL_LONG(h, 0); + zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &h, sizeof(zval *), NULL); + + if (h_te) { + if (strstr(Z_STRVAL_PP(h_te), "chunked")) { + parser->dechunk = php_http_encoding_stream_init(parser->dechunk, php_http_encoding_stream_get_dechunk_ops(), 0 TSRMLS_CC); + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED); + break; + } + } + + if (h_cl) { + char *stop; + + parser->body_length = strtoul(Z_STRVAL_PP(h_cl), &stop, 10); + + if (stop != Z_STRVAL_PP(h_cl)) { + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH); + break; + } + } + + if (h_cr) { + ulong total = 0, start = 0, end = 0; + + if (!strncasecmp(Z_STRVAL_PP(h_cr), "bytes", lenof("bytes")) + && ( Z_STRVAL_P(h)[lenof("bytes")] == ':' + || Z_STRVAL_P(h)[lenof("bytes")] == ' ' + || Z_STRVAL_P(h)[lenof("bytes")] == '=' + ) + ) { + char *total_at = NULL, *end_at = NULL; + char *start_at = Z_STRVAL_PP(h_cr) + sizeof("bytes"); + + start = strtoul(start_at, &end_at, 10); + if (end_at) { + end = strtoul(end_at + 1, &total_at, 10); + if (total_at && strncmp(total_at + 1, "*", 1)) { + total = strtoul(total_at + 1, NULL, 10); + } + + if (end >= start && (!total || end < total)) { + parser->body_length = end + 1 - start; + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH); + break; + } + } + } + } + + + if ((*message)->type == PHP_HTTP_REQUEST) { + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE); + } else { + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB); + } + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_BODY: + { + zval *zcl; + + if (parser->inflate) { + char *dec_str = NULL; + size_t dec_len; + + if (SUCCESS != php_http_encoding_stream_update(parser->inflate, str, len, &dec_str, &dec_len TSRMLS_CC)) { + return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE); + } + + if (str != buffer->data) { + STR_FREE(str); + } + str = dec_str; + len = dec_len; + } + + php_stream_write(php_http_message_body_stream(&(*message)->body), str, len); + php_http_buffer_cut(buffer, 0, cut); + + /* keep track */ + MAKE_STD_ZVAL(zcl); + ZVAL_LONG(zcl, php_http_message_body_size(&(*message)->body)); + zend_hash_update(&(*message)->hdrs, "Content-Length", sizeof("Content-Length"), &zcl, sizeof(zval *), NULL); + + if (str != buffer->data) { + STR_FREE(str); + } + str = NULL; + len = 0; + cut = 0; + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB: + { + str = buffer->data; + len = buffer->used; + cut = len; + + php_http_message_parser_state_push(parser, 2, !buffer->used?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH: + { + len = MIN(parser->body_length, buffer->used); + str = buffer->data; + cut = len; + + parser->body_length -= len; + + php_http_message_parser_state_push(parser, 2, !parser->body_length?PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE:PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED: + { + /* + * - pass available data through the dechunk stream + * - pass decoded data along + * - if stream zeroed: + * Y: - cut processed string out of buffer, but leave length of unprocessed dechunk stream data untouched + * - body done + * N: - parse ahaed + */ + size_t dec_len; + char *dec_str = NULL; + + if (SUCCESS != php_http_encoding_stream_update(parser->dechunk, buffer->data, buffer->used, &dec_str, &dec_len TSRMLS_CC)) { + return FAILURE; + } + + str = dec_str; + len = dec_len; + + if (php_http_encoding_stream_done(parser->dechunk)) { + cut = buffer->used - PHP_HTTP_BUFFER_LEN(parser->dechunk->ctx); + php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); + } else { + cut = buffer->used; + php_http_message_parser_state_push(parser, 2, PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); + } + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE: + { + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_DONE); + + if (parser->dechunk) { + char *dec_str; + size_t dec_len; + + if (SUCCESS != php_http_encoding_stream_finish(parser->dechunk, &dec_str, &dec_len TSRMLS_CC)) { + return php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE); + } + php_http_encoding_stream_dtor(parser->dechunk); + + if (dec_str && dec_len) { + str = dec_str; + len = dec_len; + cut = 0; + php_http_message_parser_state_push(parser, 1, PHP_HTTP_MESSAGE_PARSER_STATE_BODY); + } + } + + break; + } + + case PHP_HTTP_MESSAGE_PARSER_STATE_DONE: { + char *ptr = buffer->data; + + while (ptr - buffer->data < buffer->used && PHP_HTTP_IS_CTYPE(space, *ptr)) { + ++ptr; + } + + php_http_buffer_cut(buffer, 0, ptr - buffer->data); + break; + } + } + } + + return php_http_message_parser_state_is(parser); +} diff --git a/php_http_message_parser.h b/php_http_message_parser.h new file mode 100644 index 0000000..cd85eea --- /dev/null +++ b/php_http_message_parser.h @@ -0,0 +1,40 @@ + +#ifndef PHP_HTTP_MESSAGE_PARSER_H +#define PHP_HTTP_MESSAGE_PARSER_H + +typedef enum php_http_message_parser_state { + PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE = FAILURE, + PHP_HTTP_MESSAGE_PARSER_STATE_START = 0, + PHP_HTTP_MESSAGE_PARSER_STATE_HEADER, + PHP_HTTP_MESSAGE_PARSER_STATE_HEADER_DONE, + PHP_HTTP_MESSAGE_PARSER_STATE_BODY, + PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DUMB, + PHP_HTTP_MESSAGE_PARSER_STATE_BODY_LENGTH, + PHP_HTTP_MESSAGE_PARSER_STATE_BODY_CHUNKED, + PHP_HTTP_MESSAGE_PARSER_STATE_BODY_DONE, + PHP_HTTP_MESSAGE_PARSER_STATE_DONE +} php_http_message_parser_state_t; + +#define PHP_HTTP_MESSAGE_PARSER_CLEANUP 0x1 + +typedef struct php_http_message_parser { + php_http_header_parser_t header; + zend_stack stack; + size_t body_length; + php_http_message_t *message; + php_http_encoding_stream_t *dechunk; + php_http_encoding_stream_t *inflate; +#ifdef ZTS + void ***ts; +#endif +} php_http_message_parser_t; + +PHP_HTTP_API php_http_message_parser_t *php_http_message_parser_init(php_http_message_parser_t *parser TSRMLS_DC); +PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_push(php_http_message_parser_t *parser, unsigned argc, ...); +PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_is(php_http_message_parser_t *parser); +PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_state_pop(php_http_message_parser_t *parser); +PHP_HTTP_API void php_http_message_parser_dtor(php_http_message_parser_t *parser); +PHP_HTTP_API void php_http_message_parser_free(php_http_message_parser_t **parser); +PHP_HTTP_API php_http_message_parser_state_t php_http_message_parser_parse(php_http_message_parser_t *parser, php_http_buffer *buffer, unsigned flags, php_http_message_t **message); + +#endif diff --git a/php_http_misc.c b/php_http_misc.c new file mode 100644 index 0000000..8d52382 --- /dev/null +++ b/php_http_misc.c @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $Id $ */ + +#include "php_http.h" + +/* SLEEP */ + +PHP_HTTP_API void php_http_sleep(double s) +{ +#if defined(PHP_WIN32) + Sleep((DWORD) PHP_HTTP_MSEC(s)); +#elif defined(HAVE_USLEEP) + usleep(PHP_HTTP_USEC(s)); +#elif defined(HAVE_NANOSLEEP) + struct timespec req, rem; + + req.tv_sec = (time_t) s; + req.tv_nsec = PHP_HTTP_NSEC(s) % PHP_HTTP_NANOSEC; + + while (nanosleep(&req, &rem) && (errno == EINTR) && (PHP_HTTP_NSEC(rem.tv_sec) + rem.tv_nsec) > PHP_HTTP_NSEC(PHP_HTTP_DIFFSEC))) { + req.tv_sec = rem.tv_sec; + req.tv_nsec = rem.tv_nsec; + } +#else + struct timeval timeout; + + timeout.tv.sec = (time_t) s; + timeout.tv_usec = PHP_HTTP_USEC(s) % PHP_HTTP_MCROSEC; + + select(0, NULL, NULL, NULL, &timeout); +#endif +} + + +/* STRING UTILITIES */ + +int php_http_match(const char *haystack_str, const char *needle_str, int flags) +{ + int result; + + if (flags & PHP_HTTP_MATCH_FULL) { + if (flags & PHP_HTTP_MATCH_CASE) { + result = !strcmp(haystack_str, needle_str); + } else { + result = !strcasecmp(haystack_str, needle_str); + } + } else { + char *found, *haystack = estrdup(haystack_str), *needle = estrdup(needle_str); + + if (flags & PHP_HTTP_MATCH_CASE) { + found = zend_memnstr(haystack, needle, strlen(needle), haystack+strlen(haystack)); + } else { + found = php_stristr(haystack, needle, strlen(haystack), strlen(needle)); + } + + if (found) { + if (!(flags & PHP_HTTP_MATCH_WORD) + || ( (found == haystack || !PHP_HTTP_IS_CTYPE(alnum, *(found - 1))) + && (!*(found + strlen(needle)) || !PHP_HTTP_IS_CTYPE(alnum, *(found + strlen(needle)))) + ) + ) { + result = 1; + } + } + + STR_FREE(haystack); + STR_FREE(needle); + } + + return result; +} + +char *php_http_pretty_key(char *key, size_t key_len, zend_bool uctitle, zend_bool xhyphen) +{ + size_t i; + int wasalpha; + + if (key && key_len) { + if ((wasalpha = PHP_HTTP_IS_CTYPE(alpha, key[0]))) { + key[0] = (char) (uctitle ? PHP_HTTP_TO_CTYPE(upper, key[0]) : PHP_HTTP_TO_CTYPE(lower, key[0])); + } + for (i = 1; i < key_len; i++) { + if (PHP_HTTP_IS_CTYPE(alpha, key[i])) { + key[i] = (char) (((!wasalpha) && uctitle) ? PHP_HTTP_TO_CTYPE(upper, key[i]) : PHP_HTTP_TO_CTYPE(lower, key[i])); + wasalpha = 1; + } else { + if (xhyphen && (key[i] == '_')) { + key[i] = '-'; + } + wasalpha = 0; + } + } + } + return key; +} + + +size_t php_http_boundary(char *buf, size_t buf_len TSRMLS_DC) +{ + return snprintf(buf, buf_len, "%lu%0.9f", (ulong) PHP_HTTP_G->env.request.time, (float) php_combined_lcg(TSRMLS_C)); +} + +/* ARRAYS */ + +int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) +{ + int flags; + char *key = NULL; + HashTable *dst; + zval **data = NULL, **value = (zval **) pDest; + + dst = va_arg(args, HashTable *); + flags = va_arg(args, int); + + if ((!(flags & ARRAY_JOIN_STRONLY)) || hash_key->nKeyLength) { + if ((flags & ARRAY_JOIN_PRETTIFY) && hash_key->nKeyLength) { + key = php_http_pretty_key(estrndup(hash_key->arKey, hash_key->nKeyLength - 1), hash_key->nKeyLength - 1, 1, 1); + zend_hash_find(dst, key, hash_key->nKeyLength, (void *) &data); + } else { + zend_hash_quick_find(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) &data); + } + + Z_ADDREF_P(*value); + if (data) { + if (Z_TYPE_PP(data) != IS_ARRAY) { + convert_to_array(*data); + } + add_next_index_zval(*data, *value); + } else if (key) { + zend_hash_add(dst, key, hash_key->nKeyLength, value, sizeof(zval *), NULL); + } else { + zend_hash_quick_add(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, value, sizeof(zval *), NULL); + } + + if (key) { + efree(key); + } + } + + return ZEND_HASH_APPLY_KEEP; +} + +int php_http_array_apply_merge_func(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key) +{ + int flags; + char *key = NULL; + HashTable *dst; + zval **value = (zval **) pDest; + + dst = va_arg(args, HashTable *); + flags = va_arg(args, int); + + if ((!(flags & ARRAY_JOIN_STRONLY)) || hash_key->nKeyLength) { + Z_ADDREF_P(*value); + if ((flags & ARRAY_JOIN_PRETTIFY) && hash_key->nKeyLength) { + key = php_http_pretty_key(estrndup(hash_key->arKey, hash_key->nKeyLength - 1), hash_key->nKeyLength - 1, 1, 1); + zend_hash_update(dst, key, hash_key->nKeyLength, (void *) value, sizeof(zval *), NULL); + efree(key); + } else { + zend_hash_quick_update(dst, hash_key->arKey, hash_key->nKeyLength, hash_key->h, (void *) value, sizeof(zval *), NULL); + } + } + + return ZEND_HASH_APPLY_KEEP; +} + +/* PASS CALLBACK */ + +PHP_HTTP_API size_t php_http_pass_wrapper(php_http_pass_callback_arg_t *cb, const char *str, size_t len) +{ + TSRMLS_FETCH(); + return cb->cb_zts(cb->cb_arg, str, len TSRMLS_CC); +} + +/* ERROR */ + +static inline int scope_error_handling(long type TSRMLS_DC) +{ + if ((type == E_THROW) || (EG(error_handling) == EH_THROW)) { + return EH_THROW; + } + + if (EG(This) && instanceof_function(Z_OBJCE_P(EG(This)), php_http_object_class_entry)) { + return php_http_object_get_error_handling(EG(This) TSRMLS_CC); + } + + return EH_NORMAL; +} + +void php_http_error(long type TSRMLS_DC, long code, const char *format, ...) +{ + va_list args; + + va_start(args, format); + switch (scope_error_handling(type TSRMLS_CC)) { + case EH_THROW: { + char *message; + zend_class_entry *ce; + + if (EG(exception_class) && instanceof_function(EG(exception_class), php_http_exception_class_entry)) { + ce = EG(exception_class); + } else { + ce = php_http_exception_get_for_code(code); + } + + vspprintf(&message, 0, format, args); + zend_throw_exception(ce, message, code TSRMLS_CC); + efree(message); + break; + } + case EH_NORMAL: + php_verror(NULL, "", type, format, args TSRMLS_CC); + break; + case EH_SUPPRESS: + break; + } + va_end(args); +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/php_http_misc.h b/php_http_misc.h new file mode 100644 index 0000000..2b3d6e9 --- /dev/null +++ b/php_http_misc.h @@ -0,0 +1,467 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_api.h 298891 2010-05-03 08:26:38Z mike $ */ + +#ifndef PHP_HTTP_MISC_H +#define PHP_HTTP_MISC_H + +/* DEFAULTS */ + +/* DATE FORMAT RFC1123 */ +#define PHP_HTTP_DATE_FORMAT "D, d M Y H:i:s \\G\\M\\T" + +/* CR LF */ +#define PHP_HTTP_CRLF "\r\n" + +/* default cache control */ +#define PHP_HTTP_DEFAULT_CACHECONTROL "private, must-revalidate, max-age=0" + +/* max URL length */ +#define PHP_HTTP_URL_MAXLEN 4096 + +/* max request method length */ +#define PHP_HTTP_REQUEST_METHOD_MAXLEN 31 + +/* def URL arg separator */ +#define PHP_HTTP_URL_ARGSEP "&" + +/* send buffer size */ +#define PHP_HTTP_SENDBUF_SIZE 40960 + +/* CURL buffer size */ +#define PHP_HTTP_CURLBUF_SIZE 16384 + +/* SLEEP */ + +#define PHP_HTTP_DIFFSEC (0.001) +#define PHP_HTTP_MLLISEC (1000) +#define PHP_HTTP_MCROSEC (1000 * 1000) +#define PHP_HTTP_NANOSEC (1000 * 1000 * 1000) +#define PHP_HTTP_MSEC(s) ((long)(s * PHP_HTTP_MLLISEC)) +#define PHP_HTTP_USEC(s) ((long)(s * PHP_HTTP_MCROSEC)) +#define PHP_HTTP_NSEC(s) ((long)(s * PHP_HTTP_NANOSEC)) + +PHP_HTTP_API void php_http_sleep(double s); + +/* STRING UTILITIES */ + +#define PHP_HTTP_CHECK_CONTENT_TYPE(ct, action) \ + if (!strchr((ct), '/')) { \ + php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, \ + "Content type \"%s\" does not seem to contain a primary and a secondary part", (ct)); \ + action; \ + } + + +#ifndef STR_SET +# define STR_SET(STR, SET) \ + { \ + STR_FREE(STR); \ + STR = SET; \ + } +#endif + +#define STR_PTR(s) (s?s:"") + +#define lenof(S) (sizeof(S) - 1) + +#define PHP_HTTP_MATCH_LOOSE 0 +#define PHP_HTTP_MATCH_CASE 0x01 +#define PHP_HTTP_MATCH_WORD 0x10 +#define PHP_HTTP_MATCH_FULL 0x20 +#define PHP_HTTP_MATCH_STRICT (PHP_HTTP_ENV_MATCH_CASE|PHP_HTTP_ENV_MATCH_FULL) + +extern int php_http_match(const char *haystack, const char *needle, int flags); + +extern char *php_http_pretty_key(char *key, size_t key_len, zend_bool uctitle, zend_bool xhyphen); +extern size_t php_http_boundary(char *buf, size_t len TSRMLS_DC); + +static inline const char *php_http_locate_str(const char *h, size_t h_len, const char *n, size_t n_len) +{ + const char *p, *e; + + if (n_len && h_len) { + e = h + h_len; + do { + if (*h == *n) { + for (p = n; *p == h[p-n]; ++p) { + if (p == n+n_len-1) { + return h; + } + } + } + } while (h++ != e); + } + + return NULL; +} + +static inline const char *php_http_locate_body(const char *message) +{ + const char *body = NULL, *msg = message; + + while (*msg) { + if (*msg == '\n') { + if (*(msg+1) == '\n') { + body = msg + 2; + break; + } else if (*(msg+1) == '\r' && *(msg+2) == '\n') { + body = msg + 3; + break; + } + } + ++msg; + } + return body; +} + +static inline const char *php_http_locate_eol(const char *line, int *eol_len) +{ + const char *eol = strpbrk(line, "\r\n"); + + if (eol_len) { + *eol_len = eol ? ((eol[0] == '\r' && eol[1] == '\n') ? 2 : 1) : 0; + } + return eol; +} + +static inline const char *php_http_locate_bin_eol(const char *bin, size_t len, int *eol_len) +{ + const char *eol; + + for (eol = bin; eol - bin < len; ++eol) { + if (*eol == '\r' || *eol == '\n') { + *eol_len = eol ? ((eol[0] == '\r' && eol[1] == '\n') ? 2 : 1) : 0; + return eol; + } + } + + return NULL; +} + +/* ZEND */ + +#define INIT_PZVAL_ARRAY(zv, ht) \ + { \ + INIT_PZVAL((zv)); \ + Z_TYPE_P(zv) = IS_ARRAY; \ + Z_ARRVAL_P(zv) = (ht); \ + } + +static inline zval *php_http_zsep(int type, zval *z) +{ + SEPARATE_ARG_IF_REF(z); + if (Z_TYPE_P(z) != type) { + switch (type) { + case IS_NULL: convert_to_null_ex(&z); break; + case IS_BOOL: convert_to_boolean_ex(&z); break; + case IS_LONG: convert_to_long_ex(&z); break; + case IS_DOUBLE: convert_to_double_ex(&z); break; + case IS_STRING: convert_to_string_ex(&z); break; + case IS_ARRAY: convert_to_array_ex(&z); break; + case IS_OBJECT: convert_to_object_ex(&z); break; + } + } + return z; +} + + +/* return bool (v == SUCCESS) */ +#define RETVAL_SUCCESS(v) RETVAL_BOOL(SUCCESS == (v)) +#define RETURN_SUCCESS(v) RETURN_BOOL(SUCCESS == (v)) +/* return object(values) */ +#define RETVAL_OBJECT(o, addref) \ + RETVAL_OBJVAL((o)->value.obj, addref) +#define RETURN_OBJECT(o, addref) \ + RETVAL_OBJECT(o, addref); \ + return +#define RETVAL_OBJVAL(ov, addref) \ + ZVAL_OBJVAL(return_value, ov, addref) +#define RETURN_OBJVAL(ov, addref) \ + RETVAL_OBJVAL(ov, addref); \ + return +#define ZVAL_OBJVAL(zv, ov, addref) \ + (zv)->type = IS_OBJECT; \ + (zv)->value.obj = (ov);\ + if (addref && Z_OBJ_HT_P(zv)->add_ref) { \ + Z_OBJ_HT_P(zv)->add_ref((zv) TSRMLS_CC); \ + } +/* return property */ +#define RETVAL_PROP(CE, n) RETVAL_PROP_EX(CE, getThis(), n) +#define RETURN_PROP(CE, n) RETURN_PROP_EX(CE, getThis(), n) +#define RETVAL_PROP_EX(CE, this, n) \ + { \ + zval *__prop = zend_read_property(CE, this, ZEND_STRL(n), 0 TSRMLS_CC); \ + RETVAL_ZVAL(__prop, 1, 0); \ + } +#define RETURN_PROP_EX(CE, this, n) \ + { \ + zval *__prop = zend_read_property(CE, this, ZEND_STRL(n), 0 TSRMLS_CC); \ + RETURN_ZVAL(__prop, 1, 0); \ + } +#define RETVAL_SPROP(CE, n) \ + { \ + zval *__prop = zend_read_static_property(CE, ZEND_STRL(n), 0 TSRMLS_CC); \ + RETVAL_ZVAL(__prop, 1, 0); \ + } +#define RETURN_SPROP(CE, n) \ + { \ + zval *__prop = zend_read_static_property(CE, ZEND_STRL(n), 0 TSRMLS_CC); \ + RETURN_ZVAL(__prop, 1, 0); \ + } + +#define Z_OBJ_DELREF(z) \ + if (Z_OBJ_HT(z)->del_ref) { \ + Z_OBJ_HT(z)->del_ref(&(z) TSRMLS_CC); \ + } +#define Z_OBJ_ADDREF(z) \ + if (Z_OBJ_HT(z)->add_ref) { \ + Z_OBJ_HT(z)->add_ref(&(z) TSRMLS_CC); \ + } +#define Z_OBJ_DELREF_P(z) \ + if (Z_OBJ_HT_P(z)->del_ref) { \ + Z_OBJ_HT_P(z)->del_ref((z) TSRMLS_CC); \ + } +#define Z_OBJ_ADDREF_P(z) \ + if (Z_OBJ_HT_P(z)->add_ref) { \ + Z_OBJ_HT_P(z)->add_ref((z) TSRMLS_CC); \ + } +#define Z_OBJ_DELREF_PP(z) \ + if (Z_OBJ_HT_PP(z)->del_ref) { \ + Z_OBJ_HT_PP(z)->del_ref(*(z) TSRMLS_CC); \ + } +#define Z_OBJ_ADDREF_PP(z) \ + if (Z_OBJ_HT_PP(z)->add_ref) { \ + Z_OBJ_HT_PP(z)->add_ref(*(z) TSRMLS_CC); \ + } + +#define PHP_HTTP_BEGIN_ARGS_EX(class, method, ret_ref, req_args) ZEND_BEGIN_ARG_INFO_EX(args_for_ ##class## _ ##method , 0, ret_ref, req_args) +#define PHP_HTTP_BEGIN_ARGS_AR(class, method, ret_ref, req_args) ZEND_BEGIN_ARG_INFO_EX(args_for_ ##class## _ ##method , 1, ret_ref, req_args) +#define PHP_HTTP_END_ARGS } +#define PHP_HTTP_EMPTY_ARGS_EX(class, method, ret_ref) PHP_HTTP_BEGIN_ARGS_EX(class, method, ret_ref, 0) PHP_HTTP_END_ARGS +#define PHP_HTTP_ARGS(class, method) args_for_ ##class## _ ##method +#define PHP_HTTP_ARG_VAL(name, pass_ref) ZEND_ARG_INFO(pass_ref, name) +#define PHP_HTTP_ARG_OBJ(class, name, allow_null) ZEND_ARG_OBJ_INFO(0, name, class, allow_null) + +#define EMPTY_FUNCTION_ENTRY {NULL, NULL, NULL, 0, 0} + +#define PHP_MINIT_CALL(func) PHP_MINIT(func)(INIT_FUNC_ARGS_PASSTHRU) +#define PHP_RINIT_CALL(func) PHP_RINIT(func)(INIT_FUNC_ARGS_PASSTHRU) +#define PHP_MSHUTDOWN_CALL(func) PHP_MSHUTDOWN(func)(SHUTDOWN_FUNC_ARGS_PASSTHRU) +#define PHP_RSHUTDOWN_CALL(func) PHP_RSHUTDOWN(func)(SHUTDOWN_FUNC_ARGS_PASSTHRU) + + +#define PHP_HTTP_INI_ENTRY(entry, default, scope, updater, global) \ + STD_PHP_INI_ENTRY(entry, default, scope, updater, global, zend_php_http_globals, php_http_globals) +#define PHP_HTTP_INI_ENTRY_EX(entry, default, scope, updater, displayer, global) \ + STD_PHP_INI_ENTRY_EX(entry, default, scope, updater, global, zend_php_http_globals, php_http_globals, displayer) + +#define PHP_HTTP_REGISTER_CLASS(ns, classname, name, parent, flags) \ + { \ + zend_class_entry ce; \ + memset(&ce, 0, sizeof(zend_class_entry)); \ + INIT_NS_CLASS_ENTRY(ce, #ns, #classname, php_ ##name## _method_entry); \ + php_ ##name## _class_entry = zend_register_internal_class_ex(&ce, parent, NULL TSRMLS_CC); \ + php_ ##name## _class_entry->ce_flags |= flags; \ + } + +#define PHP_HTTP_REGISTER_EXCEPTION(classname, cename, parent) \ + { \ + zend_class_entry ce; \ + memset(&ce, 0, sizeof(zend_class_entry)); \ + INIT_NS_CLASS_ENTRY(ce, "http", #classname, NULL); \ + ce.create_object = NULL; \ + cename = zend_register_internal_class_ex(&ce, parent, NULL TSRMLS_CC); \ + } + +#define ACC_PROP_PRIVATE(ce, flags) ((flags & ZEND_ACC_PRIVATE) && (EG(scope) && ce == EG(scope)) +#define ACC_PROP_PROTECTED(ce, flags) ((flags & ZEND_ACC_PROTECTED) && (zend_check_protected(ce, EG(scope)))) +#define ACC_PROP_PUBLIC(flags) (flags & ZEND_ACC_PUBLIC) +#define ACC_PROP(ce, flags) (ACC_PROP_PUBLIC(flags) || ACC_PROP_PRIVATE(ce, flags) || ACC_PROP_PROTECTED(ce, flags)) + +#ifdef PHP_HTTP_HAVE_CURL +# define PHP_HTTP_DECLARE_ARG_PASS_INFO() \ + ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_2, 0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(1) \ + ZEND_END_ARG_INFO(); \ + \ + ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_3, 0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(1) \ + ZEND_END_ARG_INFO(); \ + \ + ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_4, 0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(1) \ + ZEND_END_ARG_INFO(); \ + \ + ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_5, 0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(1) \ + ZEND_END_ARG_INFO(); + +#else +# define PHP_HTTP_DECLARE_ARG_PASS_INFO() \ + ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_2, 0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(1) \ + ZEND_END_ARG_INFO(); \ +\ + ZEND_BEGIN_ARG_INFO(http_arg_pass_ref_4, 0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(0) \ + ZEND_ARG_PASS_INFO(1) \ + ZEND_END_ARG_INFO(); +#endif /* PHP_HTTP_HAVE_CURL */ + +/* ARRAYS */ + +typedef struct php_http_array_hashkey { + char *str; + uint len; + ulong num; + uint dup:1; + uint type:31; +} php_http_array_hashkey_t; +#define php_http_array_hashkey_init(dup) {NULL, 0, 0, (dup), 0} + +#define FOREACH_VAL(pos, array, val) FOREACH_HASH_VAL(pos, Z_ARRVAL_P(array), val) +#define FOREACH_HASH_VAL(pos, hash, val) \ + for ( zend_hash_internal_pointer_reset_ex(hash, &pos); \ + zend_hash_get_current_data_ex(hash, (void *) &val, &pos) == SUCCESS; \ + zend_hash_move_forward_ex(hash, &pos)) + +#define FOREACH_KEY(pos, array, key) FOREACH_HASH_KEY(pos, Z_ARRVAL_P(array), key) +#define FOREACH_HASH_KEY(pos, hash, _key) \ + for ( zend_hash_internal_pointer_reset_ex(hash, &pos); \ + ((_key).type = zend_hash_get_current_key_ex(hash, &(_key).str, &(_key).len, &(_key).num, (zend_bool) (_key).dup, &pos)) != HASH_KEY_NON_EXISTANT; \ + zend_hash_move_forward_ex(hash, &pos)) \ + +#define FOREACH_KEYVAL(pos, array, key, val) FOREACH_HASH_KEYVAL(pos, Z_ARRVAL_P(array), key, val) +#define FOREACH_HASH_KEYVAL(pos, hash, _key, val) \ + for ( zend_hash_internal_pointer_reset_ex(hash, &pos); \ + ((_key).type = zend_hash_get_current_key_ex(hash, &(_key).str, &(_key).len, &(_key).num, (zend_bool) (_key).dup, &pos)) != HASH_KEY_NON_EXISTANT && \ + zend_hash_get_current_data_ex(hash, (void *) &val, &pos) == SUCCESS; \ + zend_hash_move_forward_ex(hash, &pos)) + +#define array_copy(src, dst) zend_hash_copy(dst, src, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)) +#define ARRAY_JOIN_STRONLY 1 +#define ARRAY_JOIN_PRETTIFY 2 +#define array_join(src, dst, append, flags) zend_hash_apply_with_arguments(src TSRMLS_CC, (append)?php_http_array_apply_append_func:php_http_array_apply_merge_func, 2, dst, (int)flags) + +extern int php_http_array_apply_append_func(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key); +extern int php_http_array_apply_merge_func(void *pDest TSRMLS_DC, int num_args, va_list args, zend_hash_key *hash_key); + +/* PASS CALLBACK */ + +typedef size_t (*php_http_pass_callback_t)(void *cb_arg, const char *str, size_t len); +typedef size_t (*php_http_pass_php_http_buffer_callback_t)(void *cb_arg, php_http_buffer *str); + +typedef struct php_http_pass_callback_arg { + size_t (*cb_zts)(void *cb_arg, const char *str, size_t len TSRMLS_DC); + void *cb_arg; +} php_http_pass_callback_arg_t; + +PHP_HTTP_API size_t php_http_pass_wrapper(php_http_pass_callback_arg_t *cb_arg, const char *str, size_t len); + +/* ERROR */ + +extern void php_http_error(long type TSRMLS_DC, long code, const char *format, ...); + +#define with_error_handling(eh, ec) \ + { \ + zend_error_handling __eh; \ + zend_replace_error_handling((eh), (ec), &__eh TSRMLS_CC); + +#define end_error_handling() \ + zend_restore_error_handling(&__eh TSRMLS_CC); \ + } + +#ifndef E_THROW +# define E_THROW -1 +#endif +#define HE_THROW E_THROW TSRMLS_CC +#define HE_NOTICE E_NOTICE TSRMLS_CC +#define HE_WARNING E_WARNING TSRMLS_CC +#define HE_ERROR E_ERROR TSRMLS_CC + +typedef enum php_http_error { + PHP_HTTP_E_RUNTIME, + PHP_HTTP_E_INVALID_PARAM, + PHP_HTTP_E_HEADER, + PHP_HTTP_E_MALFORMED_HEADERS, + PHP_HTTP_E_REQUEST_METHOD, + PHP_HTTP_E_MESSAGE, + PHP_HTTP_E_MESSAGE_TYPE, + PHP_HTTP_E_ENCODING, + PHP_HTTP_E_REQUEST, + PHP_HTTP_E_REQUEST_POOL, + PHP_HTTP_E_SOCKET, + PHP_HTTP_E_RESPONSE, + PHP_HTTP_E_URL, + PHP_HTTP_E_QUERYSTRING, + PHP_HTTP_E_COOKIE, +} php_http_error_t; + +/* CURL */ + +#define PHP_HTTP_CURL_OPT(OPTION, p) curl_easy_setopt((request->ch), OPTION, (p)) + +#define PHP_HTTP_CURL_OPT_STRING(OPTION, ldiff, obdc) \ + { \ + char *K = #OPTION; \ + PHP_HTTP_CURL_OPT_STRING_EX(K+lenof("CURLOPT_KEY")+ldiff, OPTION, obdc); \ + } +#define PHP_HTTP_CURL_OPT_STRING_EX(keyname, optname, obdc) \ + if (!strcasecmp(key.str, keyname)) { \ + zval *copy = php_http_request_option_cache(request, keyname, strlen(keyname)+1, 0, php_http_zsep(IS_STRING, *param)); \ + if (obdc) { \ + if (SUCCESS != php_check_open_basedir(Z_STRVAL_P(copy) TSRMLS_CC)) { \ + return FAILURE; \ + } \ + } \ + PHP_HTTP_CURL_OPT(optname, Z_STRVAL_P(copy)); \ + zval_ptr_dtor(©); \ + continue; \ + } +#define PHP_HTTP_CURL_OPT_LONG(OPTION, ldiff) \ + { \ + char *K = #OPTION; \ + PHP_HTTP_CURL_OPT_LONG_EX(K+lenof("CURLOPT_KEY")+ldiff, OPTION); \ + } +#define PHP_HTTP_CURL_OPT_LONG_EX(keyname, optname) \ + if (!strcasecmp(key.str, keyname)) { \ + zval *copy = php_http_zsep(IS_LONG, *param); \ + PHP_HTTP_CURL_OPT(optname, Z_LVAL_P(copy)); \ + zval_ptr_dtor(©); \ + continue; \ + } + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/php_http_negotiate.c b/php_http_negotiate.c new file mode 100644 index 0000000..43b4236 --- /dev/null +++ b/php_http_negotiate.c @@ -0,0 +1,180 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_headers_api.h 300300 2010-06-09 07:29:35Z mike $ */ + +#include "php_http.h" + +#ifndef PHP_HTTP_DBG_NEG +# define PHP_HTTP_DBG_NEG 0 +#endif + +char *php_http_negotiate_language_func(const char *test, double *quality, HashTable *supported TSRMLS_DC) +{ + zval **value; + HashPosition pos; + const char *dash_test; + + FOREACH_HASH_VAL(pos, supported, value) { +#if PHP_HTTP_DBG_NEG + fprintf(stderr, "strcasecmp('%s', '%s')\n", Z_STRVAL_PP(value), test); +#endif + if (!strcasecmp(Z_STRVAL_PP(value), test)) { + return Z_STRVAL_PP(value); + } + } + + /* no distinct match found, so try primaries */ + if ((dash_test = strchr(test, '-'))) { + FOREACH_HASH_VAL(pos, supported, value) { + int len = dash_test - test; +#if PHP_HTTP_DBG_NEG + fprintf(stderr, "strncasecmp('%s', '%s', %d)\n", Z_STRVAL_PP(value), test, len); +#endif + if ( (!strncasecmp(Z_STRVAL_PP(value), test, len)) && + ( (Z_STRVAL_PP(value)[len] == '\0') || + (Z_STRVAL_PP(value)[len] == '-'))) { + *quality *= .9; + return Z_STRVAL_PP(value); + } + } + } + + return NULL; +} + + +char *php_http_negotiate_default_func(const char *test, double *quality, HashTable *supported TSRMLS_DC) +{ + zval **value; + HashPosition pos; + (void) quality; + + FOREACH_HASH_VAL(pos, supported, value) { +#if PHP_HTTP_DBG_NEG + fprintf(stderr, "strcasecmp('%s', '%s')\n", Z_STRVAL_PP(value), test); +#endif + if (!strcasecmp(Z_STRVAL_PP(value), test)) { + return Z_STRVAL_PP(value); + } + } + + return NULL; +} + + +static int php_http_negotiate_sort(const void *a, const void *b TSRMLS_DC) +{ + zval result, *first, *second; + + first = *((zval **) (*((Bucket **) a))->pData); + second= *((zval **) (*((Bucket **) b))->pData); + + if (numeric_compare_function(&result, first, second TSRMLS_CC) != SUCCESS) { + return 0; + } + return (Z_LVAL(result) > 0 ? -1 : (Z_LVAL(result) < 0 ? 1 : 0)); +} + + +PHP_HTTP_API HashTable *php_http_negotiate(const char *value, HashTable *supported, php_http_negotiate_func_t neg TSRMLS_DC) +{ + HashTable *result = NULL; + + if (*value) { + zval ex_arr, ex_del, ex_val; + + INIT_PZVAL(&ex_del); + INIT_PZVAL(&ex_arr); + INIT_PZVAL(&ex_val); + ZVAL_STRINGL(&ex_del, ",", 1, 0); + ZVAL_STRING(&ex_val, value, 1); + array_init(&ex_arr); + + php_explode(&ex_del, &ex_val, &ex_arr, INT_MAX); + + if (zend_hash_num_elements(Z_ARRVAL(ex_arr)) > 0) { + int i = 0; + HashPosition pos; + zval **entry, array; + + INIT_PZVAL(&array); + array_init(&array); + + if (!neg) { + neg = php_http_negotiate_default_func; + } + + FOREACH_HASH_VAL(pos, Z_ARRVAL(ex_arr), entry) { + int ident_len; + double quality; + char *selected, *identifier, *freeme; + const char *separator; + +#if PHP_HTTP_DBG_NEG + fprintf(stderr, "Checking %s\n", Z_STRVAL_PP(entry)); +#endif + + if ((separator = strchr(Z_STRVAL_PP(entry), ';'))) { + const char *ptr = separator; + + while (*++ptr && !PHP_HTTP_IS_CTYPE(digit, *ptr) && '.' != *ptr); + + quality = zend_strtod(ptr, NULL); + identifier = estrndup(Z_STRVAL_PP(entry), ident_len = separator - Z_STRVAL_PP(entry)); + } else { + quality = 1000.0 - i++; + identifier = estrndup(Z_STRVAL_PP(entry), ident_len = Z_STRLEN_PP(entry)); + } + freeme = identifier; + + while (PHP_HTTP_IS_CTYPE(space, *identifier)) { + ++identifier; + --ident_len; + } + while (ident_len && PHP_HTTP_IS_CTYPE(space, identifier[ident_len - 1])) { + identifier[--ident_len] = '\0'; + } + + if ((selected = neg(identifier, &quality, supported TSRMLS_CC))) { + /* don't overwrite previously set with higher quality */ + if (!zend_hash_exists(Z_ARRVAL(array), selected, strlen(selected) + 1)) { + add_assoc_double(&array, selected, quality); + } + } + + efree(freeme); + } + + result = Z_ARRVAL(array); + zend_hash_sort(result, zend_qsort, php_http_negotiate_sort, 0 TSRMLS_CC); + } + + zval_dtor(&ex_arr); + zval_dtor(&ex_val); + } + + return result; +} + + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + + diff --git a/php_http_negotiate.h b/php_http_negotiate.h new file mode 100644 index 0000000..d8814fa --- /dev/null +++ b/php_http_negotiate.h @@ -0,0 +1,88 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_headers_api.h 300300 2010-06-09 07:29:35Z mike $ */ + +#ifndef PHP_HTTP_NEGOTIATE_H +#define PHP_HTTP_NEGOTIATE_H + +typedef char *(*php_http_negotiate_func_t)(const char *test, double *quality, HashTable *supported TSRMLS_DC); + +extern char *php_http_negotiate_language_func(const char *test, double *quality, HashTable *supported TSRMLS_DC); +extern char *php_http_negotiate_default_func(const char *test, double *quality, HashTable *supported TSRMLS_DC); + +PHP_HTTP_API HashTable *php_http_negotiate(const char *value, HashTable *supported, php_http_negotiate_func_t neg TSRMLS_DC); + +static inline HashTable *php_http_negotiate_language(HashTable *supported TSRMLS_DC) +{ + HashTable *result = NULL; + char *value = php_http_env_get_request_header(ZEND_STRL("Accept-Language") TSRMLS_CC); + + if (value) { + result = php_http_negotiate(value, supported, php_http_negotiate_language_func TSRMLS_CC); + } + STR_FREE(value); + + return result; +} + +static inline HashTable *php_http_negotiate_encoding(HashTable *supported TSRMLS_DC) +{ + HashTable *result = NULL; + char *value = php_http_env_get_request_header(ZEND_STRL("Accept-Encoding") TSRMLS_CC); + + if (value) { + result = php_http_negotiate(value, supported, NULL TSRMLS_CC); + } + STR_FREE(value); + + return result; +} + +static inline HashTable *php_http_negotiate_charset(HashTable *supported TSRMLS_DC) +{ + HashTable *result = NULL; + char *value = php_http_env_get_request_header(ZEND_STRL("Accept-Charset") TSRMLS_CC); + + if (value) { + result = php_http_negotiate(value, supported, NULL TSRMLS_CC); + } + STR_FREE(value); + + return result; +} + +static inline HashTable *php_http_negotiate_content_type(HashTable *supported TSRMLS_DC) +{ + HashTable *result = NULL; + char *value = php_http_env_get_request_header(ZEND_STRL("Accept") TSRMLS_CC); + + if (value) { + result = php_http_negotiate(value, supported, NULL TSRMLS_CC); + } + STR_FREE(value); + + return result; +} + + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_object.c b/php_http_object.c new file mode 100644 index 0000000..5a1a279 --- /dev/null +++ b/php_http_object.c @@ -0,0 +1,220 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_api.c 300299 2010-06-09 06:23:16Z mike $ */ + + +#include "php_http.h" + +STATUS php_http_new(zend_object_value *ov, zend_class_entry *ce, php_http_new_t create, zend_class_entry *parent_ce, void *intern_ptr, void **obj_ptr TSRMLS_DC) +{ + if (!ce) { + ce = parent_ce; + } else if (parent_ce && !instanceof_function(ce, parent_ce TSRMLS_CC)) { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "Class %s does not extend %s", ce->name, parent_ce->name); + return FAILURE; + } + + *ov = create(ce, intern_ptr, obj_ptr TSRMLS_CC); + return SUCCESS; +} + +PHP_HTTP_API zend_error_handling_t php_http_object_get_error_handling(zval *object TSRMLS_DC) +{ + zval *zeh, *lzeh; + long eh; + + zeh = zend_read_property(Z_OBJCE_P(object), object, ZEND_STRL("errorHandling"), 0 TSRMLS_CC); + if (Z_TYPE_P(zeh) != IS_NULL) { + lzeh = php_http_zsep(IS_LONG, zeh); + eh = Z_LVAL_P(lzeh); + zval_ptr_dtor(&lzeh); + return eh; + } + zeh = zend_read_static_property(php_http_object_class_entry, ZEND_STRL("defaultErrorHandling"), 0 TSRMLS_CC); + if (Z_TYPE_P(zeh) != IS_NULL) { + lzeh = php_http_zsep(IS_LONG, zeh); + eh = Z_LVAL_P(lzeh); + zval_ptr_dtor(&lzeh); + return eh; + } + return EH_NORMAL; +} + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpObject, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpObject, method, 0) +#define PHP_HTTP_OBJECT_ME(method, visibility) PHP_ME(HttpObject, method, PHP_HTTP_ARGS(HttpObject, method), visibility) + +PHP_HTTP_BEGIN_ARGS(factory, 1) + PHP_HTTP_ARG_VAL(class_name, 0) + PHP_HTTP_ARG_VAL(ctor_args, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(setErrorHandling, 1) + PHP_HTTP_ARG_VAL(eh, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getErrorHandling); + +PHP_HTTP_BEGIN_ARGS(setDefaultErrorHandling, 1) + PHP_HTTP_ARG_VAL(eh, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getDefaultErrorHandling); + +zend_class_entry *php_http_object_class_entry; +zend_function_entry php_http_object_method_entry[] = { + PHP_HTTP_OBJECT_ME(factory, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + + PHP_HTTP_OBJECT_ME(setErrorHandling, ZEND_ACC_PUBLIC) + PHP_HTTP_OBJECT_ME(getErrorHandling, ZEND_ACC_PUBLIC) + PHP_HTTP_OBJECT_ME(setDefaultErrorHandling, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_HTTP_OBJECT_ME(getDefaultErrorHandling, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + + EMPTY_FUNCTION_ENTRY +}; + +zend_object_value php_http_object_new(zend_class_entry *ce TSRMLS_DC) +{ + return php_http_object_new_ex(ce, NULL, NULL TSRMLS_CC); +} + +zend_object_value php_http_object_new_ex(zend_class_entry *ce, void *nothing, php_http_object_t **ptr TSRMLS_DC) +{ + zend_object_value ov; + php_http_object_t *o; + + o = ecalloc(1, sizeof(php_http_object_t)); + zend_object_std_init((zend_object *)o, ce TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + + if (ptr) { + *ptr = o; + } + + ov.handle = zend_objects_store_put(o, NULL, (zend_objects_free_object_storage_t) zend_objects_free_object_storage, NULL TSRMLS_CC); + ov.handlers = zend_get_std_object_handlers(); + + return ov; +} + +PHP_METHOD(HttpObject, factory) +{ + zval *ctor_args = NULL; + zend_class_entry *class_entry = NULL; + zval *object_ctor; + zend_fcall_info fci; + zend_fcall_info_cache fcc; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "C|a/!", &class_entry, &ctor_args)) { + object_init_ex(return_value, class_entry); + + MAKE_STD_ZVAL(object_ctor); + array_init(object_ctor); + + Z_ADDREF_P(return_value); + add_next_index_zval(object_ctor, return_value); + add_next_index_stringl(object_ctor, ZEND_STRL("__construct"), 1); + + zend_fcall_info_init(object_ctor, 0, &fci, &fcc, NULL, NULL TSRMLS_CC); + zend_fcall_info_call(&fci, &fcc, NULL, ctor_args TSRMLS_CC); + + zval_ptr_dtor(&object_ctor); + } + } end_error_handling(); +} + +PHP_METHOD(HttpObject, getErrorHandling) +{ + RETURN_PROP(php_http_object_class_entry, "errorHandling"); +} + +PHP_METHOD(HttpObject, setErrorHandling) +{ + long eh; + zval *old; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &eh)) { + RETURN_FALSE; + } + + switch (eh) { + case EH_NORMAL: + case EH_SUPPRESS: + case EH_THROW: + break; + default: + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "unknown error handling code (%ld)", eh); + RETURN_FALSE; + } + + old = zend_read_property(php_http_object_class_entry, getThis(), ZEND_STRL("errorHandling"), 0 TSRMLS_CC); + Z_ADDREF_P(old); + zend_update_property_long(php_http_object_class_entry, getThis(), ZEND_STRL("errorHandling"), eh TSRMLS_CC); + RETURN_ZVAL(old, 0, 0); +} + +PHP_METHOD(HttpObject, getDefaultErrorHandling) +{ + RETURN_SPROP(php_http_object_class_entry, "defaultErrorHandling"); +} + +PHP_METHOD(HttpObject, setDefaultErrorHandling) +{ + long eh; + zval *old; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &eh)) { + RETURN_FALSE; + } + + switch (eh) { + case EH_NORMAL: + case EH_SUPPRESS: + case EH_THROW: + break; + default: + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "unknown error handling code (%ld)", eh); + RETURN_FALSE; + } + + old = zend_read_static_property(php_http_object_class_entry, ZEND_STRL("defaultErrorHandling"), 0 TSRMLS_CC); + Z_ADDREF_P(old); + zend_update_static_property_long(php_http_object_class_entry, ZEND_STRL("defaultErrorHandling"), eh TSRMLS_CC); + RETURN_ZVAL(old, 0, 1); +} +PHP_MINIT_FUNCTION(http_object) +{ + PHP_HTTP_REGISTER_CLASS(http, Object, http_object, NULL, ZEND_ACC_ABSTRACT); + php_http_object_class_entry->create_object = php_http_object_new; + + zend_declare_property_null(php_http_object_class_entry, ZEND_STRL("defaultErrorHandling"), (ZEND_ACC_STATIC|ZEND_ACC_PROTECTED) TSRMLS_CC); + zend_declare_property_null(php_http_object_class_entry, ZEND_STRL("errorHandling"), ZEND_ACC_PROTECTED TSRMLS_CC); + + zend_declare_class_constant_long(php_http_object_class_entry, ZEND_STRL("EH_NORMAL"), EH_NORMAL TSRMLS_CC); + zend_declare_class_constant_long(php_http_object_class_entry, ZEND_STRL("EH_SUPPRESS"), EH_SUPPRESS TSRMLS_CC); + zend_declare_class_constant_long(php_http_object_class_entry, ZEND_STRL("EH_THROW"), EH_THROW TSRMLS_CC); + + return SUCCESS; +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_object.h b/php_http_object.h new file mode 100644 index 0000000..c4ede0e --- /dev/null +++ b/php_http_object.h @@ -0,0 +1,53 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_api.c 300299 2010-06-09 06:23:16Z mike $ */ + +#ifndef PHP_HTTP_OBJECT_H +#define PHP_HTTP_OBJECT_H + +typedef zend_object_value (*php_http_new_t)(zend_class_entry *ce, void *, void ** TSRMLS_DC); + +extern STATUS php_http_new(zend_object_value *ov, zend_class_entry *ce, php_http_new_t create, zend_class_entry *parent_ce, void *intern_ptr, void **obj_ptr TSRMLS_DC); + +typedef struct php_http_object { + zend_object zo; +} php_http_object_t; + +extern zend_class_entry *php_http_object_class_entry; +extern zend_function_entry php_http_object_method_entry[]; + +extern PHP_MINIT_FUNCTION(http_object); + +extern zend_object_value php_http_object_new(zend_class_entry *ce TSRMLS_DC); +extern zend_object_value php_http_object_new_ex(zend_class_entry *ce, void *nothing, php_http_object_t **ptr TSRMLS_DC); + +PHP_HTTP_API zend_error_handling_t php_http_object_get_error_handling(zval *object TSRMLS_DC); + +PHP_METHOD(HttpObject, factory); +PHP_METHOD(HttpObject, setErrorHandling); +PHP_METHOD(HttpObject, getErrorHandling); +PHP_METHOD(HttpObject, setDefaultErrorHandling); +PHP_METHOD(HttpObject, getDefaultErrorHandling); + +#endif + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_params.c b/php_http_params.c new file mode 100644 index 0000000..2cc9a09 --- /dev/null +++ b/php_http_params.c @@ -0,0 +1,277 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_api.c 300299 2010-06-09 06:23:16Z mike $ */ + +#include "php_http.h" + +PHP_HTTP_API void php_http_params_parse_default_func(void *arg, const char *key, int keylen, const char *val, int vallen TSRMLS_DC) +{ + char *kdup; + zval tmp, *entry; + HashTable *ht = (HashTable *) arg; + + if (ht) { + INIT_PZVAL_ARRAY(&tmp, ht); + + if (vallen) { + MAKE_STD_ZVAL(entry); + array_init(entry); + if (keylen) { + kdup = estrndup(key, keylen); + add_assoc_stringl_ex(entry, kdup, keylen + 1, (char *) val, vallen, 1); + efree(kdup); + } else { + add_next_index_stringl(entry, (char *) val, vallen, 1); + } + add_next_index_zval(&tmp, entry); + } else { + add_next_index_stringl(&tmp, (char *) key, keylen, 1); + } + } +} + +PHP_HTTP_API STATUS php_http_params_parse(const char *param, int flags, php_http_params_parse_func_t cb, void *cb_arg TSRMLS_DC) +{ +#define ST_QUOTE 1 +#define ST_VALUE 2 +#define ST_KEY 3 +#define ST_ASSIGN 4 +#define ST_ADD 5 + + int st = ST_KEY, keylen = 0, vallen = 0; + char *s, *c, *key = NULL, *val = NULL; + + if (!cb) { + cb = php_http_params_parse_default_func; + } + + for(c = s = estrdup(param);;) { + continued: +#if 0 + { + char *tk = NULL, *tv = NULL; + + if (key) { + if (keylen) { + tk= estrndup(key, keylen); + } else { + tk = ecalloc(1, 7); + memcpy(tk, key, 3); + tk[3]='.'; tk[4]='.'; tk[5]='.'; + } + } + if (val) { + if (vallen) { + tv = estrndup(val, vallen); + } else { + tv = ecalloc(1, 7); + memcpy(tv, val, 3); + tv[3]='.'; tv[4]='.'; tv[5]='.'; + } + } + fprintf(stderr, "[%6s] %c \"%s=%s\"\n", + ( + st == ST_QUOTE ? "QUOTE" : + st == ST_VALUE ? "VALUE" : + st == ST_KEY ? "KEY" : + st == ST_ASSIGN ? "ASSIGN" : + st == ST_ADD ? "ADD": + "HUH?" + ), *c?*c:'0', tk, tv + ); + STR_FREE(tk); STR_FREE(tv); + } +#endif + switch (st) { + case ST_QUOTE: + quote: + if (*c == '"') { + if (*(c-1) == '\\') { + memmove(c-1, c, strlen(c)+1); + goto quote; + } else { + goto add; + } + } else { + if (!val) { + val = c; + } + if (!*c) { + --val; + st = ST_ADD; + } + } + break; + + case ST_VALUE: + switch (*c) { + case '"': + if (!val) { + st = ST_QUOTE; + } + break; + + case ' ': + break; + + case ';': + case '\0': + goto add; + break; + case ',': + if (flags & PHP_HTTP_PARAMS_ALLOW_COMMA) { + goto add; + } + default: + if (!val) { + val = c; + } + break; + } + break; + + case ST_KEY: + switch (*c) { + case ',': + if (flags & PHP_HTTP_PARAMS_ALLOW_COMMA) { + goto allow_comma; + } + case '\r': + case '\n': + case '\t': + case '\013': + case '\014': + goto failure; + break; + + case ' ': + if (key) { + keylen = c - key; + st = ST_ASSIGN; + } + break; + + case ';': + case '\0': + allow_comma: + if (key) { + keylen = c-- - key; + st = ST_ADD; + } + break; + + case ':': + if (!(flags & PHP_HTTP_PARAMS_COLON_SEPARATOR)) { + goto not_separator; + } + if (key) { + keylen = c - key; + st = ST_VALUE; + } else { + goto failure; + } + break; + + case '=': + if (flags & PHP_HTTP_PARAMS_COLON_SEPARATOR) { + goto not_separator; + } + if (key) { + keylen = c - key; + st = ST_VALUE; + } else { + goto failure; + } + break; + + default: + not_separator: + if (!key) { + key = c; + } + break; + } + break; + + case ST_ASSIGN: + if (*c == '=') { + st = ST_VALUE; + } else if (!*c || *c == ';' || ((flags & PHP_HTTP_PARAMS_ALLOW_COMMA) && *c == ',')) { + st = ST_ADD; + } else if (*c != ' ') { + goto failure; + } + break; + + case ST_ADD: + add: + if (val) { + vallen = c - val; + if (st != ST_QUOTE) { + while (val[vallen-1] == ' ') --vallen; + } + } else { + val = ""; + vallen = 0; + } + + cb(cb_arg, key, keylen, val, vallen TSRMLS_CC); + + st = ST_KEY; + key = val = NULL; + keylen = vallen = 0; + break; + } + if (*c) { + ++c; + } else if (st == ST_ADD) { + goto add; + } else { + break; + } + } + + efree(s); + return SUCCESS; + +failure: + if (flags & PHP_HTTP_PARAMS_RAISE_ERROR) { + php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Unexpected character (%c) at pos %tu of %zu", *c, c-s, strlen(s)); + } + if (flags & PHP_HTTP_PARAMS_ALLOW_FAILURE) { + if (st == ST_KEY) { + if (key) { + keylen = c - key; + } else { + key = c; + } + } else { + --c; + } + st = ST_ADD; + goto continued; + } + efree(s); + return FAILURE; +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_params.h b/php_http_params.h new file mode 100644 index 0000000..c570017 --- /dev/null +++ b/php_http_params.h @@ -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 | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_api.h 298891 2010-05-03 08:26:38Z mike $ */ + +#ifndef PHP_HTTP_PARAMS_H +#define PHP_HTTP_PARAMS_H + +#define PHP_HTTP_PARAMS_ALLOW_COMMA 0x01 +#define PHP_HTTP_PARAMS_ALLOW_FAILURE 0x02 +#define PHP_HTTP_PARAMS_RAISE_ERROR 0x04 +#define PHP_HTTP_PARAMS_DEFAULT (PHP_HTTP_PARAMS_ALLOW_COMMA|PHP_HTTP_PARAMS_ALLOW_FAILURE|PHP_HTTP_PARAMS_RAISE_ERROR) +#define PHP_HTTP_PARAMS_COLON_SEPARATOR 0x10 + +typedef void (*php_http_params_parse_func_t)(void *cb_arg, const char *key, int keylen, const char *val, int vallen TSRMLS_DC); + +PHP_HTTP_API void php_http_params_parse_default_func(void *ht, const char *key, int keylen, const char *val, int vallen TSRMLS_DC); +PHP_HTTP_API STATUS php_http_params_parse(const char *params, int flags, php_http_params_parse_func_t cb, void *cb_arg TSRMLS_DC); + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_persistent_handle.c b/php_http_persistent_handle.c new file mode 100644 index 0000000..a5f9689 --- /dev/null +++ b/php_http_persistent_handle.c @@ -0,0 +1,385 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_persistent_handle_api.c 292841 2009-12-31 08:48:57Z mike $ */ + +#include "php_http.h" + +#ifndef PHP_HTTP_DEBUG_PHANDLES +# define PHP_HTTP_DEBUG_PHANDLES 0 +#endif +#if PHP_HTTP_DEBUG_PHANDLES +# undef inline +# define inline +#endif + +static HashTable php_http_persistent_handles_hash; +#ifdef ZTS +# define LOCK() tsrm_mutex_lock(php_http_persistent_handles_lock) +# define UNLOCK() tsrm_mutex_unlock(php_http_persistent_handles_lock) +static MUTEX_T php_http_persistent_handles_lock; +#else +# define LOCK() +# define UNLOCK() +#endif + +typedef struct php_http_persistent_handle_list { + HashTable free; + ulong used; +} php_http_persistent_handle_list_t; + +typedef struct php_http_persistent_handle_provider { + php_http_persistent_handle_list_t list; /* "ident" => array(handles) entries */ + php_http_persistent_handle_ctor_t ctor; + php_http_persistent_handle_dtor_t dtor; + php_http_persistent_handle_copy_t copy; +} php_http_persistent_handle_provider_t; + +static inline php_http_persistent_handle_list_t *php_http_persistent_handle_list_init(php_http_persistent_handle_list_t *list) +{ + int free_list; + + if ((free_list = !list)) { + list = pemalloc(sizeof(php_http_persistent_handle_list_t), 1); + } + + list->used = 0; + + if (SUCCESS != zend_hash_init(&list->free, 0, NULL, NULL, 1)) { + if (free_list) { + pefree(list, 1); + } + list = NULL; + } + + return list; +} + +static inline void php_http_persistent_handle_list_dtor(php_http_persistent_handle_list_t *list, php_http_persistent_handle_dtor_t dtor) +{ + HashPosition pos; + void **handle; + +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "LSTDTOR: %p\n", list); +#endif + FOREACH_HASH_VAL(pos, &list->free, handle) { +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "DESTROY: %p\n", *handle); +#endif + + dtor(*handle); + } + zend_hash_destroy(&list->free); +} + +static inline void php_http_persistent_handle_list_free(php_http_persistent_handle_list_t **list, php_http_persistent_handle_dtor_t dtor) +{ + php_http_persistent_handle_list_dtor(*list, dtor); +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "LSTFREE: %p\n", *list); +#endif + pefree(*list, 1); + *list = NULL; +} + +static inline php_http_persistent_handle_list_t *php_http_persistent_handle_list_find(php_http_persistent_handle_provider_t *provider TSRMLS_DC) +{ + php_http_persistent_handle_list_t **list, *new_list; + + if (SUCCESS == zend_hash_quick_find(&provider->list.free, PHP_HTTP_G->persistent_handle.ident.s, PHP_HTTP_G->persistent_handle.ident.l, PHP_HTTP_G->persistent_handle.ident.h, (void *) &list)) { +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "LSTFIND: %p\n", *list); +#endif + return *list; + } + + if ((new_list = php_http_persistent_handle_list_init(NULL))) { + if (SUCCESS == zend_hash_quick_add(&provider->list.free, PHP_HTTP_G->persistent_handle.ident.s, PHP_HTTP_G->persistent_handle.ident.l, PHP_HTTP_G->persistent_handle.ident.h, (void *) &new_list, sizeof(php_http_persistent_handle_list_t *), (void *) &list)) { +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "LSTFIND: %p (new)\n", *list); +#endif + return *list; + } + php_http_persistent_handle_list_free(&new_list, provider->dtor); + } + + return NULL; +} + +static inline STATUS php_http_persistent_handle_do_acquire(php_http_persistent_handle_provider_t *provider, void **handle TSRMLS_DC) +{ + ulong index; + void **handle_ptr; + php_http_persistent_handle_list_t *list; + + if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) { + zend_hash_internal_pointer_end(&list->free); + if (HASH_KEY_NON_EXISTANT != zend_hash_get_current_key(&list->free, NULL, &index, 0) && SUCCESS == zend_hash_get_current_data(&list->free, (void *) &handle_ptr)) { + *handle = *handle_ptr; + zend_hash_index_del(&list->free, index); + } else { + *handle = provider->ctor(); + } + + if (*handle) { + ++provider->list.used; + ++list->used; + return SUCCESS; + } + } else { + *handle = NULL; + } + + return FAILURE; +} + +static inline STATUS php_http_persistent_handle_do_release(php_http_persistent_handle_provider_t *provider, void **handle TSRMLS_DC) +{ + php_http_persistent_handle_list_t *list; + + if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) { + if (provider->list.used >= PHP_HTTP_G->persistent_handle.limit) { + provider->dtor(*handle); + } else { + if (SUCCESS != zend_hash_next_index_insert(&list->free, (void *) handle, sizeof(void *), NULL)) { + return FAILURE; + } + } + + *handle = NULL; + --provider->list.used; + --list->used; + return SUCCESS; + } + + return FAILURE; +} + +static inline STATUS php_http_persistent_handle_do_accrete(php_http_persistent_handle_provider_t *provider, void *old_handle, void **new_handle TSRMLS_DC) +{ + php_http_persistent_handle_list_t *list; + + if (provider->copy && (*new_handle = provider->copy(old_handle))) { + if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) { + ++list->used; + } + ++provider->list.used; + return SUCCESS; + } + return FAILURE; +} + +static void php_http_persistent_handles_hash_dtor(void *p) +{ + php_http_persistent_handle_provider_t *provider = (php_http_persistent_handle_provider_t *) p; + php_http_persistent_handle_list_t **list, *list_tmp; + HashPosition pos; + + FOREACH_HASH_VAL(pos, &provider->list.free, list) { + /* fix shutdown crash in PHP4 */ + list_tmp = *list; + php_http_persistent_handle_list_free(&list_tmp, provider->dtor); + } + + zend_hash_destroy(&provider->list.free); +} + +PHP_MINIT_FUNCTION(http_persistent_handle) +{ + zend_hash_init(&php_http_persistent_handles_hash, 0, NULL, php_http_persistent_handles_hash_dtor, 1); +#ifdef ZTS + php_http_persistent_handles_lock = tsrm_mutex_alloc(); +#endif + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(http_persistent_handle) +{ + zend_hash_destroy(&php_http_persistent_handles_hash); +#ifdef ZTS + tsrm_mutex_free(php_http_persistent_handles_lock); +#endif + return SUCCESS; +} + +PHP_HTTP_API STATUS php_http_persistent_handle_provide(const char *name_str, size_t name_len, php_http_persistent_handle_ctor_t ctor, php_http_persistent_handle_dtor_t dtor, php_http_persistent_handle_copy_t copy) +{ + STATUS status = FAILURE; + php_http_persistent_handle_provider_t provider; + + LOCK(); + if (php_http_persistent_handle_list_init(&provider.list)) { + provider.ctor = ctor; + provider.dtor = dtor; + provider.copy = copy; + +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "PROVIDE: %s\n", name_str); +#endif + + if (SUCCESS == zend_hash_add(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider, sizeof(php_http_persistent_handle_provider_t), NULL)) { + status = SUCCESS; + } + } + UNLOCK(); + + return status; +} + +PHP_HTTP_API STATUS php_http_persistent_handle_acquire(const char *name_str, size_t name_len, void **handle TSRMLS_DC) +{ + STATUS status = FAILURE; + php_http_persistent_handle_provider_t *provider; + + *handle = NULL; + LOCK(); + if (SUCCESS == zend_hash_find(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider)) { + status = php_http_persistent_handle_do_acquire(provider, handle TSRMLS_CC); + } + UNLOCK(); + +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "ACQUIRE: %p (%s)\n", *handle, name_str); +#endif + + return status; +} + +PHP_HTTP_API STATUS php_http_persistent_handle_release(const char *name_str, size_t name_len, void **handle TSRMLS_DC) +{ + STATUS status = FAILURE; + php_http_persistent_handle_provider_t *provider; +#if PHP_HTTP_DEBUG_PHANDLES + void *handle_tmp = *handle; +#endif + + LOCK(); + if (SUCCESS == zend_hash_find(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider)) { + status = php_http_persistent_handle_do_release(provider, handle TSRMLS_CC); + } + UNLOCK(); + +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "RELEASE: %p (%s)\n", handle_tmp, name_str); +#endif + + return status; +} + +PHP_HTTP_API STATUS php_http_persistent_handle_accrete(const char *name_str, size_t name_len, void *old_handle, void **new_handle TSRMLS_DC) +{ + STATUS status = FAILURE; + php_http_persistent_handle_provider_t *provider; + + *new_handle = NULL; + LOCK(); + if (SUCCESS == zend_hash_find(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider)) { + status = php_http_persistent_handle_do_accrete(provider, old_handle, new_handle TSRMLS_CC); + } + UNLOCK(); + +#if PHP_HTTP_DEBUG_PHANDLES + fprintf(stderr, "ACCRETE: %p > %p (%s)\n", old_handle, *new_handle, name_str); +#endif + + return status; +} + +PHP_HTTP_API void php_http_persistent_handle_cleanup(const char *name_str, size_t name_len, int current_ident_only TSRMLS_DC) +{ + php_http_persistent_handle_provider_t *provider; + php_http_persistent_handle_list_t *list, **listp; + HashPosition pos1, pos2; + + LOCK(); + if (name_str && name_len) { + if (SUCCESS == zend_hash_find(&php_http_persistent_handles_hash, name_str, name_len+1, (void *) &provider)) { + if (current_ident_only) { + if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) { + php_http_persistent_handle_list_dtor(list, provider->dtor); + php_http_persistent_handle_list_init(list); + } + } else { + FOREACH_HASH_VAL(pos1, &provider->list.free, listp) { + php_http_persistent_handle_list_dtor(*listp, provider->dtor); + php_http_persistent_handle_list_init(*listp); + } + } + } + } else { + FOREACH_HASH_VAL(pos1, &php_http_persistent_handles_hash, provider) { + if (current_ident_only) { + if ((list = php_http_persistent_handle_list_find(provider TSRMLS_CC))) { + php_http_persistent_handle_list_dtor(list, provider->dtor); + php_http_persistent_handle_list_init(list); + } + } else { + FOREACH_HASH_VAL(pos2, &provider->list.free, listp) { + php_http_persistent_handle_list_dtor(*listp, provider->dtor); + php_http_persistent_handle_list_init(*listp); + } + } + } + } + UNLOCK(); +} + +PHP_HTTP_API HashTable *php_http_persistent_handle_statall(HashTable *ht TSRMLS_DC) +{ + zval *zentry[2]; + HashPosition pos1, pos2; + php_http_array_hashkey_t key1 = php_http_array_hashkey_init(0), key2 = php_http_array_hashkey_init(0); + php_http_persistent_handle_provider_t *provider; + php_http_persistent_handle_list_t **list; + + LOCK(); + if (zend_hash_num_elements(&php_http_persistent_handles_hash)) { + if (!ht) { + ALLOC_HASHTABLE(ht); + zend_hash_init(ht, 0, NULL, ZVAL_PTR_DTOR, 0); + } + + FOREACH_HASH_KEYVAL(pos1, &php_http_persistent_handles_hash, key1, provider) { + MAKE_STD_ZVAL(zentry[0]); + array_init(zentry[0]); + + FOREACH_HASH_KEYVAL(pos2, &provider->list.free, key2, list) { + MAKE_STD_ZVAL(zentry[1]); + array_init(zentry[1]); + add_assoc_long_ex(zentry[1], ZEND_STRS("used"), (*list)->used); + add_assoc_long_ex(zentry[1], ZEND_STRS("free"), zend_hash_num_elements(&(*list)->free)); + + /* use zend_hash_* not add_assoc_* (which is zend_symtable_*) as we want a string even for numbers */ + zend_hash_add(Z_ARRVAL_P(zentry[0]), key2.str, key2.len, &zentry[1], sizeof(zval *), NULL); + } + + zend_hash_add(ht, key1.str, key1.len, &zentry[0], sizeof(zval *), NULL); + } + } else if (ht) { + ht = NULL; + } + UNLOCK(); + + return ht; +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_persistent_handle.h b/php_http_persistent_handle.h new file mode 100644 index 0000000..075fe92 --- /dev/null +++ b/php_http_persistent_handle.h @@ -0,0 +1,50 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_persistent_handle_api.h 292841 2009-12-31 08:48:57Z mike $ */ + +#ifndef PHP_HTTP_PERSISTENT_HANDLE_H +#define PHP_HTTP_PERSISTENT_HANDLE_H + +typedef void *(*php_http_persistent_handle_ctor_t)(void); +typedef void (*php_http_persistent_handle_dtor_t)(void *handle); +typedef void *(*php_http_persistent_handle_copy_t)(void *handle); + +struct php_http_persistent_handle_globals { + ulong limit; + struct { + ulong h; + char *s; + size_t l; + } ident; +}; + +PHP_MINIT_FUNCTION(http_persistent_handle); +PHP_MSHUTDOWN_FUNCTION(http_persistent_handle); + +PHP_HTTP_API STATUS php_http_persistent_handle_provide(const char *name_str, size_t name_len, php_http_persistent_handle_ctor_t ctor, php_http_persistent_handle_dtor_t dtor, php_http_persistent_handle_copy_t copy); +PHP_HTTP_API void php_http_persistent_handle_cleanup(const char *name_str, size_t name_len, int current_ident_only TSRMLS_DC); +PHP_HTTP_API HashTable *php_http_persistent_handle_statall(HashTable *ht TSRMLS_DC); +PHP_HTTP_API STATUS php_http_persistent_handle_acquire(const char *name_str, size_t name_len, void **handle TSRMLS_DC); +PHP_HTTP_API STATUS php_http_persistent_handle_release(const char *name_str, size_t name_len, void **handle TSRMLS_DC); +PHP_HTTP_API STATUS php_http_persistent_handle_accrete(const char *name_str, size_t name_len, void *old_handle, void **new_handle TSRMLS_DC); + +#endif /* PHP_HTTP_PERSISTENT_HANDLE_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/php_http_property_proxy.c b/php_http_property_proxy.c new file mode 100644 index 0000000..196ccce --- /dev/null +++ b/php_http_property_proxy.c @@ -0,0 +1,189 @@ + +#include "php_http.h" + + +php_http_property_proxy_t *php_http_property_proxy_init(php_http_property_proxy_t *proxy, zval *object, zval *member TSRMLS_DC) +{ + if (!proxy) { + proxy = emalloc(sizeof(*proxy)); + } + memset(proxy, 0, sizeof(*proxy)); + + MAKE_STD_ZVAL(proxy->myself); + ZVAL_OBJVAL(proxy->myself, php_http_property_proxy_object_new_ex(php_http_property_proxy_class_entry, proxy, NULL TSRMLS_CC), 0); + Z_ADDREF_P(object); + proxy->object = object; + proxy->member = php_http_zsep(IS_STRING, member); + + return proxy; +} + +void php_http_property_proxy_dtor(php_http_property_proxy_t *proxy) +{ + zval_ptr_dtor(&proxy->object); + zval_ptr_dtor(&proxy->member); + zval_ptr_dtor(&proxy->myself); +} + +void php_http_property_proxy_free(php_http_property_proxy_t **proxy) +{ + if (*proxy) { + php_http_property_proxy_dtor(*proxy); + efree(*proxy); + *proxy = NULL; + } +} + + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpPropertyProxy, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpPropertyProxy, method, 0) +#define PHP_HTTP_PP_ME(method, visibility) PHP_ME(HttpPropertyProxy, method, PHP_HTTP_ARGS(HttpPropertyProxy, method), visibility) + +PHP_HTTP_EMPTY_ARGS(__construct); + +zend_class_entry *php_http_property_proxy_class_entry; +zend_function_entry php_http_property_proxy_method_entry[] = { + PHP_HTTP_PP_ME(__construct, ZEND_ACC_FINAL|ZEND_ACC_PRIVATE) + EMPTY_FUNCTION_ENTRY +}; +static zend_object_handlers php_http_property_proxy_object_handlers; + +zend_object_value php_http_property_proxy_object_new(zend_class_entry *ce TSRMLS_DC) +{ + return php_http_property_proxy_object_new_ex(ce, NULL, NULL TSRMLS_CC); +} + +zend_object_value php_http_property_proxy_object_new_ex(zend_class_entry *ce, php_http_property_proxy_t *proxy, php_http_property_proxy_object_t **ptr TSRMLS_DC) +{ + zend_object_value ov; + php_http_property_proxy_object_t *o; + + o = ecalloc(1, sizeof(*o)); + zend_object_std_init((zend_object *) o, ce TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + + if (ptr) { + *ptr = o; + } + o->proxy = proxy; + + ov.handle = zend_objects_store_put(o, NULL, php_http_property_proxy_object_free, NULL TSRMLS_CC); + ov.handlers = &php_http_property_proxy_object_handlers; + + return ov; +} + +void php_http_property_proxy_object_free(void *object TSRMLS_DC) +{ + php_http_property_proxy_object_t *o = object; + + if (o->proxy) { + php_http_property_proxy_free(&o->proxy); + } + zend_object_std_dtor((zend_object *) o TSRMLS_CC); + efree(o); +} + +static void php_http_property_proxy_object_set(zval **object, zval *value TSRMLS_DC) +{ + php_http_property_proxy_object_t *obj = zend_object_store_get_object(*object TSRMLS_CC); + + zend_update_property(Z_OBJCE_P(obj->proxy->object), obj->proxy->object, Z_STRVAL_P(obj->proxy->member), Z_STRLEN_P(obj->proxy->member), value TSRMLS_CC); +} + +static zval *php_http_property_proxy_object_get(zval *object TSRMLS_DC) +{ + php_http_property_proxy_object_t *obj = zend_object_store_get_object(object TSRMLS_CC); + + return zend_read_property(Z_OBJCE_P(obj->proxy->object), obj->proxy->object, Z_STRVAL_P(obj->proxy->member), Z_STRLEN_P(obj->proxy->member), 0 TSRMLS_CC); +} + +static STATUS php_http_property_proxy_object_cast(zval *object, zval *return_value, int type TSRMLS_DC) +{ + zval *old_value, *new_value; + + old_value = php_http_property_proxy_object_get(object TSRMLS_CC); + new_value = php_http_zsep(type, old_value); + + if (old_value != new_value) { + zval_ptr_dtor(&old_value); + } + + RETVAL_ZVAL(new_value, 0, 0); + + return SUCCESS; +} + +static zval *php_http_property_proxy_object_read_dimension(zval *object, zval *offset, int type TSRMLS_DC) +{ + zval *retval = NULL, *property = php_http_property_proxy_object_get(object TSRMLS_CC); + + if (Z_TYPE_P(property) == IS_ARRAY) { + zval **data = NULL; + + if (Z_TYPE_P(offset) == IS_LONG) { + if (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(property), Z_LVAL_P(offset), (void *) &data)) { + retval = *data; + } + } else { + offset = php_http_zsep(IS_STRING, offset); + if (SUCCESS == zend_hash_find(Z_ARRVAL_P(property), Z_STRVAL_P(offset), Z_STRLEN_P(offset), (void *) &data)) { + retval = *data; + } + zval_ptr_dtor(&offset); + } + + if (data) { + Z_ADDREF_PP(data); + } + } + zval_ptr_dtor(&property); + + return retval; +} + +static void php_http_property_proxy_object_write_dimension(zval *object, zval *offset, zval *value TSRMLS_DC) +{ + zval *property = php_http_property_proxy_object_get(object TSRMLS_CC); + + switch (Z_TYPE_P(property)) { + case IS_NULL: + array_init(property); + case IS_ARRAY: + Z_ADDREF_P(value); + if (!offset) { + add_next_index_zval(property, value); + } else if (Z_TYPE_P(offset) == IS_LONG) { + add_index_zval(property, Z_LVAL_P(offset), value); + } else { + offset = php_http_zsep(IS_STRING, offset); + add_assoc_zval_ex(property, Z_STRVAL_P(offset), Z_STRLEN_P(offset) + 1, value); + zval_ptr_dtor(&offset); + } + php_http_property_proxy_object_set(&object, property TSRMLS_CC); + break; + + default: + zval_ptr_dtor(&property); + break; + } +} + +PHP_METHOD(HttpPropertyProxy, __construct) +{ +} + +PHP_MINIT_FUNCTION(http_property_proxy) +{ + PHP_HTTP_REGISTER_CLASS(http\\object, PropertyProxy, http_property_proxy, NULL, ZEND_ACC_FINAL); + php_http_property_proxy_class_entry->create_object = php_http_property_proxy_object_new; + memcpy(&php_http_property_proxy_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_http_property_proxy_object_handlers.set = php_http_property_proxy_object_set; + php_http_property_proxy_object_handlers.get = php_http_property_proxy_object_get; + php_http_property_proxy_object_handlers.cast_object = php_http_property_proxy_object_cast; + php_http_property_proxy_object_handlers.read_dimension = php_http_property_proxy_object_read_dimension; + php_http_property_proxy_object_handlers.write_dimension = php_http_property_proxy_object_write_dimension; + + return SUCCESS; +} + diff --git a/php_http_property_proxy.h b/php_http_property_proxy.h new file mode 100644 index 0000000..bc63aaf --- /dev/null +++ b/php_http_property_proxy.h @@ -0,0 +1,31 @@ + +#ifndef PHP_HTTP_PROPERTY_PROXY_H +#define PHP_HTTP_PROPERTY_PROXY_H + +typedef struct php_http_property_proxy { + zval *myself; + zval *object; + zval *member; +} php_http_property_proxy_t; + +PHP_HTTP_API php_http_property_proxy_t *php_http_property_proxy_init(php_http_property_proxy_t *proxy, zval *object, zval *member TSRMLS_DC); +PHP_HTTP_API void php_http_property_proxy_dtor(php_http_property_proxy_t *proxy); +PHP_HTTP_API void php_http_property_proxy_free(php_http_property_proxy_t **proxy); + +typedef struct php_http_property_proxy_object { + zend_object zo; + php_http_property_proxy_t *proxy; +} php_http_property_proxy_object_t; + +extern zend_class_entry *php_http_property_proxy_class_entry; +extern zend_function_entry php_http_property_proxy_method_entry[]; + +extern zend_object_value php_http_property_proxy_object_new(zend_class_entry *ce TSRMLS_DC); +extern zend_object_value php_http_property_proxy_object_new_ex(zend_class_entry *ce, php_http_property_proxy_t *proxy, php_http_property_proxy_object_t **ptr TSRMLS_DC); +extern void php_http_property_proxy_object_free(void *object TSRMLS_DC); + +PHP_METHOD(HttpPropertyProxy, __construct); + +PHP_MINIT_FUNCTION(http_property_proxy); + +#endif /* PHP_HTTP_PROPERTY_PROXY_H_ */ diff --git a/php_http_querystring.c b/php_http_querystring.c new file mode 100644 index 0000000..ffa5052 --- /dev/null +++ b/php_http_querystring.c @@ -0,0 +1,684 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#include "php_http.h" + +/** API **/ + +static inline int php_http_querystring_modify_array_ex(zval *qarray, int key_type, char *key, int keylen, ulong idx, zval *params_entry TSRMLS_DC); +static inline int php_http_querystring_modify_array(zval *qarray, zval *params TSRMLS_DC); + +#ifdef PHP_HTTP_HAVE_ICONV +PHP_HTTP_API int php_http_querystring_xlate(zval *array, zval *param, const char *ie, const char *oe TSRMLS_DC) +{ + HashPosition pos; + zval **entry = NULL; + char *xlate_str = NULL, *xkey; + size_t xlate_len = 0, xlen; + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + + FOREACH_KEYVAL(pos, param, key, entry) { + if (key.type == HASH_KEY_IS_STRING) { + if (PHP_ICONV_ERR_SUCCESS != php_iconv_string(key.str, key.len-1, &xkey, &xlen, oe, ie)) { + php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Failed to convert '%.*s' from '%s' to '%s'", key.len-1, key.str, ie, oe); + return FAILURE; + } + } + + if (Z_TYPE_PP(entry) == IS_STRING) { + if (PHP_ICONV_ERR_SUCCESS != php_iconv_string(Z_STRVAL_PP(entry), Z_STRLEN_PP(entry), &xlate_str, &xlate_len, oe, ie)) { + if (key.type == HASH_KEY_IS_STRING) { + efree(xkey); + } + php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Failed to convert '%.*s' from '%s' to '%s'", Z_STRLEN_PP(entry), Z_STRVAL_PP(entry), ie, oe); + return FAILURE; + } + if (key.type == HASH_KEY_IS_STRING) { + add_assoc_stringl_ex(array, xkey, xlen+1, xlate_str, xlate_len, 0); + } else { + add_index_stringl(array, key.num, xlate_str, xlate_len, 0); + } + } else if (Z_TYPE_PP(entry) == IS_ARRAY) { + zval *subarray; + + MAKE_STD_ZVAL(subarray); + array_init(subarray); + if (key.type == HASH_KEY_IS_STRING) { + add_assoc_zval_ex(array, xkey, xlen+1, subarray); + } else { + add_index_zval(array, key.num, subarray); + } + if (SUCCESS != php_http_querystring_xlate(subarray, *entry, ie, oe TSRMLS_CC)) { + if (key.type == HASH_KEY_IS_STRING) { + efree(xkey); + } + return FAILURE; + } + } + + if (key.type == HASH_KEY_IS_STRING) { + efree(xkey); + } + } + return SUCCESS; +} +#endif /* HAVE_ICONV */ + +PHP_HTTP_API void php_http_querystring_update(zval *qarray, zval *qstring TSRMLS_DC) +{ + char *s = NULL; + size_t l = 0; + + if (Z_TYPE_P(qarray) != IS_ARRAY) { + convert_to_array(qarray); + } + if (SUCCESS == php_http_url_encode_hash(Z_ARRVAL_P(qarray), 0, NULL, 0, &s, &l TSRMLS_CC)) { + if (Z_TYPE_P(qstring) == IS_STRING) + zval_dtor(qstring); + ZVAL_STRINGL(qstring, s, l, 0); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Failed to update query string"); + } +} + +PHP_HTTP_API int php_http_querystring_modify(zval *qarray, zval *params TSRMLS_DC) +{ + if (Z_TYPE_P(params) == IS_ARRAY) { + return php_http_querystring_modify_array(qarray, params TSRMLS_CC); + } else if (Z_TYPE_P(params) == IS_OBJECT) { + if (instanceof_function(Z_OBJCE_P(params), php_http_querystring_class_entry TSRMLS_CC)) { + return php_http_querystring_modify_array(qarray, zend_read_property(php_http_querystring_class_entry, params, ZEND_STRL("queryArray"), 0 TSRMLS_CC) TSRMLS_CC); + } else { + return php_http_querystring_modify_array(qarray, params TSRMLS_CC); + } + } else { + int rv; + zval array; + zval *qstring = php_http_zsep(IS_STRING, params); + + INIT_PZVAL(&array); + array_init(&array); + + php_default_treat_data(PARSE_STRING, estrdup(Z_STRVAL_P(qstring)), &array TSRMLS_CC); + zval_ptr_dtor(&qstring); + + rv = php_http_querystring_modify_array(qarray, &array TSRMLS_CC); + zval_dtor(&array); + return rv; + } +} + +static inline int php_http_querystring_modify_array(zval *qarray, zval *params TSRMLS_DC) +{ + int rv = 0; + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + HashPosition pos; + zval **params_entry = NULL; + + FOREACH_HASH_KEYVAL(pos, HASH_OF(params), key, params_entry) { + /* only public properties */ + if ((key.type != HASH_KEY_IS_STRING || *key.str) && php_http_querystring_modify_array_ex(qarray, key.type, key.str, key.len, key.num, *params_entry TSRMLS_CC)) { + rv = 1; + } + } + + return rv; +} + +static inline int php_http_querystring_modify_array_ex(zval *qarray, int key_type, char *key, int keylen, ulong idx, zval *params_entry TSRMLS_DC) +{ + zval **qarray_entry; + + /* ensure array type */ + if (Z_TYPE_P(qarray) != IS_ARRAY) { + convert_to_array(qarray); + } + + /* delete */ + if (Z_TYPE_P(params_entry) == IS_NULL) { + if (key_type == HASH_KEY_IS_STRING) { + return (SUCCESS == zend_hash_del(Z_ARRVAL_P(qarray), key, keylen)); + } else { + return (SUCCESS == zend_hash_index_del(Z_ARRVAL_P(qarray), idx)); + } + } + + /* update */ + if ( ((key_type == HASH_KEY_IS_STRING) && (SUCCESS == zend_hash_find(Z_ARRVAL_P(qarray), key, keylen, (void *) &qarray_entry))) || + ((key_type == HASH_KEY_IS_LONG) && (SUCCESS == zend_hash_index_find(Z_ARRVAL_P(qarray), idx, (void *) &qarray_entry)))) { + zval equal; + + /* recursive */ + if (Z_TYPE_P(params_entry) == IS_ARRAY || Z_TYPE_P(params_entry) == IS_OBJECT) { + return php_http_querystring_modify(*qarray_entry, params_entry TSRMLS_CC); + } + /* equal */ + if ((SUCCESS == is_equal_function(&equal, *qarray_entry, params_entry TSRMLS_CC)) && Z_BVAL(equal)) { + return 0; + } + } + + /* add */ + if (Z_TYPE_P(params_entry) == IS_OBJECT) { + zval *new_array; + + MAKE_STD_ZVAL(new_array); + array_init(new_array); + php_http_querystring_modify_array(new_array, params_entry TSRMLS_CC); + params_entry = new_array; + } else { + Z_ADDREF_P(params_entry); + } + if (key_type == HASH_KEY_IS_STRING) { + add_assoc_zval_ex(qarray, key, keylen, params_entry); + } else { + add_index_zval(qarray, idx, params_entry); + } + return 1; +} + +/** PHP **/ + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpQueryString, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpQueryString, method, 0) +#define PHP_HTTP_QUERYSTRING_ME(method, visibility) PHP_ME(HttpQueryString, method, PHP_HTTP_ARGS(HttpQueryString, method), visibility) +#define PHP_HTTP_QUERYSTRING_GME(method, visibility) PHP_ME(HttpQueryString, method, PHP_HTTP_ARGS(HttpQueryString, __getter), visibility) + +PHP_HTTP_BEGIN_ARGS(__construct, 0) + PHP_HTTP_ARG_VAL(params, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getGlobalInstance); + +PHP_HTTP_EMPTY_ARGS(toArray); +PHP_HTTP_EMPTY_ARGS(toString); + +PHP_HTTP_BEGIN_ARGS(get, 0) + PHP_HTTP_ARG_VAL(name, 0) + PHP_HTTP_ARG_VAL(type, 0) + PHP_HTTP_ARG_VAL(defval, 0) + PHP_HTTP_ARG_VAL(delete, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(set, 1) + PHP_HTTP_ARG_VAL(params, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(mod, 0) + PHP_HTTP_ARG_VAL(params, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(__getter, 1) + PHP_HTTP_ARG_VAL(name, 0) + PHP_HTTP_ARG_VAL(defval, 0) + PHP_HTTP_ARG_VAL(delete, 0) +PHP_HTTP_END_ARGS; + +#ifdef PHP_HTTP_HAVE_ICONV +PHP_HTTP_BEGIN_ARGS(xlate, 2) + PHP_HTTP_ARG_VAL(from_encoding, 0) + PHP_HTTP_ARG_VAL(to_encoding, 0) +PHP_HTTP_END_ARGS; +#endif + +PHP_HTTP_EMPTY_ARGS(serialize); +PHP_HTTP_BEGIN_ARGS(unserialize, 1) + PHP_HTTP_ARG_VAL(serialized, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(offsetGet, 1) + PHP_HTTP_ARG_VAL(offset, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(offsetSet, 2) + PHP_HTTP_ARG_VAL(offset, 0) + PHP_HTTP_ARG_VAL(value, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(offsetExists, 1) + PHP_HTTP_ARG_VAL(offset, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(offsetUnset, 1) + PHP_HTTP_ARG_VAL(offset, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getIterator); + +zend_class_entry *php_http_querystring_class_entry; +zend_function_entry php_http_querystring_method_entry[] = { + PHP_HTTP_QUERYSTRING_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR|ZEND_ACC_FINAL) + + PHP_HTTP_QUERYSTRING_ME(toArray, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_ME(toString, ZEND_ACC_PUBLIC) + ZEND_MALIAS(HttpQueryString, __toString, toString, PHP_HTTP_ARGS(HttpQueryString, toString), ZEND_ACC_PUBLIC) + + PHP_HTTP_QUERYSTRING_ME(get, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_ME(set, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_ME(mod, ZEND_ACC_PUBLIC) + + PHP_HTTP_QUERYSTRING_GME(getBool, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_GME(getInt, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_GME(getFloat, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_GME(getString, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_GME(getArray, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_GME(getObject, ZEND_ACC_PUBLIC) + + PHP_HTTP_QUERYSTRING_ME(getIterator, ZEND_ACC_PUBLIC) + + PHP_HTTP_QUERYSTRING_ME(getGlobalInstance, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) +#ifdef PHP_HTTP_HAVE_ICONV + PHP_HTTP_QUERYSTRING_ME(xlate, ZEND_ACC_PUBLIC) +#endif + + /* Implements Serializable */ + PHP_HTTP_QUERYSTRING_ME(serialize, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_ME(unserialize, ZEND_ACC_PUBLIC) + + /* Implements ArrayAccess */ + PHP_HTTP_QUERYSTRING_ME(offsetGet, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_ME(offsetSet, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_ME(offsetExists, ZEND_ACC_PUBLIC) + PHP_HTTP_QUERYSTRING_ME(offsetUnset, ZEND_ACC_PUBLIC) + + EMPTY_FUNCTION_ENTRY +}; + +PHP_MINIT_FUNCTION(http_querystring) +{ + PHP_HTTP_REGISTER_CLASS(http, QueryString, http_querystring, php_http_object_class_entry, 0); + + zend_class_implements(php_http_querystring_class_entry TSRMLS_CC, 3, zend_ce_serializable, zend_ce_arrayaccess, zend_ce_aggregate); + + zend_declare_property_null(php_http_querystring_class_entry, ZEND_STRL("instance"), (ZEND_ACC_STATIC|ZEND_ACC_PRIVATE) TSRMLS_CC); + zend_declare_property_null(php_http_querystring_class_entry, ZEND_STRL("queryArray"), ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_null(php_http_querystring_class_entry, ZEND_STRL("queryString"), ZEND_ACC_PRIVATE TSRMLS_CC); + + zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_BOOL"), PHP_HTTP_QUERYSTRING_TYPE_BOOL TSRMLS_CC); + zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_INT"), PHP_HTTP_QUERYSTRING_TYPE_INT TSRMLS_CC); + zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_FLOAT"), PHP_HTTP_QUERYSTRING_TYPE_FLOAT TSRMLS_CC); + zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_STRING"), PHP_HTTP_QUERYSTRING_TYPE_STRING TSRMLS_CC); + zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_ARRAY"), PHP_HTTP_QUERYSTRING_TYPE_ARRAY TSRMLS_CC); + zend_declare_class_constant_long(php_http_querystring_class_entry, ZEND_STRL("TYPE_OBJECT"), PHP_HTTP_QUERYSTRING_TYPE_OBJECT TSRMLS_CC); + + return SUCCESS; +} + +static inline void php_http_querystring_get(zval *this_ptr, int type, char *name, uint name_len, zval *defval, zend_bool del, zval *return_value TSRMLS_DC) +{ + zval **arrval, *qarray = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC); + + if ((Z_TYPE_P(qarray) == IS_ARRAY) && (SUCCESS == zend_hash_find(Z_ARRVAL_P(qarray), name, name_len + 1, (void *) &arrval))) { + if (type) { + zval *value = php_http_zsep(type, *arrval); + RETVAL_ZVAL(value, 1, 1); + } else { + RETVAL_ZVAL(*arrval, 1, 0); + } + + if (del && (SUCCESS == zend_hash_del(Z_ARRVAL_P(qarray), name, name_len + 1))) { + php_http_querystring_update(qarray, zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), 0 TSRMLS_CC) TSRMLS_CC); + } + } else if(defval) { + RETURN_ZVAL(defval, 1, 0); + } +} + +static inline void php_http_querystring_set(zval *instance, zval *params TSRMLS_DC) +{ + zval *na = NULL, *qa = zend_read_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryArray"), 0 TSRMLS_CC); + + if (Z_TYPE_P(qa) != IS_ARRAY) { + MAKE_STD_ZVAL(qa); + array_init(qa); + zend_update_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryArray"), qa TSRMLS_CC); + na = qa; + } + + if (params && php_http_querystring_modify(qa, params TSRMLS_CC)) { + zval *ns = NULL, *qs = zend_read_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryString"), 0 TSRMLS_CC); + + if (Z_TYPE_P(qs) != IS_STRING) { + MAKE_STD_ZVAL(qs); + ZVAL_EMPTY_STRING(qs); + zend_update_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryString"), qs TSRMLS_CC); + ns = qs; + } + + php_http_querystring_update(qa, qs TSRMLS_CC); + + if (ns) { + zval_ptr_dtor(&ns); + } + } + + if (na) { + zval_ptr_dtor(&na); + } +} + +PHP_METHOD(HttpQueryString, __construct) +{ + zval *params = NULL; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z", ¶ms)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) { + php_http_querystring_set(getThis(), params TSRMLS_CC); + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpQueryString, getGlobalInstance) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters_none()) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) { + zval *instance = *zend_std_get_static_property(php_http_querystring_class_entry, ZEND_STRL("instance"), 0, NULL TSRMLS_CC); + + if (Z_TYPE_P(instance) != IS_OBJECT) { + zval **_SERVER = NULL, **_GET = NULL, **QUERY_STRING = NULL; + + zend_is_auto_global("_GET", lenof("_GET") TSRMLS_CC); + zend_is_auto_global("_SERVER", lenof("_SERVER") TSRMLS_CC); + + if ((SUCCESS == zend_hash_find(&EG(symbol_table), "_SERVER", sizeof("_SERVER"), (void *) &_SERVER)) + && (Z_TYPE_PP(_SERVER) == IS_ARRAY) + && (SUCCESS == zend_hash_find(Z_ARRVAL_PP(_SERVER), "QUERY_STRING", sizeof("QUERY_STRING"), (void *) &QUERY_STRING)) + && (SUCCESS == zend_hash_find(&EG(symbol_table), "_GET", sizeof("_GET"), (void *) &_GET)) + && (Z_TYPE_PP(_GET) == IS_ARRAY) + ) { + zval *qstring = *QUERY_STRING, *qarray = *_GET; + + if (Z_TYPE_P(qstring) != IS_STRING) { + convert_to_string(qstring); + } + + MAKE_STD_ZVAL(instance); + ZVAL_OBJVAL(instance, php_http_querystring_object_new(php_http_querystring_class_entry TSRMLS_CC), 0); + + zend_update_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryArray"), qarray TSRMLS_CC); + zend_update_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryString"), qstring TSRMLS_CC); + + Z_SET_ISREF_P(zend_read_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryArray"), 0 TSRMLS_CC)); + Z_SET_ISREF_P(zend_read_property(php_http_querystring_class_entry, instance, ZEND_STRL("queryString"), 0 TSRMLS_CC)); + + zend_update_static_property(php_http_querystring_class_entry, ZEND_STRL("instance"), instance TSRMLS_CC); + zval_ptr_dtor(&instance); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Could not acquire reference to superglobal GET or QUERY_STRING"); + } + } + RETVAL_ZVAL(instance, 1, 0); + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpQueryString, getIterator) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters_none()) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) { + zval *retval = NULL, *qa = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC); + + object_init_ex(return_value, spl_ce_RecursiveArrayIterator); + zend_call_method_with_1_params(&return_value, spl_ce_RecursiveArrayIterator, NULL, "__construct", &retval, qa); + if (retval) { + zval_ptr_dtor(&retval); + } + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpQueryString, toString) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_querystring_class_entry, "queryString"); + } + RETURN_EMPTY_STRING(); +} + +PHP_METHOD(HttpQueryString, toArray) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_querystring_class_entry, "queryArray"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpQueryString, get) +{ + char *name_str = NULL; + int name_len = 0; + long type = 0; + zend_bool del = 0; + zval *ztype = NULL, *defval = NULL; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|szzb", &name_str, &name_len, &ztype, &defval, &del)) { + if (name_str && name_len) { + if (ztype) { + if (Z_TYPE_P(ztype) == IS_LONG) { + type = Z_LVAL_P(ztype); + } else if(Z_TYPE_P(ztype) == IS_STRING) { + switch (Z_STRVAL_P(ztype)[0]) { + case 'B': + case 'b': type = PHP_HTTP_QUERYSTRING_TYPE_BOOL; break; + case 'L': + case 'l': + case 'I': + case 'i': type = PHP_HTTP_QUERYSTRING_TYPE_INT; break; + case 'd': + case 'D': + case 'F': + case 'f': type = PHP_HTTP_QUERYSTRING_TYPE_FLOAT; break; + case 'S': + case 's': type = PHP_HTTP_QUERYSTRING_TYPE_STRING; break; + case 'A': + case 'a': type = PHP_HTTP_QUERYSTRING_TYPE_ARRAY; break; + case 'O': + case 'o': type = PHP_HTTP_QUERYSTRING_TYPE_OBJECT; break; + } + } + } + php_http_querystring_get(getThis(), type, name_str, name_len, defval, del, return_value TSRMLS_CC); + } else { + RETURN_PROP(php_http_querystring_class_entry, "queryString"); + } + } +} + +PHP_METHOD(HttpQueryString, set) +{ + zval *params; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", ¶ms)) { + php_http_querystring_set(getThis(), params TSRMLS_CC); + RETURN_PROP(php_http_querystring_class_entry, "queryString"); + } + + RETURN_FALSE; +} + +PHP_METHOD(HttpQueryString, mod) +{ + zval *params; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", ¶ms)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) { + ZVAL_OBJVAL(return_value, Z_OBJ_HT_P(getThis())->clone_obj(getThis() TSRMLS_CC), 0); + php_http_querystring_set(return_value, params TSRMLS_CC); + } end_error_handling(); + } + } end_error_handling(); +} + +#define PHP_HTTP_QUERYSTRING_GETTER(method, TYPE) \ +PHP_METHOD(HttpQueryString, method) \ +{ \ + char *name; \ + int name_len; \ + zval *defval = NULL; \ + zend_bool del = 0; \ + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|zb", &name, &name_len, &defval, &del)) { \ + php_http_querystring_get(getThis(), TYPE, name, name_len, defval, del, return_value TSRMLS_CC); \ + } \ +} +PHP_HTTP_QUERYSTRING_GETTER(getBool, IS_BOOL); +PHP_HTTP_QUERYSTRING_GETTER(getInt, IS_LONG); +PHP_HTTP_QUERYSTRING_GETTER(getFloat, IS_DOUBLE); +PHP_HTTP_QUERYSTRING_GETTER(getString, IS_STRING); +PHP_HTTP_QUERYSTRING_GETTER(getArray, IS_ARRAY); +PHP_HTTP_QUERYSTRING_GETTER(getObject, IS_OBJECT); + +#ifdef PHP_HTTP_HAVE_ICONV +PHP_METHOD(HttpQueryString, xlate) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + char *ie, *oe; + int ie_len, oe_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &ie, &ie_len, &oe, &oe_len)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) { + zval *qa = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC); + + if (Z_TYPE_P(qa) != IS_ARRAY) { + MAKE_STD_ZVAL(qa); + array_init(qa); + zend_update_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), qa TSRMLS_CC); + zval_ptr_dtor(&qa); + } else { + zval xa; + + INIT_PZVAL(&xa); + array_init(&xa); + if (SUCCESS == php_http_querystring_xlate(&xa, qa, ie, oe TSRMLS_CC)) { + zval *ns = NULL, *qs = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), 0 TSRMLS_CC); + + /* shitty internal zvals */ + if (Z_TYPE_P(qs) != IS_STRING) { + MAKE_STD_ZVAL(qs); + ZVAL_EMPTY_STRING(qs); + zend_update_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), qs TSRMLS_CC); + ns = qs; + } + + zend_hash_clean(Z_ARRVAL_P(qa)); + array_copy(Z_ARRVAL(xa), Z_ARRVAL_P(qa)); + php_http_querystring_update(qa, qs TSRMLS_CC); + + if (ns) { + zval_ptr_dtor(&ns); + } + } + zval_dtor(&xa); + } + RETVAL_ZVAL(getThis(), 1, 0); + } end_error_handling(); + } + } end_error_handling(); + +} +#endif /* HAVE_ICONV */ + +PHP_METHOD(HttpQueryString, serialize) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_querystring_class_entry, "queryString"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpQueryString, unserialize) +{ + zval *serialized; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &serialized)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(querystring)) { + if (Z_TYPE_P(serialized) == IS_STRING) { + php_http_querystring_set(getThis(), serialized TSRMLS_CC); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_QUERYSTRING, "Expected a string as parameter"); + } + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpQueryString, offsetGet) +{ + char *offset_str; + int offset_len; + zval **value; + + if ((SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset_str, &offset_len)) + && (SUCCESS == zend_hash_find(Z_ARRVAL_P(zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC)), offset_str, offset_len + 1, (void *) &value)) + ) { + RETVAL_ZVAL(*value, 1, 0); + } +} + +PHP_METHOD(HttpQueryString, offsetSet) +{ + char *offset_str; + int offset_len; + zval *value; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz", &offset_str, &offset_len, &value)) { + zval *qarr = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC), *qstr = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), 0 TSRMLS_CC); + + Z_ADDREF_P(value); + add_assoc_zval_ex(qarr, offset_str, offset_len + 1, value); + php_http_querystring_update(qarr, qstr TSRMLS_CC); + } +} + +PHP_METHOD(HttpQueryString, offsetExists) +{ + char *offset_str; + int offset_len; + zval **value; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset_str, &offset_len)) { + RETURN_BOOL((SUCCESS == zend_hash_find(Z_ARRVAL_P(zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC)), offset_str, offset_len + 1, (void *) &value)) && (Z_TYPE_PP(value) != IS_NULL)); + } +} + +PHP_METHOD(HttpQueryString, offsetUnset) +{ + char *offset_str; + int offset_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &offset_str, &offset_len)) { + zval *qarr = zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryArray"), 0 TSRMLS_CC); + + if (SUCCESS == zend_hash_del(Z_ARRVAL_P(qarr), offset_str, offset_len + 1)) { + php_http_querystring_update(qarr, zend_read_property(php_http_querystring_class_entry, getThis(), ZEND_STRL("queryString"), 0 TSRMLS_CC) TSRMLS_CC); + } + } +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/php_http_querystring.h b/php_http_querystring.h new file mode 100644 index 0000000..92abd01 --- /dev/null +++ b/php_http_querystring.h @@ -0,0 +1,82 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_querystring_api.h 292841 2009-12-31 08:48:57Z mike $ */ + +#ifndef PHP_HTTP_QUERYSTRING_H +#define PHP_HTTP_QUERYSTRING_H + +/* API */ + +#ifdef PHP_HTTP_HAVE_ICONV +PHP_HTTP_API int php_http_querystring_xlate(zval *array, zval *param, const char *ie, const char *oe TSRMLS_DC); +#endif /* PHP_HTTP_HAVE_ICONV */ +PHP_HTTP_API void php_http_querystring_update(zval *qarray, zval *qstring TSRMLS_DC); +PHP_HTTP_API int php_http_querystring_modify(zval *qarray, zval *params TSRMLS_DC); + +/* PHP */ + +typedef struct php_http_querystring_object { + zend_object zo; +} php_http_querystring_object_t; + +#define PHP_HTTP_QUERYSTRING_TYPE_BOOL IS_BOOL +#define PHP_HTTP_QUERYSTRING_TYPE_INT IS_LONG +#define PHP_HTTP_QUERYSTRING_TYPE_FLOAT IS_DOUBLE +#define PHP_HTTP_QUERYSTRING_TYPE_STRING IS_STRING +#define PHP_HTTP_QUERYSTRING_TYPE_ARRAY IS_ARRAY +#define PHP_HTTP_QUERYSTRING_TYPE_OBJECT IS_OBJECT + +extern zend_class_entry *php_http_querystring_class_entry; +extern zend_function_entry php_http_querystring_method_entry[]; + +extern PHP_MINIT_FUNCTION(http_querystring); + +#define php_http_querystring_object_new php_http_object_new +#define php_http_querystring_object_new_ex php_http_object_new_ex + +PHP_METHOD(HttpQueryString, getGlobalInstance); +PHP_METHOD(HttpQueryString, __construct); +PHP_METHOD(HttpQueryString, getIterator); +PHP_METHOD(HttpQueryString, toString); +PHP_METHOD(HttpQueryString, toArray); +PHP_METHOD(HttpQueryString, get); +PHP_METHOD(HttpQueryString, set); +PHP_METHOD(HttpQueryString, mod); +PHP_METHOD(HttpQueryString, getBool); +PHP_METHOD(HttpQueryString, getInt); +PHP_METHOD(HttpQueryString, getFloat); +PHP_METHOD(HttpQueryString, getString); +PHP_METHOD(HttpQueryString, getArray); +PHP_METHOD(HttpQueryString, getObject); +#ifdef PHP_HTTP_HAVE_ICONV +PHP_METHOD(HttpQueryString, xlate); +#endif /* PHP_HTTP_HAVE_ICONV */ +PHP_METHOD(HttpQueryString, factory); +PHP_METHOD(HttpQueryString, singleton); +PHP_METHOD(HttpQueryString, serialize); +PHP_METHOD(HttpQueryString, unserialize); +PHP_METHOD(HttpQueryString, offsetGet); +PHP_METHOD(HttpQueryString, offsetSet); +PHP_METHOD(HttpQueryString, offsetExists); +PHP_METHOD(HttpQueryString, offsetUnset); + +#endif /* PHP_HTTP_QUERYSTRING_H */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/php_http_request.c b/php_http_request.c new file mode 100644 index 0000000..1df601f --- /dev/null +++ b/php_http_request.c @@ -0,0 +1,2362 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_request_api.c 298591 2010-04-26 11:46:55Z mike $ */ + +#include "php_http.h" + +#ifdef PHP_HTTP_NEED_OPENSSL_TSL +static MUTEX_T *php_http_openssl_tsl = NULL; + +static void php_http_openssl_thread_lock(int mode, int n, const char * file, int line) +{ + if (mode & CRYPTO_LOCK) { + tsrm_mutex_lock(php_http_openssl_tsl[n]); + } else { + tsrm_mutex_unlock(php_http_openssl_tsl[n]); + } +} + +static ulong php_http_openssl_thread_id(void) +{ + return (ulong) tsrm_thread_id(); +} +#endif +#ifdef PHP_HTTP_NEED_GNUTLS_TSL +static int php_http_gnutls_mutex_create(void **m) +{ + if (*((MUTEX_T *) m) = tsrm_mutex_alloc()) { + return SUCCESS; + } else { + return FAILURE; + } +} + +static int php_http_gnutls_mutex_destroy(void **m) +{ + tsrm_mutex_free(*((MUTEX_T *) m)); + return SUCCESS; +} + +static int php_http_gnutls_mutex_lock(void **m) +{ + return tsrm_mutex_lock(*((MUTEX_T *) m)); +} + +static int php_http_gnutls_mutex_unlock(void **m) +{ + return tsrm_mutex_unlock(*((MUTEX_T *) m)); +} + +static struct gcry_thread_cbs php_http_gnutls_tsl = { + GCRY_THREAD_OPTION_USER, + NULL, + php_http_gnutls_mutex_create, + php_http_gnutls_mutex_destroy, + php_http_gnutls_mutex_lock, + php_http_gnutls_mutex_unlock +}; +#endif + + +/* safe curl wrappers */ +#define init_curl_storage(ch) \ + {\ + php_http_request_storage_t *st = pecalloc(1, sizeof(php_http_request_storage_t), 1); \ + curl_easy_setopt(ch, CURLOPT_PRIVATE, st); \ + curl_easy_setopt(ch, CURLOPT_ERRORBUFFER, st->errorbuffer); \ + } + +static void *safe_curl_init(void) +{ + CURL *ch; + + if ((ch = curl_easy_init())) { + init_curl_storage(ch); + return ch; + } + return NULL; +} +static void *safe_curl_copy(void *p) +{ + CURL *ch; + + if ((ch = curl_easy_duphandle(p))) { + init_curl_storage(ch); + return ch; + } + return NULL; +} +static void safe_curl_dtor(void *p) { + php_http_request_storage_t *st = php_http_request_storage_get(p); + + curl_easy_cleanup(p); + + if (st) { + if (st->url) { + pefree(st->url, 1); + } + if (st->cookiestore) { + pefree(st->cookiestore, 1); + } + pefree(st, 1); + } +} + +static inline zval *php_http_request_option(php_http_request_t *request, HashTable *options, char *key, size_t keylen, int type); +static inline zval *php_http_request_option_cache(php_http_request_t *r, char *key, size_t keylen, ulong h, zval *opt); +static inline int php_http_request_cookies_enabled(php_http_request_t *r); + +static size_t php_http_curl_read_callback(void *, size_t, size_t, void *); +static int php_http_curl_progress_callback(void *, double, double, double, double); +static int php_http_curl_raw_callback(CURL *, curl_infotype, char *, size_t, void *); +static int php_http_curl_dummy_callback(char *data, size_t n, size_t l, void *s) { return n*l; } +static curlioerr php_http_curl_ioctl_callback(CURL *, curliocmd, void *); + +PHP_HTTP_API CURL * php_http_curl_init(CURL *ch, php_http_request_t *request TSRMLS_DC) +{ + if (ch || (SUCCESS == php_http_persistent_handle_acquire(ZEND_STRL("http_request"), &ch TSRMLS_CC))) { +#if defined(ZTS) + curl_easy_setopt(ch, CURLOPT_NOSIGNAL, 1L); +#endif + curl_easy_setopt(ch, CURLOPT_HEADER, 0L); + curl_easy_setopt(ch, CURLOPT_FILETIME, 1L); + curl_easy_setopt(ch, CURLOPT_AUTOREFERER, 1L); + curl_easy_setopt(ch, CURLOPT_VERBOSE, 1L); + curl_easy_setopt(ch, CURLOPT_HEADERFUNCTION, NULL); + curl_easy_setopt(ch, CURLOPT_DEBUGFUNCTION, php_http_curl_raw_callback); + curl_easy_setopt(ch, CURLOPT_READFUNCTION, php_http_curl_read_callback); + curl_easy_setopt(ch, CURLOPT_IOCTLFUNCTION, php_http_curl_ioctl_callback); + curl_easy_setopt(ch, CURLOPT_WRITEFUNCTION, php_http_curl_dummy_callback); + + /* set context */ + if (request) { + curl_easy_setopt(ch, CURLOPT_DEBUGDATA, request); + + /* attach curl handle */ + request->ch = ch; + /* set defaults (also in php_http_request_reset()) */ + php_http_request_defaults(request); + } + } + + return ch; +} +PHP_HTTP_API CURL *php_http_curl_copy(CURL *ch TSRMLS_DC) +{ + CURL *copy; + + if (SUCCESS == php_http_persistent_handle_accrete(ZEND_STRL("http_request"), ch, © TSRMLS_CC)) { + return copy; + } + return NULL; +} +PHP_HTTP_API void php_http_curl_free(CURL **ch TSRMLS_DC) +{ + if (*ch) { + curl_easy_setopt(*ch, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(*ch, CURLOPT_PROGRESSFUNCTION, NULL); + curl_easy_setopt(*ch, CURLOPT_VERBOSE, 0L); + curl_easy_setopt(*ch, CURLOPT_DEBUGFUNCTION, NULL); + + php_http_persistent_handle_release(ZEND_STRL("http_request"), ch TSRMLS_CC); + } +} + +PHP_HTTP_API php_http_request_t *php_http_request_init(php_http_request_t *request, CURL *ch, php_http_request_method_t meth, const char *url TSRMLS_DC) +{ + php_http_request_t *r; + + if (request) { + r = request; + } else { + r = emalloc(sizeof(php_http_request_t)); + } + memset(r, 0, sizeof(php_http_request_t)); + + r->ch = ch; + r->url = (url) ? php_http_url_absolute(url, 0) : NULL; + r->meth = (meth > 0) ? meth : PHP_HTTP_GET; + + r->parser.ctx = php_http_message_parser_init(NULL TSRMLS_CC); + r->parser.msg = php_http_message_init(NULL, 0 TSRMLS_CC); + r->parser.buf = php_http_buffer_init(NULL); + + php_http_buffer_init(&r->_cache.cookies); + zend_hash_init(&r->_cache.options, 0, NULL, ZVAL_PTR_DTOR, 0); + + TSRMLS_SET_CTX(r->ts); + + return r; +} + +PHP_HTTP_API void php_http_request_dtor(php_http_request_t *request) +{ + TSRMLS_FETCH_FROM_CTX(request->ts); + + php_http_request_reset(request); + php_http_curl_free(&request->ch); + php_http_message_body_free(&request->body); + php_http_message_parser_free(&request->parser.ctx); + php_http_message_free(&request->parser.msg); + php_http_buffer_free(&request->parser.buf); + + php_http_buffer_dtor(&request->_cache.cookies); + zend_hash_destroy(&request->_cache.options); + if (request->_cache.headers) { + curl_slist_free_all(request->_cache.headers); + request->_cache.headers = NULL; + } + if (request->_progress.callback) { + zval_ptr_dtor(&request->_progress.callback); + request->_progress.callback = NULL; + } +} + +PHP_HTTP_API void php_http_request_free(php_http_request_t **request) +{ + if (*request) { + TSRMLS_FETCH_FROM_CTX((*request)->ts); + php_http_request_dtor(*request); + efree(*request); + *request = NULL; + } +} + +PHP_HTTP_API void php_http_request_reset(php_http_request_t *request) +{ + TSRMLS_FETCH_FROM_CTX(request->ts); + STR_SET(request->url, NULL); + php_http_message_body_dtor(request->body); + php_http_request_defaults(request); + + if (request->ch) { + php_http_request_storage_t *st = php_http_request_storage_get(request->ch); + + if (st) { + if (st->url) { + pefree(st->url, 1); + st->url = NULL; + } + if (st->cookiestore) { + pefree(st->cookiestore, 1); + st->cookiestore = NULL; + } + st->errorbuffer[0] = '\0'; + } + } +} + +PHP_HTTP_API STATUS php_http_request_enable_cookies(php_http_request_t *request) +{ + int initialized = 1; + TSRMLS_FETCH_FROM_CTX(request->ts); + + PHP_HTTP_CHECK_CURL_INIT(request->ch, php_http_curl_init(request->ch, request TSRMLS_CC), initialized = 0); + if (initialized && (php_http_request_cookies_enabled(request) || (CURLE_OK == curl_easy_setopt(request->ch, CURLOPT_COOKIEFILE, "")))) { + return SUCCESS; + } + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not enable cookies for this session"); + return FAILURE; +} + +PHP_HTTP_API STATUS php_http_request_reset_cookies(php_http_request_t *request, int session_only) +{ + int initialized = 1; + TSRMLS_FETCH_FROM_CTX(request->ts); + + PHP_HTTP_CHECK_CURL_INIT(request->ch, php_http_curl_init(request->ch, request TSRMLS_CC), initialized = 0); + if (initialized) { + if (!php_http_request_cookies_enabled(request)) { + if (SUCCESS != php_http_request_enable_cookies(request)) { + return FAILURE; + } + } + if (session_only) { + if (CURLE_OK == curl_easy_setopt(request->ch, CURLOPT_COOKIELIST, "SESS")) { + return SUCCESS; + } + } else { + if (CURLE_OK == curl_easy_setopt(request->ch, CURLOPT_COOKIELIST, "ALL")) { + return SUCCESS; + } + } + } + return FAILURE; +} + +PHP_HTTP_API STATUS php_http_request_flush_cookies(php_http_request_t *request) +{ + int initialized = 1; + TSRMLS_FETCH_FROM_CTX(request->ts); + + PHP_HTTP_CHECK_CURL_INIT(request->ch, php_http_curl_init(request->ch, request TSRMLS_CC), initialized = 0); + if (initialized) { + if (!php_http_request_cookies_enabled(request)) { + return FAILURE; + } + if (CURLE_OK == curl_easy_setopt(request->ch, CURLOPT_COOKIELIST, "FLUSH")) { + return SUCCESS; + } + } + return FAILURE; +} + +PHP_HTTP_API void php_http_request_defaults(php_http_request_t *request) +{ + if (request->ch) { + PHP_HTTP_CURL_OPT(CURLOPT_NOPROGRESS, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_PROGRESSDATA, request); + PHP_HTTP_CURL_OPT(CURLOPT_PROGRESSFUNCTION, php_http_curl_progress_callback); + PHP_HTTP_CURL_OPT(CURLOPT_URL, NULL); +#if PHP_HTTP_CURL_VERSION(7,19,4) + PHP_HTTP_CURL_OPT(CURLOPT_NOPROXY, NULL); +#endif + PHP_HTTP_CURL_OPT(CURLOPT_PROXY, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_PROXYPORT, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_PROXYTYPE, 0L); + /* libcurl < 7.19.6 does not clear auth info with USERPWD set to NULL */ +#if PHP_HTTP_CURL_VERSION(7,19,1) + PHP_HTTP_CURL_OPT(CURLOPT_PROXYUSERNAME, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_PROXYPASSWORD, NULL); +#endif + PHP_HTTP_CURL_OPT(CURLOPT_PROXYAUTH, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_HTTPPROXYTUNNEL, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_DNS_CACHE_TIMEOUT, 60L); + PHP_HTTP_CURL_OPT(CURLOPT_IPRESOLVE, 0); + PHP_HTTP_CURL_OPT(CURLOPT_LOW_SPEED_LIMIT, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_LOW_SPEED_TIME, 0L); + /* LFS weirdance + PHP_HTTP_CURL_OPT(CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) 0); + PHP_HTTP_CURL_OPT(CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) 0); + */ + /* crashes + PHP_HTTP_CURL_OPT(CURLOPT_MAXCONNECTS, 5L); */ + PHP_HTTP_CURL_OPT(CURLOPT_FRESH_CONNECT, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_FORBID_REUSE, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_INTERFACE, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_PORT, 0L); +#if PHP_HTTP_CURL_VERSION(7,19,0) + PHP_HTTP_CURL_OPT(CURLOPT_ADDRESS_SCOPE, 0L); +#endif + PHP_HTTP_CURL_OPT(CURLOPT_LOCALPORT, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_LOCALPORTRANGE, 0L); + /* libcurl < 7.19.6 does not clear auth info with USERPWD set to NULL */ +#if PHP_HTTP_CURL_VERSION(7,19,1) + PHP_HTTP_CURL_OPT(CURLOPT_USERNAME, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_PASSWORD, NULL); +#endif + PHP_HTTP_CURL_OPT(CURLOPT_HTTPAUTH, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_ENCODING, NULL); + /* we do this ourself anyway */ + PHP_HTTP_CURL_OPT(CURLOPT_HTTP_CONTENT_DECODING, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_HTTP_TRANSFER_DECODING, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_FOLLOWLOCATION, 0L); +#if PHP_HTTP_CURL_VERSION(7,19,1) + PHP_HTTP_CURL_OPT(CURLOPT_POSTREDIR, 0L); +#else + PHP_HTTP_CURL_OPT(CURLOPT_POST301, 0L); +#endif + PHP_HTTP_CURL_OPT(CURLOPT_UNRESTRICTED_AUTH, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_REFERER, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_USERAGENT, "PECL::HTTP/" PHP_HTTP_EXT_VERSION " (PHP/" PHP_VERSION ")"); + PHP_HTTP_CURL_OPT(CURLOPT_HTTPHEADER, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_COOKIE, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_COOKIESESSION, 0L); + /* these options would enable curl's cookie engine by default which we don't want + PHP_HTTP_CURL_OPT(CURLOPT_COOKIEFILE, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_COOKIEJAR, NULL); */ + PHP_HTTP_CURL_OPT(CURLOPT_COOKIELIST, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_RANGE, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_RESUME_FROM, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_MAXFILESIZE, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_TIMECONDITION, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_TIMEVALUE, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_TIMEOUT, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_CONNECTTIMEOUT, 3); + PHP_HTTP_CURL_OPT(CURLOPT_SSLCERT, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_SSLCERTTYPE, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_SSLCERTPASSWD, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_SSLKEY, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_SSLKEYTYPE, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_SSLKEYPASSWD, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_SSLENGINE, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_SSLVERSION, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_SSL_VERIFYPEER, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_SSL_VERIFYHOST, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_SSL_CIPHER_LIST, NULL); +#if PHP_HTTP_CURL_VERSION(7,19,0) + PHP_HTTP_CURL_OPT(CURLOPT_ISSUERCERT, NULL); + #if defined(PHP_HTTP_HAVE_OPENSSL) + PHP_HTTP_CURL_OPT(CURLOPT_CRLFILE, NULL); + #endif +#endif +#if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL) + PHP_HTTP_CURL_OPT(CURLOPT_CERTINFO, NULL); +#endif +#ifdef PHP_HTTP_CURL_CAINFO + PHP_HTTP_CURL_OPT(CURLOPT_CAINFO, PHP_HTTP_CURL_CAINFO); +#else + PHP_HTTP_CURL_OPT(CURLOPT_CAINFO, NULL); +#endif + PHP_HTTP_CURL_OPT(CURLOPT_CAPATH, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_RANDOM_FILE, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_EGDSOCKET, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_POSTFIELDS, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_POSTFIELDSIZE, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_HTTPPOST, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_IOCTLDATA, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_READDATA, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_INFILESIZE, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_NONE); + PHP_HTTP_CURL_OPT(CURLOPT_CUSTOMREQUEST, NULL); + PHP_HTTP_CURL_OPT(CURLOPT_NOBODY, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_POST, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_UPLOAD, 0L); + PHP_HTTP_CURL_OPT(CURLOPT_HTTPGET, 1L); + } +} + +PHP_HTTP_API void php_http_request_set_progress_callback(php_http_request_t *request, zval *cb) +{ + if (request->_progress.callback) { + zval_ptr_dtor(&request->_progress.callback); + } + if ((request->_progress.callback = cb)) { + Z_ADDREF_P(cb); + } +} + +PHP_HTTP_API STATUS php_http_request_prepare(php_http_request_t *request, HashTable *options) +{ + zval *zoption; + zend_bool range_req = 0; + php_http_request_storage_t *storage; + + TSRMLS_FETCH_FROM_CTX(request->ts); + + PHP_HTTP_CHECK_CURL_INIT(request->ch, php_http_curl_init(NULL, request TSRMLS_CC), return FAILURE); + + if (!(storage = php_http_request_storage_get(request->ch))) { + return FAILURE; + } + storage->errorbuffer[0] = '\0'; + /* set options */ + if (storage->url) { + pefree(storage->url, 1); + } + storage->url = pestrdup(request->url, 1); + PHP_HTTP_CURL_OPT(CURLOPT_URL, storage->url); + + /* progress callback */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("onprogress"), -1))) { + php_http_request_set_progress_callback(request, zoption); + } + + /* proxy */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxyhost"), IS_STRING))) { + PHP_HTTP_CURL_OPT(CURLOPT_PROXY, Z_STRVAL_P(zoption)); + /* type */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxytype"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_PROXYTYPE, Z_LVAL_P(zoption)); + } + /* port */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxyport"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_PROXYPORT, Z_LVAL_P(zoption)); + } + /* user:pass */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxyauth"), IS_STRING)) && Z_STRLEN_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_PROXYUSERPWD, Z_STRVAL_P(zoption)); + } + /* auth method */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxyauthtype"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_PROXYAUTH, Z_LVAL_P(zoption)); + } + /* tunnel */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("proxytunnel"), IS_BOOL)) && Z_BVAL_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_HTTPPROXYTUNNEL, 1L); + } + } +#if PHP_HTTP_CURL_VERSION(7,19,4) + if ((zoption = php_http_request_option(request, options, ZEND_STRS("noproxy"), IS_STRING))) { + PHP_HTTP_CURL_OPT(CURLOPT_NOPROXY, Z_STRVAL_P(zoption)); + } +#endif + + /* dns */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("dns_cache_timeout"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_DNS_CACHE_TIMEOUT, Z_LVAL_P(zoption)); + } + if ((zoption = php_http_request_option(request, options, ZEND_STRS("ipresolve"), IS_LONG)) && Z_LVAL_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_IPRESOLVE, Z_LVAL_P(zoption)); + } + + /* limits */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("low_speed_limit"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_LOW_SPEED_LIMIT, Z_LVAL_P(zoption)); + } + if ((zoption = php_http_request_option(request, options, ZEND_STRS("low_speed_time"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_LOW_SPEED_TIME, Z_LVAL_P(zoption)); + } + /* LSF weirdance + if ((zoption = php_http_request_option(request, options, ZEND_STRS("max_send_speed"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_MAX_SEND_SPEED_LARGE, (curl_off_t) Z_LVAL_P(zoption)); + } + if ((zoption = php_http_request_option(request, options, ZEND_STRS("max_recv_speed"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) Z_LVAL_P(zoption)); + } + */ + /* crashes + if ((zoption = php_http_request_option(request, options, ZEND_STRS("maxconnects"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_MAXCONNECTS, Z_LVAL_P(zoption)); + } */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("fresh_connect"), IS_BOOL)) && Z_BVAL_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_FRESH_CONNECT, 1L); + } + if ((zoption = php_http_request_option(request, options, ZEND_STRS("forbid_reuse"), IS_BOOL)) && Z_BVAL_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_FORBID_REUSE, 1L); + } + + /* outgoing interface */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("interface"), IS_STRING))) { + PHP_HTTP_CURL_OPT(CURLOPT_INTERFACE, Z_STRVAL_P(zoption)); + + if ((zoption = php_http_request_option(request, options, ZEND_STRS("portrange"), IS_ARRAY))) { + zval **prs, **pre; + + zend_hash_internal_pointer_reset(Z_ARRVAL_P(zoption)); + if (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void *) &prs)) { + zend_hash_move_forward(Z_ARRVAL_P(zoption)); + if (SUCCESS == zend_hash_get_current_data(Z_ARRVAL_P(zoption), (void *) &pre)) { + zval *prs_cpy = php_http_zsep(IS_LONG, *prs); + zval *pre_cpy = php_http_zsep(IS_LONG, *pre); + + if (Z_LVAL_P(prs_cpy) && Z_LVAL_P(pre_cpy)) { + PHP_HTTP_CURL_OPT(CURLOPT_LOCALPORT, MIN(Z_LVAL_P(prs_cpy), Z_LVAL_P(pre_cpy))); + PHP_HTTP_CURL_OPT(CURLOPT_LOCALPORTRANGE, labs(Z_LVAL_P(prs_cpy)-Z_LVAL_P(pre_cpy))+1L); + } + zval_ptr_dtor(&prs_cpy); + zval_ptr_dtor(&pre_cpy); + } + } + } + } + + /* another port */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("port"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_PORT, Z_LVAL_P(zoption)); + } + + /* RFC4007 zone_id */ +#if PHP_HTTP_CURL_VERSION(7,19,0) + if ((zoption = php_http_request_option(request, options, ZEND_STRS("address_scope"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_ADDRESS_SCOPE, Z_LVAL_P(zoption)); + } +#endif + + /* auth */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("httpauth"), IS_STRING)) && Z_STRLEN_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_USERPWD, Z_STRVAL_P(zoption)); + } + if ((zoption = php_http_request_option(request, options, ZEND_STRS("httpauthtype"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_HTTPAUTH, Z_LVAL_P(zoption)); + } + + /* redirects, defaults to 0 */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("redirect"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_FOLLOWLOCATION, Z_LVAL_P(zoption) ? 1L : 0L); + PHP_HTTP_CURL_OPT(CURLOPT_MAXREDIRS, Z_LVAL_P(zoption)); + if ((zoption = php_http_request_option(request, options, ZEND_STRS("unrestrictedauth"), IS_BOOL))) { + PHP_HTTP_CURL_OPT(CURLOPT_UNRESTRICTED_AUTH, Z_LVAL_P(zoption)); + } + if ((zoption = php_http_request_option(request, options, ZEND_STRS("postredir"), IS_BOOL))) { +#if PHP_HTTP_CURL_VERSION(7,19,1) + PHP_HTTP_CURL_OPT(CURLOPT_POSTREDIR, Z_BVAL_P(zoption) ? 1L : 0L); +#else + PHP_HTTP_CURL_OPT(CURLOPT_POST301, Z_BVAL_P(zoption) ? 1L : 0L); +#endif + } + } + + /* retries, defaults to 0 */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("retrycount"), IS_LONG))) { + request->_retry.count = Z_LVAL_P(zoption); + if ((zoption = php_http_request_option(request, options, ZEND_STRS("retrydelay"), IS_DOUBLE))) { + request->_retry.delay = Z_DVAL_P(zoption); + } else { + request->_retry.delay = 0; + } + } else { + request->_retry.count = 0; + } + + /* referer */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("referer"), IS_STRING)) && Z_STRLEN_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_REFERER, Z_STRVAL_P(zoption)); + } + + /* useragent, default "PECL::HTTP/version (PHP/version)" */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("useragent"), IS_STRING))) { + /* allow to send no user agent, not even default one */ + if (Z_STRLEN_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_USERAGENT, Z_STRVAL_P(zoption)); + } else { + PHP_HTTP_CURL_OPT(CURLOPT_USERAGENT, NULL); + } + } + + /* resume */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("resume"), IS_LONG)) && (Z_LVAL_P(zoption) > 0)) { + range_req = 1; + PHP_HTTP_CURL_OPT(CURLOPT_RESUME_FROM, Z_LVAL_P(zoption)); + } + /* or range of kind array(array(0,499), array(100,1499)) */ + else if ((zoption = php_http_request_option(request, options, ZEND_STRS("range"), IS_ARRAY)) && zend_hash_num_elements(Z_ARRVAL_P(zoption))) { + HashPosition pos1, pos2; + zval **rr, **rb, **re; + php_http_buffer rs; + + php_http_buffer_init(&rs); + FOREACH_VAL(pos1, zoption, rr) { + if (Z_TYPE_PP(rr) == IS_ARRAY) { + zend_hash_internal_pointer_reset_ex(Z_ARRVAL_PP(rr), &pos2); + if (SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(rr), (void *) &rb, &pos2)) { + zend_hash_move_forward_ex(Z_ARRVAL_PP(rr), &pos2); + if (SUCCESS == zend_hash_get_current_data_ex(Z_ARRVAL_PP(rr), (void *) &re, &pos2)) { + if ( ((Z_TYPE_PP(rb) == IS_LONG) || ((Z_TYPE_PP(rb) == IS_STRING) && is_numeric_string(Z_STRVAL_PP(rb), Z_STRLEN_PP(rb), NULL, NULL, 1))) && + ((Z_TYPE_PP(re) == IS_LONG) || ((Z_TYPE_PP(re) == IS_STRING) && is_numeric_string(Z_STRVAL_PP(re), Z_STRLEN_PP(re), NULL, NULL, 1)))) { + zval *rbl = php_http_zsep(IS_LONG, *rb); + zval *rel = php_http_zsep(IS_LONG, *re); + + if ((Z_LVAL_P(rbl) >= 0) && (Z_LVAL_P(rel) >= 0)) { + php_http_buffer_appendf(&rs, "%ld-%ld,", Z_LVAL_P(rbl), Z_LVAL_P(rel)); + } + zval_ptr_dtor(&rbl); + zval_ptr_dtor(&rel); + } + } + } + } + } + + if (PHP_HTTP_BUFFER_LEN(&rs)) { + zval *cached_range; + + /* ditch last comma */ + PHP_HTTP_BUFFER_VAL(&rs)[PHP_HTTP_BUFFER_LEN(&rs)-- -1] = '\0'; + /* cache string */ + MAKE_STD_ZVAL(cached_range); + ZVAL_STRINGL(cached_range, PHP_HTTP_BUFFER_VAL(&rs), PHP_HTTP_BUFFER_LEN(&rs), 0); + PHP_HTTP_CURL_OPT(CURLOPT_RANGE, Z_STRVAL_P(php_http_request_option_cache(request, ZEND_STRS("range"), 0, cached_range))); + zval_ptr_dtor(&cached_range); + } + } + + /* additional headers, array('name' => 'value') */ + if (request->_cache.headers) { + curl_slist_free_all(request->_cache.headers); + request->_cache.headers = NULL; + } + if ((zoption = php_http_request_option(request, options, ZEND_STRS("headers"), IS_ARRAY))) { + php_http_array_hashkey_t header_key = php_http_array_hashkey_init(0); + zval **header_val; + HashPosition pos; + php_http_buffer header; + + php_http_buffer_init(&header); + FOREACH_KEYVAL(pos, zoption, header_key, header_val) { + if (header_key.type == HASH_KEY_IS_STRING) { + zval *header_cpy = php_http_zsep(IS_STRING, *header_val); + + if (!strcasecmp(header_key.str, "range")) { + range_req = 1; + } + + php_http_buffer_appendf(&header, "%s: %s", header_key.str, Z_STRVAL_P(header_cpy)); + php_http_buffer_fix(&header); + request->_cache.headers = curl_slist_append(request->_cache.headers, PHP_HTTP_BUFFER_VAL(&header)); + php_http_buffer_reset(&header); + + zval_ptr_dtor(&header_cpy); + } + } + php_http_buffer_dtor(&header); + } + /* etag */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("etag"), IS_STRING)) && Z_STRLEN_P(zoption)) { + zend_bool is_quoted = !((Z_STRVAL_P(zoption)[0] != '"') || (Z_STRVAL_P(zoption)[Z_STRLEN_P(zoption)-1] != '"')); + php_http_buffer header; + + php_http_buffer_init(&header); + php_http_buffer_appendf(&header, is_quoted?"%s: %s":"%s: \"%s\"", range_req?"If-Match":"If-None-Match", Z_STRVAL_P(zoption)); + php_http_buffer_fix(&header); + request->_cache.headers = curl_slist_append(request->_cache.headers, PHP_HTTP_BUFFER_VAL(&header)); + php_http_buffer_dtor(&header); + } + /* compression */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("compress"), IS_BOOL)) && Z_LVAL_P(zoption)) { + request->_cache.headers = curl_slist_append(request->_cache.headers, "Accept-Encoding: gzip;q=1.0,deflate;q=0.5"); + } + PHP_HTTP_CURL_OPT(CURLOPT_HTTPHEADER, request->_cache.headers); + + /* lastmodified */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("lastmodified"), IS_LONG))) { + if (Z_LVAL_P(zoption)) { + if (Z_LVAL_P(zoption) > 0) { + PHP_HTTP_CURL_OPT(CURLOPT_TIMEVALUE, Z_LVAL_P(zoption)); + } else { + PHP_HTTP_CURL_OPT(CURLOPT_TIMEVALUE, (long) PHP_HTTP_G->env.request.time + Z_LVAL_P(zoption)); + } + PHP_HTTP_CURL_OPT(CURLOPT_TIMECONDITION, (long) (range_req ? CURL_TIMECOND_IFUNMODSINCE : CURL_TIMECOND_IFMODSINCE)); + } else { + PHP_HTTP_CURL_OPT(CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); + } + } + + /* cookies, array('name' => 'value') */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("cookies"), IS_ARRAY))) { + php_http_buffer_dtor(&request->_cache.cookies); + if (zend_hash_num_elements(Z_ARRVAL_P(zoption))) { + zval *urlenc_cookies = NULL; + /* check whether cookies should not be urlencoded; default is to urlencode them */ + if ((!(urlenc_cookies = php_http_request_option(request, options, ZEND_STRS("encodecookies"), IS_BOOL))) || Z_BVAL_P(urlenc_cookies)) { + if (SUCCESS == php_http_url_encode_hash_recursive(HASH_OF(zoption), &request->_cache.cookies, "; ", lenof("; "), NULL, 0 TSRMLS_CC)) { + php_http_buffer_fix(&request->_cache.cookies); + PHP_HTTP_CURL_OPT(CURLOPT_COOKIE, request->_cache.cookies.data); + } + } else { + HashPosition pos; + php_http_array_hashkey_t cookie_key = php_http_array_hashkey_init(0); + zval **cookie_val; + + FOREACH_KEYVAL(pos, zoption, cookie_key, cookie_val) { + if (cookie_key.type == HASH_KEY_IS_STRING) { + zval *val = php_http_zsep(IS_STRING, *cookie_val); + php_http_buffer_appendf(&request->_cache.cookies, "%s=%s; ", cookie_key.str, Z_STRVAL_P(val)); + zval_ptr_dtor(&val); + } + } + + php_http_buffer_fix(&request->_cache.cookies); + if (PHP_HTTP_BUFFER_LEN(&request->_cache.cookies)) { + PHP_HTTP_CURL_OPT(CURLOPT_COOKIE, PHP_HTTP_BUFFER_VAL(&request->_cache.cookies)); + } + } + } + } + + /* don't load session cookies from cookiestore */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("cookiesession"), IS_BOOL)) && Z_BVAL_P(zoption)) { + PHP_HTTP_CURL_OPT(CURLOPT_COOKIESESSION, 1L); + } + + /* cookiestore, read initial cookies from that file and store cookies back into that file */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("cookiestore"), IS_STRING))) { + if (Z_STRLEN_P(zoption)) { + if (SUCCESS != php_check_open_basedir(Z_STRVAL_P(zoption) TSRMLS_CC)) { + return FAILURE; + } + } + if (storage->cookiestore) { + pefree(storage->cookiestore, 1); + } + storage->cookiestore = pestrndup(Z_STRVAL_P(zoption), Z_STRLEN_P(zoption), 1); + PHP_HTTP_CURL_OPT(CURLOPT_COOKIEFILE, storage->cookiestore); + PHP_HTTP_CURL_OPT(CURLOPT_COOKIEJAR, storage->cookiestore); + } + + /* maxfilesize */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("maxfilesize"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_MAXFILESIZE, Z_LVAL_P(zoption)); + } + + /* http protocol */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("protocol"), IS_LONG))) { + PHP_HTTP_CURL_OPT(CURLOPT_HTTP_VERSION, Z_LVAL_P(zoption)); + } + + /* timeout, defaults to 0 */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("timeout"), IS_DOUBLE))) { + PHP_HTTP_CURL_OPT(CURLOPT_TIMEOUT_MS, (long)(Z_DVAL_P(zoption)*1000)); + } + /* connecttimeout, defaults to 0 */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("connecttimeout"), IS_DOUBLE))) { + PHP_HTTP_CURL_OPT(CURLOPT_CONNECTTIMEOUT_MS, (long)(Z_DVAL_P(zoption)*1000)); + } + + /* ssl */ + if ((zoption = php_http_request_option(request, options, ZEND_STRS("ssl"), IS_ARRAY))) { + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + zval **param; + HashPosition pos; + + FOREACH_KEYVAL(pos, zoption, key, param) { + if (key.type == HASH_KEY_IS_STRING) { + PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLCERT, 0, 1); + PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLCERTTYPE, 0, 0); + PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLCERTPASSWD, 0, 0); + + PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLKEY, 0, 0); + PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLKEYTYPE, 0, 0); + PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLKEYPASSWD, 0, 0); + + PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSLENGINE, 0, 0); + PHP_HTTP_CURL_OPT_LONG(CURLOPT_SSLVERSION, 0); + + PHP_HTTP_CURL_OPT_LONG(CURLOPT_SSL_VERIFYPEER, 1); + PHP_HTTP_CURL_OPT_LONG(CURLOPT_SSL_VERIFYHOST, 1); + PHP_HTTP_CURL_OPT_STRING(CURLOPT_SSL_CIPHER_LIST, 1, 0); + + PHP_HTTP_CURL_OPT_STRING(CURLOPT_CAINFO, -3, 1); + PHP_HTTP_CURL_OPT_STRING(CURLOPT_CAPATH, -3, 1); + PHP_HTTP_CURL_OPT_STRING(CURLOPT_RANDOM_FILE, -3, 1); + PHP_HTTP_CURL_OPT_STRING(CURLOPT_EGDSOCKET, -3, 1); +#if PHP_HTTP_CURL_VERSION(7,19,0) + PHP_HTTP_CURL_OPT_STRING(CURLOPT_ISSUERCERT, -3, 1); + #if defined(PHP_HTTP_HAVE_OPENSSL) + PHP_HTTP_CURL_OPT_STRING(CURLOPT_CRLFILE, -3, 1); + #endif +#endif +#if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL) + PHP_HTTP_CURL_OPT_LONG(CURLOPT_CERTINFO, -3); +#endif + } + } + } + + /* request method */ + switch (request->meth) { + case PHP_HTTP_GET: + PHP_HTTP_CURL_OPT(CURLOPT_HTTPGET, 1L); + break; + + case PHP_HTTP_HEAD: + PHP_HTTP_CURL_OPT(CURLOPT_NOBODY, 1L); + break; + + case PHP_HTTP_POST: + PHP_HTTP_CURL_OPT(CURLOPT_POST, 1L); + break; + + case PHP_HTTP_PUT: + PHP_HTTP_CURL_OPT(CURLOPT_UPLOAD, 1L); + break; + + default: { + const char *meth = php_http_request_method_name(request->meth); + + if (meth) { + PHP_HTTP_CURL_OPT(CURLOPT_CUSTOMREQUEST, meth); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_METHOD, "Unsupported request method: %d (%s)", request->meth, request->url); + return FAILURE; + } + break; + } + } + + /* attach request body */ + if (request->body && (request->meth != PHP_HTTP_GET) && (request->meth != PHP_HTTP_HEAD) && (request->meth != PHP_HTTP_OPTIONS)) { + if (1 || request->meth == PHP_HTTP_PUT) { + /* PUT/UPLOAD _needs_ READDATA */ + PHP_HTTP_CURL_OPT(CURLOPT_IOCTLDATA, request); + PHP_HTTP_CURL_OPT(CURLOPT_READDATA, request); + PHP_HTTP_CURL_OPT(CURLOPT_INFILESIZE, php_http_message_body_size(request->body)); + } else { + abort(); + //PHP_HTTP_CURL_OPT(CURLOPT_POSTFIELDS, request->body->real->data); + PHP_HTTP_CURL_OPT(CURLOPT_POSTFIELDSIZE, php_http_message_body_size(request->body)); + } + } + + return SUCCESS; +} + +PHP_HTTP_API void php_http_request_exec(php_http_request_t *request) +{ + uint tries = 0; + CURLcode result; + TSRMLS_FETCH_FROM_CTX(request->ts); + +retry: + if (CURLE_OK != (result = curl_easy_perform(request->ch))) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "%s; %s (%s)", curl_easy_strerror(result), php_http_request_storage_get(request->ch)->errorbuffer, request->url); + if (EG(exception)) { + add_property_long(EG(exception), "curlCode", result); + } + + if (request->_retry.count > tries++) { + switch (result) { + case CURLE_COULDNT_RESOLVE_PROXY: + case CURLE_COULDNT_RESOLVE_HOST: + case CURLE_COULDNT_CONNECT: + case CURLE_WRITE_ERROR: + case CURLE_READ_ERROR: + case CURLE_OPERATION_TIMEDOUT: + case CURLE_SSL_CONNECT_ERROR: + case CURLE_GOT_NOTHING: + case CURLE_SSL_ENGINE_SETFAILED: + case CURLE_SEND_ERROR: + case CURLE_RECV_ERROR: + case CURLE_SSL_ENGINE_INITFAILED: + case CURLE_LOGIN_DENIED: + if (request->_retry.delay >= PHP_HTTP_DIFFSEC) { + php_http_sleep(request->_retry.delay); + } + goto retry; + default: + break; + } + } + } +} + +static size_t php_http_curl_read_callback(void *data, size_t len, size_t n, void *ctx) +{ + php_http_request_t *request = (php_http_request_t *) ctx; + TSRMLS_FETCH_FROM_CTX(request->ts); + + if (request->body) { + return php_stream_read(php_http_message_body_stream(request->body), data, len * n); + } + return 0; +} + +static int php_http_curl_progress_callback(void *ctx, double dltotal, double dlnow, double ultotal, double ulnow) +{ + php_http_request_t *request = (php_http_request_t *) ctx; + + request->_progress.state.dl.total = dltotal; + request->_progress.state.dl.now = dlnow; + request->_progress.state.ul.total = ultotal; + request->_progress.state.ul.now = ulnow; + + if (request->_progress.callback) { + zval *param, retval; + TSRMLS_FETCH_FROM_CTX(request->ts); + + INIT_PZVAL(&retval); + ZVAL_NULL(&retval); + + MAKE_STD_ZVAL(param); + array_init(param); + add_assoc_double(param, "dltotal", request->_progress.state.dl.total); + add_assoc_double(param, "dlnow", request->_progress.state.dl.now); + add_assoc_double(param, "ultotal", request->_progress.state.ul.total); + add_assoc_double(param, "ulnow", request->_progress.state.ul.now); + + with_error_handling(EH_NORMAL, NULL) { + request->_progress.in_cb = 1; + call_user_function(EG(function_table), NULL, request->_progress.callback, &retval, 1, ¶m TSRMLS_CC); + request->_progress.in_cb = 0; + } end_error_handling(); + + zval_ptr_dtor(¶m); + zval_dtor(&retval); + } + + return 0; +} + +static curlioerr php_http_curl_ioctl_callback(CURL *ch, curliocmd cmd, void *ctx) +{ + php_http_request_t *request = (php_http_request_t *) ctx; + TSRMLS_FETCH_FROM_CTX(request->ts); + + if (cmd != CURLIOCMD_RESTARTREAD) { + return CURLIOE_UNKNOWNCMD; + } + + if (request->body) { + if (SUCCESS == php_stream_rewind(php_http_message_body_stream(request->body))) { + return CURLIOE_OK; + } + } + + return CURLIOE_FAILRESTART; +} + +static int php_http_curl_raw_callback(CURL *ch, curl_infotype type, char *data, size_t length, void *ctx) +{ + php_http_request_t *request = (php_http_request_t *) ctx; + + /* process data */ + switch (type) { + case CURLINFO_HEADER_IN: + case CURLINFO_DATA_IN: + case CURLINFO_HEADER_OUT: + case CURLINFO_DATA_OUT: + php_http_buffer_append(request->parser.buf, data, length); + if (PHP_HTTP_MESSAGE_PARSER_STATE_FAILURE == php_http_message_parser_parse(request->parser.ctx, request->parser.buf, 0, &request->parser.msg)) { + return -1; + } + break; + default: + break; + } + + /* debug */ +#if 0 + { + const char _sym[] = "><><><"; + if (type) { + for (fprintf(stderr, "%c ", _sym[type-1]); length--; data++) { + fprintf(stderr, PHP_HTTP_IS_CTYPE(print, *data)?"%c":"\\x%02X", (int) *data); + if (*data == '\n' && length) { + fprintf(stderr, "\n%c ", _sym[type-1]); + } + } + fprintf(stderr, "\n"); + } else { + fprintf(stderr, "# %s", data); + } + } +#endif + + return 0; +} + +static inline zval *php_http_request_option(php_http_request_t *r, HashTable *options, char *key, size_t keylen, int type) +{ + TSRMLS_FETCH_FROM_CTX(r->ts); + + if (options) { + zval **zoption; + ulong h = zend_hash_func(key, keylen); + + if (SUCCESS == zend_hash_quick_find(options, key, keylen, h, (void *) &zoption)) { + zval *option, *cached; + + option = php_http_zsep(type, *zoption); + cached = php_http_request_option_cache(r, key, keylen, h, option); + + zval_ptr_dtor(&option); + return cached; + } + } + + return NULL; +} + +static inline zval *php_http_request_option_cache(php_http_request_t *r, char *key, size_t keylen, ulong h, zval *opt) +{ + TSRMLS_FETCH_FROM_CTX(r->ts); + Z_ADDREF_P(opt); + + if (h) { + zend_hash_quick_update(&r->_cache.options, key, keylen, h, &opt, sizeof(zval *), NULL); + } else { + zend_hash_update(&r->_cache.options, key, keylen, &opt, sizeof(zval *), NULL); + } + + return opt; +} + +static inline int php_http_request_cookies_enabled(php_http_request_t *request) { + php_http_request_storage_t *st; + + if (request->ch && (st = php_http_request_storage_get(request->ch)) && st->cookiestore) { + /* cookies are enabled */ + return 1; + } + return 0; +} + +/* USERLAND */ + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpRequest, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpRequest, method, 0) +#define PHP_HTTP_REQUEST_ME(method, visibility) PHP_ME(HttpRequest, method, PHP_HTTP_ARGS(HttpRequest, method), visibility) +#define PHP_HTTP_REQUEST_ALIAS(method, func) PHP_HTTP_STATIC_ME_ALIAS(method, func, PHP_HTTP_ARGS(HttpRequest, method)) +#define PHP_HTTP_REQUEST_MALIAS(me, al, vis) ZEND_FENTRY(me, ZEND_MN(HttpRequest_##al), PHP_HTTP_ARGS(HttpRequest, al), vis) + +PHP_HTTP_BEGIN_ARGS(__construct, 0) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(method, 0) + PHP_HTTP_ARG_VAL(options, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(factory, 0) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(method, 0) + PHP_HTTP_ARG_VAL(options, 0) + PHP_HTTP_ARG_VAL(class_name, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getOptions); +PHP_HTTP_BEGIN_ARGS(setOptions, 0) + PHP_HTTP_ARG_VAL(options, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getSslOptions); +PHP_HTTP_BEGIN_ARGS(setSslOptions, 0) + PHP_HTTP_ARG_VAL(ssl_options, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(addSslOptions, 0) + PHP_HTTP_ARG_VAL(ssl_optins, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getHeaders); +PHP_HTTP_BEGIN_ARGS(setHeaders, 0) + PHP_HTTP_ARG_VAL(headers, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(addHeaders, 1) + PHP_HTTP_ARG_VAL(headers, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getCookies); +PHP_HTTP_BEGIN_ARGS(setCookies, 0) + PHP_HTTP_ARG_VAL(cookies, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(addCookies, 1) + PHP_HTTP_ARG_VAL(cookies, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(enableCookies); +PHP_HTTP_BEGIN_ARGS(resetCookies, 0) + PHP_HTTP_ARG_VAL(session_only, 0) +PHP_HTTP_END_ARGS; +PHP_HTTP_EMPTY_ARGS(flushCookies); + +PHP_HTTP_EMPTY_ARGS(getUrl); +PHP_HTTP_BEGIN_ARGS(setUrl, 1) + PHP_HTTP_ARG_VAL(url, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getMethod); +PHP_HTTP_BEGIN_ARGS(setMethod, 1) + PHP_HTTP_ARG_VAL(request_method, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getContentType); +PHP_HTTP_BEGIN_ARGS(setContentType, 1) + PHP_HTTP_ARG_VAL(content_type, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getQueryData); +PHP_HTTP_BEGIN_ARGS(setQueryData, 0) + PHP_HTTP_ARG_VAL(query_data, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(addQueryData, 1) + PHP_HTTP_ARG_VAL(query_data, 0) +PHP_HTTP_END_ARGS; + + +PHP_HTTP_EMPTY_ARGS(getBody); +PHP_HTTP_BEGIN_ARGS(setBody, 0) + PHP_HTTP_ARG_OBJ(http\\message\\Body, body, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(addBody, 1) + PHP_HTTP_ARG_OBJ(http\\message\\Body, body, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(getResponseCookies, 0) + PHP_HTTP_ARG_VAL(flags, 0) + PHP_HTTP_ARG_VAL(allowed_extras, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getResponseBody); +PHP_HTTP_EMPTY_ARGS(getResponseCode); +PHP_HTTP_EMPTY_ARGS(getResponseStatus); +PHP_HTTP_BEGIN_ARGS(getResponseInfo, 0) + PHP_HTTP_ARG_VAL(name, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(getResponseHeader, 0) + PHP_HTTP_ARG_VAL(header_name, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getMessageClass); +PHP_HTTP_BEGIN_ARGS(setMessageClass, 1) + PHP_HTTP_ARG_VAL(message_class_name, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(getResponseMessage); +PHP_HTTP_EMPTY_ARGS(getRawResponseMessage); +PHP_HTTP_EMPTY_ARGS(getRequestMessage); +PHP_HTTP_EMPTY_ARGS(getRawRequestMessage); +PHP_HTTP_EMPTY_ARGS(getHistory); +PHP_HTTP_EMPTY_ARGS(clearHistory); +PHP_HTTP_EMPTY_ARGS(send); + +PHP_HTTP_BEGIN_ARGS(get, 1) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(options, 0) + PHP_HTTP_ARG_VAL(info, 1) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(head, 1) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(options, 0) + PHP_HTTP_ARG_VAL(info, 1) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(postData, 2) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(data, 0) + PHP_HTTP_ARG_VAL(options, 0) + PHP_HTTP_ARG_VAL(info, 1) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(postFields, 2) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(data, 0) + PHP_HTTP_ARG_VAL(options, 0) + PHP_HTTP_ARG_VAL(info, 1) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(putData, 2) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(data, 0) + PHP_HTTP_ARG_VAL(options, 0) + PHP_HTTP_ARG_VAL(info, 1) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(putFile, 2) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(file, 0) + PHP_HTTP_ARG_VAL(options, 0) + PHP_HTTP_ARG_VAL(info, 1) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(putStream, 2) + PHP_HTTP_ARG_VAL(url, 0) + PHP_HTTP_ARG_VAL(stream, 0) + PHP_HTTP_ARG_VAL(options, 0) + PHP_HTTP_ARG_VAL(info, 1) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(methodRegister, 1) + PHP_HTTP_ARG_VAL(method_name, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(methodUnregister, 1) + PHP_HTTP_ARG_VAL(method, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(methodName, 1) + PHP_HTTP_ARG_VAL(method_id, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(methodExists, 1) + PHP_HTTP_ARG_VAL(method, 0) +PHP_HTTP_END_ARGS; + +#ifdef HAVE_CURL_FORMGET +PHP_HTTP_BEGIN_ARGS(encodeBody, 2) + PHP_HTTP_ARG_VAL(fields, 0) + PHP_HTTP_ARG_VAL(files, 0) +PHP_HTTP_END_ARGS; +#endif + +zend_class_entry *php_http_request_class_entry; +zend_function_entry php_http_request_method_entry[] = { + PHP_HTTP_REQUEST_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + + PHP_HTTP_REQUEST_ME(setOptions, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getOptions, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(setSslOptions, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getSslOptions, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(addSslOptions, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(addHeaders, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getHeaders, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(setHeaders, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(addCookies, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getCookies, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(setCookies, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(enableCookies, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(resetCookies, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(flushCookies, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(setMethod, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getMethod, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(setUrl, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getUrl, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(setContentType, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getContentType, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(setQueryData, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getQueryData, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(addQueryData, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(setBody, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getBody, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(addBody, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(send, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(getResponseHeader, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getResponseCookies, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getResponseCode, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getResponseStatus, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getResponseBody, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getResponseInfo, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getResponseMessage, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getRequestMessage, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(getHistory, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(clearHistory, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQUEST_ME(getMessageClass, ZEND_ACC_PUBLIC) + PHP_HTTP_REQUEST_ME(setMessageClass, ZEND_ACC_PUBLIC) + + EMPTY_FUNCTION_ENTRY +}; +static zend_object_handlers php_http_request_object_handlers; + +zend_object_value php_http_request_object_new(zend_class_entry *ce TSRMLS_DC) +{ + return php_http_request_object_new_ex(ce, NULL, NULL); +} + +zend_object_value php_http_request_object_new_ex(zend_class_entry *ce, CURL *ch, php_http_request_object_t **ptr TSRMLS_DC) +{ + zend_object_value ov; + php_http_request_object_t *o; + + o = ecalloc(1, sizeof(php_http_request_object_t)); + zend_object_std_init((zend_object *) o, ce TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + + o->request = php_http_request_init(NULL, ch, 0, NULL TSRMLS_CC); + + if (ptr) { + *ptr = o; + } + + ov.handle = zend_objects_store_put(o, NULL, php_http_request_object_free, NULL TSRMLS_CC); + ov.handlers = &php_http_request_object_handlers; + + return ov; +} + +zend_object_value php_http_request_object_clone(zval *this_ptr TSRMLS_DC) +{ + zend_object_value new_ov; + php_http_request_object_t *new_obj, *old_obj = zend_object_store_get_object(this_ptr TSRMLS_CC); + + new_ov = php_http_request_object_new_ex(old_obj->zo.ce, NULL, &new_obj TSRMLS_CC); + if (old_obj->request->ch) { + php_http_curl_init(php_http_curl_copy(old_obj->request->ch), new_obj->request TSRMLS_CC); + } + + zend_objects_clone_members(&new_obj->zo, new_ov, &old_obj->zo, Z_OBJ_HANDLE_P(this_ptr) TSRMLS_CC); + /* FIXME */ + + return new_ov; +} + +void php_http_request_object_free(void *object TSRMLS_DC) +{ + php_http_request_object_t *o = (php_http_request_object_t *) object; + + php_http_request_free(&o->request); + zend_object_std_dtor((zend_object *) o TSRMLS_CC); + efree(o); +} + +static inline void php_http_request_object_check_request_content_type(zval *this_ptr TSRMLS_DC) +{ + zval *ctype = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("contentType"), 0 TSRMLS_CC); + + if (Z_STRLEN_P(ctype)) { + zval **headers, *opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC); + + if ( (Z_TYPE_P(opts) == IS_ARRAY) && + (SUCCESS == zend_hash_find(Z_ARRVAL_P(opts), ZEND_STRS("headers"), (void *) &headers)) && + (Z_TYPE_PP(headers) == IS_ARRAY)) { + zval **ct_header; + + /* only override if not already set */ + if ((SUCCESS != zend_hash_find(Z_ARRVAL_PP(headers), ZEND_STRS("Content-Type"), (void *) &ct_header))) { + add_assoc_stringl(*headers, "Content-Type", Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1); + } else + /* or not a string, zero length string or a string of spaces */ + if ((Z_TYPE_PP(ct_header) != IS_STRING) || !Z_STRLEN_PP(ct_header)) { + add_assoc_stringl(*headers, "Content-Type", Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1); + } else { + int i, only_space = 1; + + /* check for spaces only */ + for (i = 0; i < Z_STRLEN_PP(ct_header); ++i) { + if (!PHP_HTTP_IS_CTYPE(space, Z_STRVAL_PP(ct_header)[i])) { + only_space = 0; + break; + } + } + if (only_space) { + add_assoc_stringl(*headers, "Content-Type", Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1); + } + } + } else { + zval *headers; + + MAKE_STD_ZVAL(headers); + array_init(headers); + add_assoc_stringl(headers, "Content-Type", Z_STRVAL_P(ctype), Z_STRLEN_P(ctype), 1); + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "addheaders", NULL, headers); + zval_ptr_dtor(&headers); + } + } +} + +static inline zend_object_value php_http_request_object_message(zval *this_ptr, php_http_message_t *msg TSRMLS_DC) +{ + zend_object_value ov; + zval *zcn = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("messageClass"), 0 TSRMLS_CC); + zend_class_entry *class_entry; + + if (Z_STRLEN_P(zcn) + && (class_entry = zend_fetch_class(Z_STRVAL_P(zcn), Z_STRLEN_P(zcn), 0 TSRMLS_CC)) + && (SUCCESS == php_http_new(&ov, class_entry, (php_http_new_t) php_http_message_object_new_ex, php_http_message_class_entry, msg, NULL TSRMLS_CC))) { + return ov; + } else { + return php_http_message_object_new_ex(php_http_message_class_entry, msg, NULL TSRMLS_CC); + } +} + +STATUS php_http_request_object_requesthandler(php_http_request_object_t *obj, zval *this_ptr TSRMLS_DC) +{ + STATUS status = SUCCESS; + zval *zurl, *zmeth, *zbody, *zqdata, *zoptions; + php_http_message_body_t *body = NULL; + php_url *tmp, qdu = {0}; + + php_http_request_reset(obj->request); + PHP_HTTP_CHECK_CURL_INIT(obj->request->ch, php_http_curl_init(NULL, obj->request TSRMLS_CC), return FAILURE); + php_http_request_object_check_request_content_type(getThis() TSRMLS_CC); + + zmeth = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("method"), 0 TSRMLS_CC); + obj->request->meth = Z_LVAL_P(zmeth); + + zurl = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("url"), 0 TSRMLS_CC); + zqdata = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), 0 TSRMLS_CC); + if (Z_STRLEN_P(zqdata)) { + qdu.query = Z_STRVAL_P(zqdata); + } + php_http_url(0, tmp = php_url_parse(Z_STRVAL_P(zurl)), &qdu, NULL, &obj->request->url, NULL TSRMLS_CC); + php_url_free(tmp); + + zbody = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody"), 0 TSRMLS_CC); + if (Z_TYPE_P(zbody) == IS_OBJECT) { + body = ((php_http_message_body_object_t *)zend_object_store_get_object(zbody TSRMLS_CC))->body; + } + obj->request->body = body; + + zoptions = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC); + php_http_request_prepare(obj->request, Z_ARRVAL_P(zoptions)); + + /* check if there's a onProgress method and add it as progress callback if one isn't already set */ + if (zend_hash_exists(&Z_OBJCE_P(getThis())->function_table, ZEND_STRS("onprogress"))) { + zval **entry, *pcb; + + if ((Z_TYPE_P(zoptions) != IS_ARRAY) + || (SUCCESS != zend_hash_find(Z_ARRVAL_P(zoptions), ZEND_STRS("onprogress"), (void *) &entry) + || (!zend_is_callable(*entry, 0, NULL TSRMLS_CC)))) { + MAKE_STD_ZVAL(pcb); + array_init(pcb); + Z_ADDREF_P(getThis()); + add_next_index_zval(pcb, getThis()); + add_next_index_stringl(pcb, "onprogress", lenof("onprogress"), 1); + php_http_request_set_progress_callback(obj->request, pcb); + zval_ptr_dtor(&pcb); + } + } + + return status; +} + +STATUS php_http_request_object_responsehandler(php_http_request_object_t *obj, zval *this_ptr TSRMLS_DC) +{ + STATUS ret = SUCCESS; + zval *info; + php_http_message_t *msg; + + /* always fetch info */ + MAKE_STD_ZVAL(info); + array_init(info); + php_http_request_info(obj->request, Z_ARRVAL_P(info)); + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseInfo"), info TSRMLS_CC); + zval_ptr_dtor(&info); + + /* update history * / + if (i_zend_is_true(zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("recordHistory"), 0 TSRMLS_CC))) { + zval *new_hist, *old_hist = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("history"), 0 TSRMLS_CC); + zend_object_value ov = php_http_request_object_message(getThis(), obj->request->message_parser.message TSRMLS_CC); + + MAKE_STD_ZVAL(new_hist); + ZVAL_OBJVAL(new_hist, ov, 0); + + if (Z_TYPE_P(old_hist) == IS_OBJECT) { + php_http_message_object_prepend(new_hist, old_hist, 0 TSRMLS_CC); + } + + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("history"), new_hist TSRMLS_CC); + zval_ptr_dtor(&new_hist); + } +*/ +// if ((msg = obj->request->_current.request)) { +// /* update request message */ +// zval *message; +// +// MAKE_STD_ZVAL(message); +// ZVAL_OBJVAL(message, php_http_request_object_message(getThis(), msg TSRMLS_CC), 1); +// zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestMessage"), message TSRMLS_CC); +// } +// fprintf(stderr, "RESPONSE MESSAGE: %p\n", obj->request->parser.msg); + if ((msg = obj->request->parser.msg)) { + /* update properties with response info */ + zval *message; + + zend_update_property_long(php_http_request_class_entry, getThis(), ZEND_STRL("responseCode"), msg->http.info.response.code TSRMLS_CC); + zend_update_property_string(php_http_request_class_entry, getThis(), ZEND_STRL("responseStatus"), STR_PTR(msg->http.info.response.status) TSRMLS_CC); + + MAKE_STD_ZVAL(message); + ZVAL_OBJVAL(message, php_http_request_object_message(getThis(), msg TSRMLS_CC), 0); + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), message TSRMLS_CC); + zval_ptr_dtor(&message); + obj->request->parser.msg = php_http_message_init(NULL, 0 TSRMLS_CC); + } else { + /* update properties with empty values */ + zval *znull; + + MAKE_STD_ZVAL(znull); + ZVAL_NULL(znull); + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), znull TSRMLS_CC); + zval_ptr_dtor(&znull); + + zend_update_property_long(php_http_request_class_entry, getThis(), ZEND_STRL("responseCode"), 0 TSRMLS_CC); + zend_update_property_string(php_http_request_class_entry, getThis(), ZEND_STRL("responseStatus"), "" TSRMLS_CC); + } + + php_http_request_set_progress_callback(obj->request, NULL); + + if (!EG(exception) && zend_hash_exists(&Z_OBJCE_P(getThis())->function_table, ZEND_STRS("onfinish"))) { + zval *param; + + MAKE_STD_ZVAL(param); + ZVAL_BOOL(param, ret == SUCCESS); + with_error_handling(EH_NORMAL, NULL) { + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "onfinish", NULL, param); + } end_error_handling(); + zval_ptr_dtor(¶m); + } + + return ret; +} + +static int apply_pretty_key(void *pDest, int num_args, va_list args, zend_hash_key *hash_key) +{ + if (hash_key->arKey && hash_key->nKeyLength > 1) { + hash_key->h = zend_hash_func(php_http_pretty_key(hash_key->arKey, hash_key->nKeyLength - 1, 1, 0), hash_key->nKeyLength); + } + return ZEND_HASH_APPLY_KEEP; +} + +static inline void php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAMETERS, char *key, size_t len, int overwrite, int prettify_keys) +{ + zval *old_opts, *new_opts, *opts = NULL, **entry = NULL; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a/!", &opts)) { + RETURN_FALSE; + } + + MAKE_STD_ZVAL(new_opts); + array_init(new_opts); + old_opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC); + if (Z_TYPE_P(old_opts) == IS_ARRAY) { + array_copy(Z_ARRVAL_P(old_opts), Z_ARRVAL_P(new_opts)); + } + + if (SUCCESS == zend_hash_find(Z_ARRVAL_P(new_opts), key, len, (void *) &entry)) { + if (overwrite) { + zend_hash_clean(Z_ARRVAL_PP(entry)); + } + if (opts && zend_hash_num_elements(Z_ARRVAL_P(opts))) { + if (overwrite) { + array_copy(Z_ARRVAL_P(opts), Z_ARRVAL_PP(entry)); + } else { + array_join(Z_ARRVAL_P(opts), Z_ARRVAL_PP(entry), 0, prettify_keys ? ARRAY_JOIN_PRETTIFY : 0); + } + } + } else if (opts) { + if (prettify_keys) { + zend_hash_apply_with_arguments(Z_ARRVAL_P(opts) TSRMLS_CC, apply_pretty_key, 0, NULL); + } + Z_ADDREF_P(opts); + add_assoc_zval_ex(new_opts, key, len, opts); + } + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), new_opts TSRMLS_CC); + zval_ptr_dtor(&new_opts); + + RETURN_TRUE; +} + +static inline void php_http_request_object_get_options_subr(INTERNAL_FUNCTION_PARAMETERS, char *key, size_t len) +{ + if (SUCCESS == zend_parse_parameters_none()) { + zval *opts, **options; + + opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC); + array_init(return_value); + + if ( (Z_TYPE_P(opts) == IS_ARRAY) && + (SUCCESS == zend_hash_find(Z_ARRVAL_P(opts), key, len, (void *) &options))) { + convert_to_array(*options); + array_copy(Z_ARRVAL_PP(options), Z_ARRVAL_P(return_value)); + } + } +} + + +PHP_METHOD(HttpRequest, __construct) +{ + char *url_str = NULL; + int url_len; + long meth = -1; + zval *options = NULL; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|sla!", &url_str, &url_len, &meth, &options)) { + if (url_str) { + zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("url"), url_str, url_len TSRMLS_CC); + } + if (meth > -1) { + zend_update_property_long(php_http_request_class_entry, getThis(), ZEND_STRL("method"), meth TSRMLS_CC); + } + if (options) { + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "setoptions", NULL, options); + } + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequest, setOptions) +{ + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + HashPosition pos; + zval *opts = NULL, *old_opts, *new_opts, *add_opts, **opt; + + if (SUCCESS != zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|a!/", &opts)) { + RETURN_FALSE; + } + + MAKE_STD_ZVAL(new_opts); + array_init(new_opts); + + if (!opts || !zend_hash_num_elements(Z_ARRVAL_P(opts))) { + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), new_opts TSRMLS_CC); + zval_ptr_dtor(&new_opts); + RETURN_TRUE; + } + + MAKE_STD_ZVAL(add_opts); + array_init(add_opts); + /* some options need extra attention -- thus cannot use array_merge() directly */ + FOREACH_KEYVAL(pos, opts, key, opt) { + if (key.type == HASH_KEY_IS_STRING) { +#define KEYMATCH(k, s) ((sizeof(s)==k.len) && !strcasecmp(k.str, s)) + if (KEYMATCH(key, "headers")) { + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "addheaders", NULL, *opt); + } else if (KEYMATCH(key, "cookies")) { + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "addcookies", NULL, *opt); + } else if (KEYMATCH(key, "ssl")) { + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "addssloptions", NULL, *opt); + } else if (KEYMATCH(key, "url") || KEYMATCH(key, "uri")) { + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "seturl", NULL, *opt); + } else if (KEYMATCH(key, "method")) { + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "setmethod", NULL, *opt); + } else if (KEYMATCH(key, "flushcookies")) { + if (i_zend_is_true(*opt)) { + php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + php_http_request_flush_cookies(obj->request); + } + } else if (KEYMATCH(key, "resetcookies")) { + php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + php_http_request_reset_cookies(obj->request, (zend_bool) i_zend_is_true(*opt)); + } else if (KEYMATCH(key, "enablecookies")) { + php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + php_http_request_enable_cookies(obj->request); + } else if (KEYMATCH(key, "recordHistory")) { + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("recordHistory"), *opt TSRMLS_CC); + } else if (KEYMATCH(key, "messageClass")) { + zend_call_method_with_1_params(&getThis(), Z_OBJCE_P(getThis()), NULL, "setmessageclass", NULL, *opt); + } else if (Z_TYPE_PP(opt) == IS_NULL) { + old_opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC); + if (Z_TYPE_P(old_opts) == IS_ARRAY) { + zend_hash_del(Z_ARRVAL_P(old_opts), key.str, key.len); + } + } else { + Z_ADDREF_P(*opt); + add_assoc_zval_ex(add_opts, key.str, key.len, *opt); + } + } + } + + old_opts = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), 0 TSRMLS_CC); + if (Z_TYPE_P(old_opts) == IS_ARRAY) { + array_copy(Z_ARRVAL_P(old_opts), Z_ARRVAL_P(new_opts)); + } + array_join(Z_ARRVAL_P(add_opts), Z_ARRVAL_P(new_opts), 0, 0); + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("options"), new_opts TSRMLS_CC); + zval_ptr_dtor(&new_opts); + zval_ptr_dtor(&add_opts); + + RETURN_TRUE; +} + + + +PHP_METHOD(HttpRequest, getOptions) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "options"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, setSslOptions) +{ + php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("ssl"), 1, 0); +} + +PHP_METHOD(HttpRequest, addSslOptions) +{ + php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("ssl"), 0, 0); +} + +PHP_METHOD(HttpRequest, getSslOptions) +{ + php_http_request_object_get_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("ssl")); +} + +PHP_METHOD(HttpRequest, addHeaders) +{ + php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("headers"), 0, 1); +} + +PHP_METHOD(HttpRequest, setHeaders) +{ + php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("headers"), 1, 1); +} + +PHP_METHOD(HttpRequest, getHeaders) +{ + php_http_request_object_get_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("headers")); +} + +PHP_METHOD(HttpRequest, setCookies) +{ + php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("cookies"), 1, 0); +} + +PHP_METHOD(HttpRequest, addCookies) +{ + php_http_request_object_set_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("cookies"), 0, 0); +} + +PHP_METHOD(HttpRequest, getCookies) +{ + php_http_request_object_get_options_subr(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_STRS("cookies")); +} + +PHP_METHOD(HttpRequest, enableCookies) +{ + if (SUCCESS == zend_parse_parameters_none()){ + php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_SUCCESS(php_http_request_enable_cookies(obj->request)); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, resetCookies) +{ + zend_bool session_only = 0; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &session_only)) { + php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + RETURN_SUCCESS(php_http_request_reset_cookies(obj->request, session_only)); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, flushCookies) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_SUCCESS(php_http_request_flush_cookies(obj->request)); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, setUrl) +{ + char *url_str = NULL; + int url_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &url_len, &url_len)) { + zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("url"), url_str, url_len TSRMLS_CC); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getUrl) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "url"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, setMethod) +{ + long meth; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &meth)) { + zend_update_property_long(php_http_request_class_entry, getThis(), ZEND_STRL("method"), meth TSRMLS_CC); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getMethod) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "method"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, setContentType) +{ + char *ctype; + int ct_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &ctype, &ct_len)) { + if (ct_len) { + PHP_HTTP_CHECK_CONTENT_TYPE(ctype, RETURN_FALSE); + } + zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("contentType"), ctype, ct_len TSRMLS_CC); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getContentType) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "contentType"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, setQueryData) +{ + zval *qdata = NULL; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z!", &qdata)) { + if ((!qdata) || Z_TYPE_P(qdata) == IS_NULL) { + zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), "", 0 TSRMLS_CC); + } else if ((Z_TYPE_P(qdata) == IS_ARRAY) || (Z_TYPE_P(qdata) == IS_OBJECT)) { + char *query_data_str = NULL; + size_t query_data_len; + + if (SUCCESS != php_http_url_encode_hash(HASH_OF(qdata), 0, NULL, 0, &query_data_str, &query_data_len TSRMLS_CC)) { + RETURN_FALSE; + } + + zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), query_data_str, query_data_len TSRMLS_CC); + efree(query_data_str); + } else { + zval *data = php_http_zsep(IS_STRING, qdata); + + zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), Z_STRVAL_P(data), Z_STRLEN_P(data) TSRMLS_CC); + zval_ptr_dtor(&data); + } + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getQueryData) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "queryData"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, addQueryData) +{ + zval *qdata; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a/", &qdata)) { + char *query_data_str = NULL; + size_t query_data_len = 0; + zval *old_qdata = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), 0 TSRMLS_CC); + + if (SUCCESS != php_http_url_encode_hash(HASH_OF(qdata), 1, Z_STRVAL_P(old_qdata), Z_STRLEN_P(old_qdata), &query_data_str, &query_data_len TSRMLS_CC)) { + RETURN_FALSE; + } + + zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("queryData"), query_data_str, query_data_len TSRMLS_CC); + efree(query_data_str); + + RETURN_TRUE; + } + RETURN_FALSE; + +} + +PHP_METHOD(HttpRequest, setBody) +{ + zval *body = NULL; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|O!", &body, php_http_message_body_class_entry)) { + if (body && Z_TYPE_P(body) != IS_NULL) { + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody"), body TSRMLS_CC); + } else { + zend_update_property_null(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody") TSRMLS_CC); + } + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, addBody) +{ + zval *new_body; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &new_body, php_http_message_body_class_entry)) { + zval *old_body = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody"), 0 TSRMLS_CC); + + if (Z_TYPE_P(old_body) == IS_OBJECT) { + php_http_message_body_object_t *old_obj = zend_object_store_get_object(old_body TSRMLS_CC); + php_http_message_body_object_t *new_obj = zend_object_store_get_object(new_body TSRMLS_CC); + + php_http_message_body_to_callback(old_obj->body, (php_http_pass_callback_t) php_http_message_body_append, new_obj->body, 0, 0); + } else { + zend_update_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestBody"), new_body TSRMLS_CC); + } + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getBody) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "requestBody"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getResponseHeader) +{ + zval *header; + char *header_name = NULL; + int header_len = 0; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &header_name, &header_len)) { + zval *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), 0 TSRMLS_CC); + + if (Z_TYPE_P(message) == IS_OBJECT) { + php_http_message_object_t *msg = zend_object_store_get_object(message TSRMLS_CC); + + if (header_len) { + if ((header = php_http_message_header(msg->message, header_name, header_len + 1, 0))) { + RETURN_ZVAL(header, 1, 1); + } + } else { + array_init(return_value); + zend_hash_copy(Z_ARRVAL_P(return_value), &msg->message->hdrs, (copy_ctor_func_t) zval_add_ref, NULL, sizeof(zval *)); + return; + } + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getResponseCookies) +{ + long flags = 0; + zval *allowed_extras_array = NULL; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|la!", &flags, &allowed_extras_array)) { + int i = 0; + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + char **allowed_extras = NULL; + zval **header = NULL, **entry = NULL, *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), 0 TSRMLS_CC); + HashPosition pos, pos1, pos2; + + if (Z_TYPE_P(message) == IS_OBJECT) { + php_http_message_object_t *msg = zend_object_store_get_object(message TSRMLS_CC); + + array_init(return_value); + + if (allowed_extras_array) { + allowed_extras = ecalloc(zend_hash_num_elements(Z_ARRVAL_P(allowed_extras_array)) + 1, sizeof(char *)); + FOREACH_VAL(pos, allowed_extras_array, entry) { + zval *data = php_http_zsep(IS_STRING, *entry); + allowed_extras[i++] = estrndup(Z_STRVAL_P(data), Z_STRLEN_P(data)); + zval_ptr_dtor(&data); + } + } + + FOREACH_HASH_KEYVAL(pos1, &msg->message->hdrs, key, header) { + if (key.type == HASH_KEY_IS_STRING && !strcasecmp(key.str, "Set-Cookie")) { + php_http_cookie_list_t *list; + + if (Z_TYPE_PP(header) == IS_ARRAY) { + zval **single_header; + + FOREACH_VAL(pos2, *header, single_header) { + zval *data = php_http_zsep(IS_STRING, *single_header); + + if ((list = php_http_cookie_list_parse(NULL, Z_STRVAL_P(data), flags, allowed_extras TSRMLS_CC))) { + zval *cookie; + + MAKE_STD_ZVAL(cookie); + ZVAL_OBJVAL(cookie, php_http_cookie_object_new_ex(php_http_cookie_class_entry, list, NULL TSRMLS_CC), 0); + add_next_index_zval(return_value, cookie); + } + zval_ptr_dtor(&data); + } + } else { + zval *data = php_http_zsep(IS_STRING, *header); + if ((list = php_http_cookie_list_parse(NULL, Z_STRVAL_P(data), flags, allowed_extras TSRMLS_CC))) { + zval *cookie; + + MAKE_STD_ZVAL(cookie); + ZVAL_OBJVAL(cookie, php_http_cookie_object_new_ex(php_http_cookie_class_entry, list, NULL TSRMLS_CC), 0); + add_next_index_zval(return_value, cookie); + } + zval_ptr_dtor(&data); + } + } + } + + if (allowed_extras) { + for (i = 0; allowed_extras[i]; ++i) { + efree(allowed_extras[i]); + } + efree(allowed_extras); + } + + return; + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getResponseBody) +{ + if (SUCCESS == zend_parse_parameters_none()) { + zval *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), 0 TSRMLS_CC); + zval *body = zend_read_property(php_http_message_class_entry, message, ZEND_STRL("body"), 0 TSRMLS_CC); + + RETURN_ZVAL(body, 1, 0); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getResponseCode) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "responseCode"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getResponseStatus) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "responseStatus"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getResponseInfo) +{ + char *info_name = NULL; + int info_len = 0; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|s", &info_name, &info_len)) { + zval **infop, *info = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseInfo"), 0 TSRMLS_CC); + + if (Z_TYPE_P(info) != IS_ARRAY) { + RETURN_FALSE; + } + + if (info_len && info_name) { + if (SUCCESS == zend_hash_find(Z_ARRVAL_P(info), php_http_pretty_key(info_name, info_len, 0, 0), info_len + 1, (void *) &infop)) { + RETURN_ZVAL(*infop, 1, 0); + } else { + php_http_error(HE_NOTICE, PHP_HTTP_E_INVALID_PARAM, "Could not find response info named %s", info_name); + RETURN_FALSE; + } + } else { + RETURN_ZVAL(info, 1, 0); + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getResponseMessage) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters_none()) { + zval *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("responseMessage"), 0 TSRMLS_CC); + + if (Z_TYPE_P(message) == IS_OBJECT) { + RETVAL_OBJECT(message, 1); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "HttpRequest does not contain a response message"); + } + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequest, getRequestMessage) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters_none()) { + zval *message = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("requestMessage"), 0 TSRMLS_CC); + + if (Z_TYPE_P(message) == IS_OBJECT) { + RETVAL_OBJECT(message, 1); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "HttpRequest does not contain a request message"); + } + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequest, getHistory) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters_none()) { + zval *hist = zend_read_property(php_http_request_class_entry, getThis(), ZEND_STRL("history"), 0 TSRMLS_CC); + + if (Z_TYPE_P(hist) == IS_OBJECT) { + RETVAL_OBJECT(hist, 1); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "The history is empty"); + } + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequest, clearHistory) +{ + if (SUCCESS == zend_parse_parameters_none()) { + zend_update_property_null(php_http_request_class_entry, getThis(), ZEND_STRL("history") TSRMLS_CC); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, getMessageClass) +{ + if (SUCCESS == zend_parse_parameters_none()) { + RETURN_PROP(php_http_request_class_entry, "messageClass"); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequest, setMessageClass) +{ + char *cn; + int cl; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &cn, &cl)) { + zend_update_property_stringl(php_http_request_class_entry, getThis(), ZEND_STRL("messageClass"), cn, cl TSRMLS_CC); + } +} + +PHP_METHOD(HttpRequest, send) +{ + RETVAL_FALSE; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (obj->pool) { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "Cannot perform HttpRequest::send() while attached to an HttpRequestPool"); + } else if (SUCCESS == php_http_request_object_requesthandler(obj, getThis() TSRMLS_CC)) { + php_http_request_exec(obj->request); + if (SUCCESS == php_http_request_object_responsehandler(obj, getThis() TSRMLS_CC)) { + RETVAL_PROP(php_http_request_class_entry, "responseMessage"); + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Failed to handle response"); + } + } else { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Failed to handle request"); + } + } + } end_error_handling(); +} + +PHP_MINIT_FUNCTION(http_request) +{ +#ifdef PHP_HTTP_NEED_OPENSSL_TSL + /* mod_ssl, libpq or ext/curl might already have set thread lock callbacks */ + if (!CRYPTO_get_id_callback()) { + int i, c = CRYPTO_num_locks(); + + php_http_openssl_tsl = malloc(c * sizeof(MUTEX_T)); + + for (i = 0; i < c; ++i) { + php_http_openssl_tsl[i] = tsrm_mutex_alloc(); + } + + CRYPTO_set_id_callback(php_http_openssl_thread_id); + CRYPTO_set_locking_callback(php_http_openssl_thread_lock); + } +#endif +#ifdef PHP_HTTP_NEED_GNUTLS_TSL + gcry_control(GCRYCTL_SET_THREAD_CBS, &php_http_gnutls_tsl); +#endif + + if (CURLE_OK != curl_global_init(CURL_GLOBAL_ALL)) { + return FAILURE; + } + + if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request"), safe_curl_init, safe_curl_dtor, safe_curl_copy)) { + return FAILURE; + } + + PHP_HTTP_REGISTER_CLASS(http, Request, http_request, php_http_object_class_entry, 0); + php_http_request_class_entry->create_object = php_http_request_object_new; + memcpy(&php_http_request_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_http_request_object_handlers.clone_obj = php_http_request_object_clone; + + zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("options"), ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("responseInfo"), ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("responseMessage"), ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_long(php_http_request_class_entry, ZEND_STRL("responseCode"), 0, ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("responseStatus"), "", ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("requestMessage"), ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_long(php_http_request_class_entry, ZEND_STRL("method"), PHP_HTTP_GET, ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("url"), "", ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("contentType"), "", ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("requestBody"), "", ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("queryData"), "", ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_null(php_http_request_class_entry, ZEND_STRL("history"), ZEND_ACC_PRIVATE TSRMLS_CC); + zend_declare_property_bool(php_http_request_class_entry, ZEND_STRL("recordHistory"), 0, ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_string(php_http_request_class_entry, ZEND_STRL("messageClass"), "", ZEND_ACC_PRIVATE TSRMLS_CC); + + /* + * HTTP Protocol Version Constants + */ + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("VERSION_1_0"), CURL_HTTP_VERSION_1_0 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("VERSION_1_1"), CURL_HTTP_VERSION_1_1 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("VERSION_NONE"), CURL_HTTP_VERSION_NONE TSRMLS_CC); /* to be removed */ + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("VERSION_ANY"), CURL_HTTP_VERSION_NONE TSRMLS_CC); + + /* + * SSL Version Constants + */ + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("SSL_VERSION_TLSv1"), CURL_SSLVERSION_TLSv1 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("SSL_VERSION_SSLv2"), CURL_SSLVERSION_SSLv2 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("SSL_VERSION_SSLv3"), CURL_SSLVERSION_SSLv3 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("SSL_VERSION_ANY"), CURL_SSLVERSION_DEFAULT TSRMLS_CC); + + /* + * DNS IPvX resolving + */ + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("IPRESOLVE_V4"), CURL_IPRESOLVE_V4 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("IPRESOLVE_V6"), CURL_IPRESOLVE_V6 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("IPRESOLVE_ANY"), CURL_IPRESOLVE_WHATEVER TSRMLS_CC); + + /* + * Auth Constants + */ + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_BASIC"), CURLAUTH_BASIC TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_DIGEST"), CURLAUTH_DIGEST TSRMLS_CC); +#if PHP_HTTP_CURL_VERSION(7,19,3) + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_DIGEST_IE"), CURLAUTH_DIGEST_IE TSRMLS_CC); +#endif + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_NTLM"), CURLAUTH_NTLM TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_GSSNEG"), CURLAUTH_GSSNEGOTIATE TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("AUTH_ANY"), CURLAUTH_ANY TSRMLS_CC); + + /* + * Proxy Type Constants + */ + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_SOCKS4"), CURLPROXY_SOCKS4 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_SOCKS4A"), CURLPROXY_SOCKS5 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_SOCKS5_HOSTNAME"), CURLPROXY_SOCKS5 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_SOCKS5"), CURLPROXY_SOCKS5 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_HTTP"), CURLPROXY_HTTP TSRMLS_CC); +# if PHP_HTTP_CURL_VERSION(7,19,4) + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("PROXY_HTTP_1_0"), CURLPROXY_HTTP_1_0 TSRMLS_CC); +# endif + + /* + * Post Redirection Constants + */ +#if PHP_HTTP_CURL_VERSION(7,19,1) + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("POSTREDIR_301"), CURL_REDIR_POST_301 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("POSTREDIR_302"), CURL_REDIR_POST_302 TSRMLS_CC); + zend_declare_class_constant_long(php_http_request_class_entry, ZEND_STRL("POSTREDIR_ALL"), CURL_REDIR_POST_ALL TSRMLS_CC); +#endif + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(http_request) +{ + curl_global_cleanup(); +#ifdef PHP_HTTP_NEED_OPENSSL_TSL + if (php_http_openssl_tsl) { + int i, c = CRYPTO_num_locks(); + + CRYPTO_set_id_callback(NULL); + CRYPTO_set_locking_callback(NULL); + + for (i = 0; i < c; ++i) { + tsrm_mutex_free(php_http_openssl_tsl[i]); + } + + free(php_http_openssl_tsl); + php_http_openssl_tsl = NULL; + } +#endif + return SUCCESS; +} + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_request.h b/php_http_request.h new file mode 100644 index 0000000..9e314ca --- /dev/null +++ b/php_http_request.h @@ -0,0 +1,189 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_request_api.h 292841 2009-12-31 08:48:57Z mike $ */ + +#ifndef PHP_HTTP_REQUEST_H +#define PHP_HTTP_REQUEST_H + +#include "php_http_request_method.h" +#include "php_http_request_pool.h" + +extern PHP_MINIT_FUNCTION(http_request); +extern PHP_MSHUTDOWN_FUNCTION(http_request); + +typedef struct php_http_request_progress_state_counter { + double now; + double total; +} php_http_request_progress_state_counter_t; + +typedef struct php_http_request_progress_state { + php_http_request_progress_state_counter_t ul; + php_http_request_progress_state_counter_t dl; +} php_http_request_progress_state_t; + +typedef struct php_http_request { + CURL *ch; + char *url; + php_http_request_method_t meth; + php_http_message_body_t *body; + struct { + php_http_message_parser_t *ctx; + php_http_message_t *msg; + php_http_buffer *buf; + } parser; + + struct { + php_http_buffer cookies; + HashTable options; + struct curl_slist *headers; + } _cache; + + struct { + uint count; + double delay; + } _retry; + + struct { + struct { + struct { + double now; + double total; + } ul; + struct { + double now; + double total; + } dl; + } state; + zval *callback; + unsigned in_cb:1; + } _progress; + +#ifdef ZTS + void ***ts; +#endif + +} php_http_request_t; + +/* CURLOPT_PRIVATE storage living as long as a CURL handle */ +typedef struct php_http_request_storage { + char *url; + char *cookiestore; + char errorbuffer[CURL_ERROR_SIZE]; +} php_http_request_storage_t; + + +static inline php_http_request_storage_t *php_http_request_storage_get(CURL *ch) +{ + php_http_request_storage_t *st = NULL; + curl_easy_getinfo(ch, CURLINFO_PRIVATE, &st); + return st; +} + +PHP_HTTP_API CURL *php_http_curl_init(CURL *ch, php_http_request_t *request TSRMLS_DC); +PHP_HTTP_API void php_http_curl_free(CURL **ch TSRMLS_DC); +PHP_HTTP_API CURL *php_http_curl_copy(CURL *ch TSRMLS_DC); + +#define PHP_HTTP_CHECK_CURL_INIT(ch, init, action) \ + if ((!(ch)) && (!((ch) = init))) { \ + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not initialize curl"); \ + action; \ + } + + +PHP_HTTP_API php_http_request_t *php_http_request_init(php_http_request_t *request, CURL *ch, php_http_request_method_t meth, const char *url TSRMLS_DC); +PHP_HTTP_API void php_http_request_dtor(php_http_request_t *request); +PHP_HTTP_API void php_http_request_free(php_http_request_t **request); +PHP_HTTP_API void php_http_request_reset(php_http_request_t *r); +PHP_HTTP_API STATUS php_http_request_enable_cookies(php_http_request_t *request); +PHP_HTTP_API STATUS php_http_request_reset_cookies(php_http_request_t *request, int session_only); +PHP_HTTP_API STATUS php_http_request_flush_cookies(php_http_request_t *request); +PHP_HTTP_API void php_http_request_defaults(php_http_request_t *request); +PHP_HTTP_API STATUS php_http_request_prepare(php_http_request_t *request, HashTable *options); +PHP_HTTP_API void php_http_request_exec(php_http_request_t *request); +PHP_HTTP_API void php_http_request_info(php_http_request_t *request, HashTable *info); +PHP_HTTP_API void php_http_request_set_progress_callback(php_http_request_t *request, zval *cb); + + +typedef struct php_http_request_object { + zend_object zo; + php_http_request_t *request; + php_http_request_pool_t *pool; + php_http_request_datashare_t *share; +} php_http_request_object_t; + +extern zend_class_entry *php_http_request_class_entry; +extern zend_function_entry php_http_request_method_entry[]; + +extern zend_object_value php_http_request_object_new(zend_class_entry *ce TSRMLS_DC); +extern zend_object_value php_http_request_object_new_ex(zend_class_entry *ce, CURL *ch, php_http_request_object_t **ptr TSRMLS_DC); +extern zend_object_value php_http_request_object_clone(zval *zobject TSRMLS_DC); +extern void php_http_request_object_free(void *object TSRMLS_DC); + +extern STATUS php_http_request_object_requesthandler(php_http_request_object_t *obj, zval *this_ptr TSRMLS_DC); +extern STATUS php_http_request_object_responsehandler(php_http_request_object_t *obj, zval *this_ptr TSRMLS_DC); + +PHP_METHOD(HttpRequest, __construct); +PHP_METHOD(HttpRequest, setOptions); +PHP_METHOD(HttpRequest, getOptions); +PHP_METHOD(HttpRequest, addSslOptions); +PHP_METHOD(HttpRequest, setSslOptions); +PHP_METHOD(HttpRequest, getSslOptions); +PHP_METHOD(HttpRequest, addHeaders); +PHP_METHOD(HttpRequest, getHeaders); +PHP_METHOD(HttpRequest, setHeaders); +PHP_METHOD(HttpRequest, addCookies); +PHP_METHOD(HttpRequest, getCookies); +PHP_METHOD(HttpRequest, setCookies); +PHP_METHOD(HttpRequest, enableCookies); +PHP_METHOD(HttpRequest, resetCookies); +PHP_METHOD(HttpRequest, flushCookies); +PHP_METHOD(HttpRequest, setMethod); +PHP_METHOD(HttpRequest, getMethod); +PHP_METHOD(HttpRequest, setUrl); +PHP_METHOD(HttpRequest, getUrl); +PHP_METHOD(HttpRequest, setContentType); +PHP_METHOD(HttpRequest, getContentType); +PHP_METHOD(HttpRequest, setQueryData); +PHP_METHOD(HttpRequest, getQueryData); +PHP_METHOD(HttpRequest, addQueryData); +PHP_METHOD(HttpRequest, getBody); +PHP_METHOD(HttpRequest, setBody); +PHP_METHOD(HttpRequest, addBody); +PHP_METHOD(HttpRequest, send); +PHP_METHOD(HttpRequest, getResponseData); +PHP_METHOD(HttpRequest, getResponseHeader); +PHP_METHOD(HttpRequest, getResponseCookies); +PHP_METHOD(HttpRequest, getResponseCode); +PHP_METHOD(HttpRequest, getResponseStatus); +PHP_METHOD(HttpRequest, getResponseBody); +PHP_METHOD(HttpRequest, getResponseInfo); +PHP_METHOD(HttpRequest, getResponseMessage); +PHP_METHOD(HttpRequest, getRawResponseMessage); +PHP_METHOD(HttpRequest, getRequestMessage); +PHP_METHOD(HttpRequest, getRawRequestMessage); +PHP_METHOD(HttpRequest, getHistory); +PHP_METHOD(HttpRequest, clearHistory); +PHP_METHOD(HttpRequest, getMessageClass); +PHP_METHOD(HttpRequest, setMessageClass); + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_request_datashare.c b/php_http_request_datashare.c new file mode 100644 index 0000000..c1df14b --- /dev/null +++ b/php_http_request_datashare.c @@ -0,0 +1,468 @@ + +#include "php_http.h" + +static HashTable php_http_request_datashare_options; +static php_http_request_datashare_t php_http_request_datashare_global; +static int php_http_request_datashare_compare_handles(void *h1, void *h2); +static void php_http_request_datashare_destroy_handles(void *el); + +#ifdef ZTS +static void *php_http_request_datashare_locks_init(void); +static void php_http_request_datashare_locks_dtor(void *l); +static void php_http_request_datashare_lock_func(CURL *handle, curl_lock_data data, curl_lock_access locktype, void *userptr); +static void php_http_request_datashare_unlock_func(CURL *handle, curl_lock_data data, void *userptr); +#endif + +php_http_request_datashare_t *php_http_request_datashare_global_get(void) +{ + return &php_http_request_datashare_global; +} + +PHP_HTTP_API php_http_request_datashare_t *php_http_request_datashare_init(php_http_request_datashare_t *share, zend_bool persistent TSRMLS_DC) +{ + zend_bool free_share; + + if ((free_share = !share)) { + share = pemalloc(sizeof(php_http_request_datashare_t), persistent); + } + memset(share, 0, sizeof(php_http_request_datashare_t)); + + if (SUCCESS != php_http_persistent_handle_acquire(ZEND_STRL("http_request_datashare"), &share->ch TSRMLS_CC)) { + if (free_share) { + pefree(share, persistent); + } + return NULL; + } + + if (!(share->persistent = persistent)) { + share->handle.list = emalloc(sizeof(zend_llist)); + zend_llist_init(share->handle.list, sizeof(zval *), ZVAL_PTR_DTOR, 0); +#ifdef ZTS + } else { + if (SUCCESS == php_http_persistent_handle_acquire(ZEND_STRL("http_request_datashare_lock"), (void *) &share->handle.locks)) { + curl_share_setopt(share->ch, CURLSHOPT_LOCKFUNC, php_http_request_datashare_lock_func); + curl_share_setopt(share->ch, CURLSHOPT_UNLOCKFUNC, php_http_request_datashare_unlock_func); + curl_share_setopt(share->ch, CURLSHOPT_USERDATA, share->handle.locks); + } +#endif + } + + TSRMLS_SET_CTX(share->ts); + + return share; +} + +PHP_HTTP_API STATUS php_http_request_datashare_attach(php_http_request_datashare_t *share, zval *request) +{ + CURLcode rc; + TSRMLS_FETCH_FROM_CTX(share->ts); + php_http_request_object_t *obj = zend_object_store_get_object(request TSRMLS_CC); + + if (obj->share) { + if (obj->share == share) { + return SUCCESS; + } else if (SUCCESS != php_http_request_datashare_detach(obj->share, request)) { + return FAILURE; + } + } + + PHP_HTTP_CHECK_CURL_INIT(obj->request->ch, php_http_curl_init(obj->request->ch, obj->request TSRMLS_CC), return FAILURE); + if (CURLE_OK != (rc = curl_easy_setopt(obj->request->ch, CURLOPT_SHARE, share->ch))) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not attach HttpRequest object(#%d) to the HttpRequestDataShare: %s", Z_OBJ_HANDLE_P(request), curl_easy_strerror(rc)); + return FAILURE; + } + + obj->share = share; + Z_ADDREF_P(request); + zend_llist_add_element(PHP_HTTP_RSHARE_HANDLES(share), (void *) &request); + + return SUCCESS; +} + +PHP_HTTP_API STATUS php_http_request_datashare_detach(php_http_request_datashare_t *share, zval *request) +{ + CURLcode rc; + TSRMLS_FETCH_FROM_CTX(share->ts); + php_http_request_object_t *obj = zend_object_store_get_object(request TSRMLS_CC); + + if (!obj->share) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "HttpRequest object(#%d) is not attached to any HttpRequestDataShare", Z_OBJ_HANDLE_P(request)); + } else if (obj->share != share) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "HttpRequest object(#%d) is not attached to this HttpRequestDataShare", Z_OBJ_HANDLE_P(request)); + } else if (CURLE_OK != (rc = curl_easy_setopt(obj->request->ch, CURLOPT_SHARE, NULL))) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not detach HttpRequest object(#%d) from the HttpRequestDataShare: %s", Z_OBJ_HANDLE_P(request), curl_share_strerror(rc)); + } else { + obj->share = NULL; + zend_llist_del_element(PHP_HTTP_RSHARE_HANDLES(share), request, php_http_request_datashare_compare_handles); + return SUCCESS; + } + return FAILURE; +} + +PHP_HTTP_API void php_http_request_datashare_detach_all(php_http_request_datashare_t *share) +{ + zval **r; + + while ((r = zend_llist_get_first(PHP_HTTP_RSHARE_HANDLES(share)))) { + php_http_request_datashare_detach(share, *r); + } +} + +PHP_HTTP_API void php_http_request_datashare_dtor(php_http_request_datashare_t *share) +{ + TSRMLS_FETCH_FROM_CTX(share->ts); + + if (!share->persistent) { + zend_llist_destroy(share->handle.list); + efree(share->handle.list); + } + php_http_persistent_handle_release(ZEND_STRL("http_request_datashare"), &share->ch TSRMLS_CC); +#ifdef ZTS + if (share->persistent) { + php_http_persistent_handle_release(ZEND_STRL("http_request_datashare_lock"), (void *) &share->handle.locks TSRMLS_CC); + } +#endif +} + +PHP_HTTP_API void php_http_request_datashare_free(php_http_request_datashare_t **share) +{ + php_http_request_datashare_dtor(*share); + pefree(*share, (*share)->persistent); + *share = NULL; +} + +PHP_HTTP_API STATUS php_http_request_datashare_set(php_http_request_datashare_t *share, const char *option, size_t option_len, zend_bool enable) +{ + curl_lock_data *opt; + CURLSHcode rc; + TSRMLS_FETCH_FROM_CTX(share->ts); + + if (SUCCESS == zend_hash_find(&php_http_request_datashare_options, (char *) option, option_len + 1, (void *) &opt)) { + if (CURLSHE_OK == (rc = curl_share_setopt(share->ch, enable ? CURLSHOPT_SHARE : CURLSHOPT_UNSHARE, *opt))) { + return SUCCESS; + } + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not %s sharing of %s data: %s", enable ? "enable" : "disable", option, curl_share_strerror(rc)); + } + return FAILURE; +} + +static int php_http_request_datashare_compare_handles(void *h1, void *h2) +{ + return (Z_OBJ_HANDLE_PP((zval **) h1) == Z_OBJ_HANDLE_P((zval *) h2)); +} + +static void php_http_request_datashare_destroy_handles(void *el) +{ + zval **r = (zval **) el; + TSRMLS_FETCH(); + + { /* gcc 2.95 needs these braces */ + php_http_request_object_t *obj = zend_object_store_get_object(*r TSRMLS_CC); + + curl_easy_setopt(obj->request->ch, CURLOPT_SHARE, NULL); + zval_ptr_dtor(r); + } +} + +#ifdef ZTS +static void *php_http_request_datashare_locks_init(void) +{ + int i; + php_http_request_datashare_lock_t *locks = pecalloc(CURL_LOCK_DATA_LAST, sizeof(php_http_request_datashare_lock_t), 1); + + if (locks) { + for (i = 0; i < CURL_LOCK_DATA_LAST; ++i) { + locks[i].mx = tsrm_mutex_alloc(); + } + } + + return locks; +} + +static void php_http_request_datashare_locks_dtor(void *l) +{ + int i; + php_http_request_datashare_lock_t *locks = (php_http_request_datashare_lock_t *) l; + + for (i = 0; i < CURL_LOCK_DATA_LAST; ++i) { + tsrm_mutex_free(locks[i].mx); + } + pefree(locks, 1); +} + +static void php_http_request_datashare_lock_func(CURL *handle, curl_lock_data data, curl_lock_access locktype, void *userptr) +{ + php_http_request_datashare_lock_t *locks = (php_http_request_datashare_lock_t *) userptr; + + /* TSRM can't distinguish shared/exclusive locks */ + tsrm_mutex_lock(locks[data].mx); + locks[data].ch = handle; +} + +static void php_http_request_datashare_unlock_func(CURL *handle, curl_lock_data data, void *userptr) +{ + php_http_request_datashare_lock_t *locks = (php_http_request_datashare_lock_t *) userptr; + + if (locks[data].ch == handle) { + tsrm_mutex_unlock(locks[data].mx); + } +} +#endif + + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpRequestDataShare, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpRequestDataShare, method, 0) +#define PHP_HTTP_RSHARE_ME(method, visibility) PHP_ME(HttpRequestDataShare, method, PHP_HTTP_ARGS(HttpRequestDataShare, method), visibility) + +PHP_HTTP_EMPTY_ARGS(__destruct); +PHP_HTTP_EMPTY_ARGS(count); + +PHP_HTTP_BEGIN_ARGS(attach, 1) + PHP_HTTP_ARG_OBJ(http\\Request, request, 0) +PHP_HTTP_END_ARGS; +PHP_HTTP_BEGIN_ARGS(detach, 1) + PHP_HTTP_ARG_OBJ(http\\Request, request, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(reset); + +PHP_HTTP_EMPTY_ARGS(getGlobalInstance); + + +static zval *php_http_request_datashare_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC); +static void php_http_request_datashare_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC); + +#define THIS_CE php_http_request_datashare_class_entry +zend_class_entry *php_http_request_datashare_class_entry; +zend_function_entry php_http_request_datashare_method_entry[] = { + PHP_HTTP_RSHARE_ME(__destruct, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) + PHP_HTTP_RSHARE_ME(count, ZEND_ACC_PUBLIC) + PHP_HTTP_RSHARE_ME(attach, ZEND_ACC_PUBLIC) + PHP_HTTP_RSHARE_ME(detach, ZEND_ACC_PUBLIC) + PHP_HTTP_RSHARE_ME(reset, ZEND_ACC_PUBLIC) + PHP_HTTP_RSHARE_ME(getGlobalInstance, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + EMPTY_FUNCTION_ENTRY +}; +static zend_object_handlers php_http_request_datashare_object_handlers; + +zend_object_value php_http_request_datashare_object_new(zend_class_entry *ce TSRMLS_DC) +{ + return php_http_request_datashare_object_new_ex(ce, NULL, NULL TSRMLS_CC); +} + +zend_object_value php_http_request_datashare_object_new_ex(zend_class_entry *ce, php_http_request_datashare_t *share, php_http_request_datashare_object_t **ptr TSRMLS_DC) +{ + zend_object_value ov; + php_http_request_datashare_object_t *o; + + o = ecalloc(1, sizeof(*o)); + zend_object_std_init((zend_object *) o, ce TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + + if (share) { + o->share = share; + } else { + o->share = php_http_request_datashare_init(NULL, 0 TSRMLS_CC); + } + + if (ptr) { + *ptr = o; + } + + ov.handle = zend_objects_store_put(o, NULL, php_http_request_datashare_object_free, NULL TSRMLS_CC); + ov.handlers = &php_http_request_datashare_object_handlers; + + return ov; +} + +void php_http_request_datashare_object_free(void *object TSRMLS_DC) +{ + php_http_request_datashare_object_t *o = (php_http_request_datashare_object_t *) object; + + if (!o->share->persistent) { + php_http_request_datashare_free(&o->share); + } + zend_object_std_dtor((zend_object *) o); + efree(o); +} + +static zval *php_http_request_datashare_object_read_prop(zval *object, zval *member, int type, const zend_literal *literal_key TSRMLS_DC) +{ + if (type == BP_VAR_W && zend_get_property_info(THIS_CE, member, 1 TSRMLS_CC)) { + zend_error(E_ERROR, "Cannot access HttpRequestDataShare default properties by reference or array key/index"); + return NULL; + } + + return zend_get_std_object_handlers()->read_property(object, member, type, literal_key TSRMLS_CC); +} + +static void php_http_request_datashare_object_write_prop(zval *object, zval *member, zval *value, const zend_literal *literal_key TSRMLS_DC) +{ + if (zend_get_property_info(THIS_CE, member, 1 TSRMLS_CC)) { + int status; + php_http_request_datashare_object_t *obj = zend_object_store_get_object(object TSRMLS_CC); + + status = php_http_request_datashare_set(obj->share, Z_STRVAL_P(member), Z_STRLEN_P(member), (zend_bool) i_zend_is_true(value)); + if (SUCCESS != status) { + return; + } + } + + zend_get_std_object_handlers()->write_property(object, member, value, literal_key TSRMLS_CC); +} + +PHP_METHOD(HttpRequestDataShare, __destruct) +{ + php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (SUCCESS == zend_parse_parameters_none()) { + ; /* we always want to clean up */ + } + + php_http_request_datashare_detach_all(obj->share); +} + +PHP_METHOD(HttpRequestDataShare, count) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_LONG(zend_llist_count(PHP_HTTP_RSHARE_HANDLES(obj->share))); + } + RETURN_FALSE; +} + + +PHP_METHOD(HttpRequestDataShare, attach) +{ + zval *request; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) { + php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_SUCCESS(php_http_request_datashare_attach(obj->share, request)); + } + RETURN_FALSE; + +} + +PHP_METHOD(HttpRequestDataShare, detach) +{ + zval *request; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) { + php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_SUCCESS(php_http_request_datashare_detach(obj->share, request)); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestDataShare, reset) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_datashare_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + php_http_request_datashare_detach_all(obj->share); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestDataShare, getGlobalInstance) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters_none()) { + zval *instance = *zend_std_get_static_property(THIS_CE, ZEND_STRL("instance"), 0, NULL TSRMLS_CC); + + if (Z_TYPE_P(instance) != IS_OBJECT) { + MAKE_STD_ZVAL(instance); + ZVAL_OBJVAL(instance, php_http_request_datashare_object_new_ex(THIS_CE, php_http_request_datashare_global_get(), NULL TSRMLS_CC), 1); + zend_update_static_property(THIS_CE, ZEND_STRL("instance"), instance TSRMLS_CC); + + if (PHP_HTTP_G->request_datashare.cookie) { + zend_update_property_bool(THIS_CE, instance, ZEND_STRL("cookie"), PHP_HTTP_G->request_datashare.cookie TSRMLS_CC); + } + if (PHP_HTTP_G->request_datashare.dns) { + zend_update_property_bool(THIS_CE, instance, ZEND_STRL("dns"), PHP_HTTP_G->request_datashare.dns TSRMLS_CC); + } + if (PHP_HTTP_G->request_datashare.ssl) { + zend_update_property_bool(THIS_CE, instance, ZEND_STRL("ssl"), PHP_HTTP_G->request_datashare.ssl TSRMLS_CC); + } + if (PHP_HTTP_G->request_datashare.connect) { + zend_update_property_bool(THIS_CE, instance, ZEND_STRL("connect"), PHP_HTTP_G->request_datashare.connect TSRMLS_CC); + } + } + + RETVAL_ZVAL(instance, 0, 0); + } + }end_error_handling(); +} + +PHP_MINIT_FUNCTION(http_request_datashare) +{ + curl_lock_data val; + + if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request_datashare"), curl_share_init, (php_http_persistent_handle_dtor_t) curl_share_cleanup, NULL)) { + return FAILURE; + } +#ifdef ZTS + if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request_datashare_lock"), php_http_request_datashare_locks_init, php_http_request_datashare_locks_dtor, NULL)) { + return FAILURE; + } +#endif + + if (!php_http_request_datashare_init(&php_http_request_datashare_global, 1 TSRMLS_CC)) { + return FAILURE; + } + + zend_hash_init(&php_http_request_datashare_options, 4, NULL, NULL, 1); +#define ADD_DATASHARE_OPT(name, opt) \ + val = opt; \ + zend_hash_add(&php_http_request_datashare_options, name, sizeof(name), &val, sizeof(curl_lock_data), NULL) + ADD_DATASHARE_OPT("cookie", CURL_LOCK_DATA_COOKIE); + ADD_DATASHARE_OPT("dns", CURL_LOCK_DATA_DNS); + ADD_DATASHARE_OPT("ssl", CURL_LOCK_DATA_SSL_SESSION); + ADD_DATASHARE_OPT("connect", CURL_LOCK_DATA_CONNECT); + + PHP_HTTP_REGISTER_CLASS(http\\request, DataShare, http_request_datashare, php_http_object_class_entry, 0); + php_http_request_datashare_class_entry->create_object = php_http_request_datashare_object_new; + memcpy(&php_http_request_datashare_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_http_request_datashare_object_handlers.clone_obj = NULL; + php_http_request_datashare_object_handlers.read_property = php_http_request_datashare_object_read_prop; + php_http_request_datashare_object_handlers.write_property = php_http_request_datashare_object_write_prop; + php_http_request_datashare_object_handlers.get_property_ptr_ptr = NULL; + + zend_class_implements(php_http_request_datashare_class_entry TSRMLS_CC, 1, spl_ce_Countable); + + zend_declare_property_null(THIS_CE, ZEND_STRL("instance"), (ZEND_ACC_STATIC|ZEND_ACC_PRIVATE) TSRMLS_CC); + zend_declare_property_bool(THIS_CE, ZEND_STRL("cookie"), 0, ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_bool(THIS_CE, ZEND_STRL("dns"), 0, ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_bool(THIS_CE, ZEND_STRL("ssl"), 0, ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_bool(THIS_CE, ZEND_STRL("connect"), 0, ZEND_ACC_PUBLIC TSRMLS_CC); + + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(http_request_datashare) +{ + php_http_request_datashare_dtor(&php_http_request_datashare_global); + zend_hash_destroy(&php_http_request_datashare_options); + + return SUCCESS; +} + +PHP_RINIT_FUNCTION(http_request_datashare) +{ + zend_llist_init(&PHP_HTTP_G->request_datashare.handles, sizeof(zval *), php_http_request_datashare_destroy_handles, 0); + + return SUCCESS; +} + +PHP_RSHUTDOWN_FUNCTION(http_request_datashare) +{ + zend_llist_destroy(&PHP_HTTP_G->request_datashare.handles); + + return SUCCESS; +} + diff --git a/php_http_request_datashare.h b/php_http_request_datashare.h new file mode 100644 index 0000000..fdc4950 --- /dev/null +++ b/php_http_request_datashare.h @@ -0,0 +1,71 @@ +#ifndef PHP_HTTP_REQUEST_DATASHARE_H +#define PHP_HTTP_REQUEST_DATASHARE_H + +#ifdef ZTS +typedef struct php_http_request_datashare_lock { + CURL *ch; + MUTEX_T mx; +} php_http_request_datashare_lock_t; +#endif + +typedef union php_http_request_datashare_handle { + zend_llist *list; +#ifdef ZTS + php_http_request_datashare_lock_t *locks; +#endif +} php_http_request_datashare_handle_t; + +typedef struct php_http_request_datashare_t { + CURLSH *ch; + php_http_request_datashare_handle_t handle; + unsigned persistent:1; +#ifdef ZTS + void ***ts; +#endif +} php_http_request_datashare_t; + +struct php_http_request_datashare_globals { + zend_llist handles; + zend_bool cookie; + zend_bool dns; + zend_bool ssl; + zend_bool connect; +}; + +#define PHP_HTTP_RSHARE_HANDLES(s) ((s)->persistent ? &PHP_HTTP_G->request_datashare.handles : (s)->handle.list) + +extern php_http_request_datashare_t *php_http_request_datashare_global_get(void); + +extern PHP_MINIT_FUNCTION(http_request_datashare); +extern PHP_MSHUTDOWN_FUNCTION(http_request_datashare); +extern PHP_RINIT_FUNCTION(http_request_datashare); +extern PHP_RSHUTDOWN_FUNCTION(http_request_datashare); + +PHP_HTTP_API php_http_request_datashare_t *php_http_request_datashare_init(php_http_request_datashare_t *share, zend_bool persistent TSRMLS_DC); +PHP_HTTP_API STATUS php_http_request_datashare_attach(php_http_request_datashare_t *share, zval *request); +PHP_HTTP_API STATUS php_http_request_datashare_detach(php_http_request_datashare_t *share, zval *request); +PHP_HTTP_API void php_http_request_datashare_detach_all(php_http_request_datashare_t *share); +PHP_HTTP_API void php_http_request_datashare_dtor(php_http_request_datashare_t *share); +PHP_HTTP_API void php_http_request_datashare_free(php_http_request_datashare_t **share); +PHP_HTTP_API STATUS php_http_request_datashare_set(php_http_request_datashare_t *share, const char *option, size_t option_len, zend_bool enable); + +typedef struct php_http_request_datashare_object { + zend_object zo; + php_http_request_datashare_t *share; +} php_http_request_datashare_object_t; + +extern zend_class_entry *php_http_request_datashare_class_entry; +extern zend_function_entry php_http_request_datashare_method_entry[]; + +extern zend_object_value php_http_request_datashare_object_new(zend_class_entry *ce TSRMLS_DC); +extern zend_object_value php_http_request_datashare_object_new_ex(zend_class_entry *ce, php_http_request_datashare_t *share, php_http_request_datashare_object_t **ptr TSRMLS_DC); +extern void php_http_request_datashare_object_free(void *object TSRMLS_DC); + +PHP_METHOD(HttpRequestDataShare, __destruct); +PHP_METHOD(HttpRequestDataShare, count); +PHP_METHOD(HttpRequestDataShare, attach); +PHP_METHOD(HttpRequestDataShare, detach); +PHP_METHOD(HttpRequestDataShare, reset); +PHP_METHOD(HttpRequestDataShare, getGlobalInstance); + +#endif /* PHP_HTTP_REQUEST_DATASHARE_H */ diff --git a/php_http_request_info.c b/php_http_request_info.c new file mode 100644 index 0000000..494a5dc --- /dev/null +++ b/php_http_request_info.c @@ -0,0 +1,191 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_request_info.c 293136 2010-01-05 08:48:52Z mike $ */ + +#include "php_http.h" + +PHP_HTTP_API void php_http_request_info(php_http_request_t *request, HashTable *info) +{ + char *c; + long l; + double d; + struct curl_slist *s, *p; + zval *subarray, array; + INIT_PZVAL_ARRAY(&array, info); + + /* BEGIN */ + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_EFFECTIVE_URL, &c)) { + add_assoc_string_ex(&array, "effective_url", sizeof("effective_url"), c ? c : "", 1); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_RESPONSE_CODE, &l)) { + add_assoc_long_ex(&array, "response_code", sizeof("response_code"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_TOTAL_TIME, &d)) { + add_assoc_double_ex(&array, "total_time", sizeof("total_time"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_NAMELOOKUP_TIME, &d)) { + add_assoc_double_ex(&array, "namelookup_time", sizeof("namelookup_time"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONNECT_TIME, &d)) { + add_assoc_double_ex(&array, "connect_time", sizeof("connect_time"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_PRETRANSFER_TIME, &d)) { + add_assoc_double_ex(&array, "pretransfer_time", sizeof("pretransfer_time"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SIZE_UPLOAD, &d)) { + add_assoc_double_ex(&array, "size_upload", sizeof("size_upload"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SIZE_DOWNLOAD, &d)) { + add_assoc_double_ex(&array, "size_download", sizeof("size_download"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SPEED_DOWNLOAD, &d)) { + add_assoc_double_ex(&array, "speed_download", sizeof("speed_download"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SPEED_UPLOAD, &d)) { + add_assoc_double_ex(&array, "speed_upload", sizeof("speed_upload"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_HEADER_SIZE, &l)) { + add_assoc_long_ex(&array, "header_size", sizeof("header_size"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_REQUEST_SIZE, &l)) { + add_assoc_long_ex(&array, "request_size", sizeof("request_size"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SSL_VERIFYRESULT, &l)) { + add_assoc_long_ex(&array, "ssl_verifyresult", sizeof("ssl_verifyresult"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_FILETIME, &l)) { + add_assoc_long_ex(&array, "filetime", sizeof("filetime"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d)) { + add_assoc_double_ex(&array, "content_length_download", sizeof("content_length_download"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONTENT_LENGTH_UPLOAD, &d)) { + add_assoc_double_ex(&array, "content_length_upload", sizeof("content_length_upload"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_STARTTRANSFER_TIME, &d)) { + add_assoc_double_ex(&array, "starttransfer_time", sizeof("starttransfer_time"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONTENT_TYPE, &c)) { + add_assoc_string_ex(&array, "content_type", sizeof("content_type"), c ? c : "", 1); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_REDIRECT_TIME, &d)) { + add_assoc_double_ex(&array, "redirect_time", sizeof("redirect_time"), d); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_REDIRECT_COUNT, &l)) { + add_assoc_long_ex(&array, "redirect_count", sizeof("redirect_count"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_HTTP_CONNECTCODE, &l)) { + add_assoc_long_ex(&array, "connect_code", sizeof("connect_code"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_HTTPAUTH_AVAIL, &l)) { + add_assoc_long_ex(&array, "httpauth_avail", sizeof("httpauth_avail"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_PROXYAUTH_AVAIL, &l)) { + add_assoc_long_ex(&array, "proxyauth_avail", sizeof("proxyauth_avail"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_OS_ERRNO, &l)) { + add_assoc_long_ex(&array, "os_errno", sizeof("os_errno"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_NUM_CONNECTS, &l)) { + add_assoc_long_ex(&array, "num_connects", sizeof("num_connects"), l); + } + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_SSL_ENGINES, &s)) { + MAKE_STD_ZVAL(subarray); + array_init(subarray); + for (p = s; p; p = p->next) { + if (p->data) { + add_next_index_string(subarray, p->data, 1); + } + } + add_assoc_zval_ex(&array, "ssl_engines", sizeof("ssl_engines"), subarray); + curl_slist_free_all(s); + } +#if PHP_HTTP_CURL_VERSION(7,14,1) + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_COOKIELIST, &s)) { + MAKE_STD_ZVAL(subarray); + array_init(subarray); + for (p = s; p; p = p->next) { + if (p->data) { + add_next_index_string(subarray, p->data, 1); + } + } + add_assoc_zval_ex(&array, "cookies", sizeof("cookies"), subarray); + curl_slist_free_all(s); + } +#endif +#if PHP_HTTP_CURL_VERSION(7,18,2) + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_REDIRECT_URL, &c)) { + add_assoc_string_ex(&array, "redirect_url", sizeof("redirect_url"), c ? c : "", 1); + } +#endif +#if PHP_HTTP_CURL_VERSION(7,19,0) + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_PRIMARY_IP, &c)) { + add_assoc_string_ex(&array, "primary_ip", sizeof("primary_ip"), c ? c : "", 1); + } +#endif +#if PHP_HTTP_CURL_VERSION(7,19,0) + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_APPCONNECT_TIME, &d)) { + add_assoc_double_ex(&array, "appconnect_time", sizeof("appconnect_time"), d); + } +#endif +#if PHP_HTTP_CURL_VERSION(7,19,4) + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CONDITION_UNMET, &l)) { + add_assoc_long_ex(&array, "condition_unmet", sizeof("condition_unmet"), l); + } +#endif +/* END */ +#if PHP_HTTP_CURL_VERSION(7,19,1) && defined(PHP_HTTP_HAVE_OPENSSL) + { + int i; + zval *ci_array; + struct curl_certinfo *ci; + char *colon, *keyname; + + if (CURLE_OK == curl_easy_getinfo(request->ch, CURLINFO_CERTINFO, &ci)) { + MAKE_STD_ZVAL(ci_array); + array_init(ci_array); + + for (i = 0; i < ci->num_of_certs; ++i) { + s = ci->certinfo[i]; + + MAKE_STD_ZVAL(subarray); + array_init(subarray); + for (p = s; p; p = p->next) { + if (p->data) { + if ((colon = strchr(p->data, ':'))) { + keyname = estrndup(p->data, colon - p->data); + add_assoc_string_ex(subarray, keyname, colon - p->data + 1, colon + 1, 1); + efree(keyname); + } else { + add_next_index_string(subarray, p->data, 1); + } + } + } + add_next_index_zval(ci_array, subarray); + } + add_assoc_zval_ex(&array, "certinfo", sizeof("certinfo"), ci_array); + } + } +#endif + add_assoc_string_ex(&array, "error", sizeof("error"), php_http_request_storage_get(request->ch)->errorbuffer, 1); +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ diff --git a/php_http_request_method.c b/php_http_request_method.c new file mode 100644 index 0000000..298b867 --- /dev/null +++ b/php_http_request_method.c @@ -0,0 +1,229 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_request_method_api.c 292841 2009-12-31 08:48:57Z mike $ */ + +#include "php_http.h" + +static PHP_HTTP_STRLIST(php_http_request_methods) = + PHP_HTTP_STRLIST_ITEM("UNKNOWN") + /* HTTP/1.1 */ + PHP_HTTP_STRLIST_ITEM("GET") + PHP_HTTP_STRLIST_ITEM("HEAD") + PHP_HTTP_STRLIST_ITEM("POST") + PHP_HTTP_STRLIST_ITEM("PUT") + PHP_HTTP_STRLIST_ITEM("DELETE") + PHP_HTTP_STRLIST_ITEM("OPTIONS") + PHP_HTTP_STRLIST_ITEM("TRACE") + PHP_HTTP_STRLIST_ITEM("CONNECT") + /* WebDAV - RFC 2518 */ + PHP_HTTP_STRLIST_ITEM("PROPFIND") + PHP_HTTP_STRLIST_ITEM("PROPPATCH") + PHP_HTTP_STRLIST_ITEM("MKCOL") + PHP_HTTP_STRLIST_ITEM("COPY") + PHP_HTTP_STRLIST_ITEM("MOVE") + PHP_HTTP_STRLIST_ITEM("LOCK") + PHP_HTTP_STRLIST_ITEM("UNLOCK") + /* WebDAV Versioning - RFC 3253 */ + PHP_HTTP_STRLIST_ITEM("VERSION-CONTROL") + PHP_HTTP_STRLIST_ITEM("REPORT") + PHP_HTTP_STRLIST_ITEM("CHECKOUT") + PHP_HTTP_STRLIST_ITEM("CHECKIN") + PHP_HTTP_STRLIST_ITEM("UNCHECKOUT") + PHP_HTTP_STRLIST_ITEM("MKWORKSPACE") + PHP_HTTP_STRLIST_ITEM("UPDATE") + PHP_HTTP_STRLIST_ITEM("LABEL") + PHP_HTTP_STRLIST_ITEM("MERGE") + PHP_HTTP_STRLIST_ITEM("BASELINE-CONTROL") + PHP_HTTP_STRLIST_ITEM("MKACTIVITY") + /* WebDAV Access Control - RFC 3744 */ + PHP_HTTP_STRLIST_ITEM("ACL") + PHP_HTTP_STRLIST_STOP +; + +PHP_HTTP_API const char *php_http_request_method_name(php_http_request_method_t meth) +{ + if (meth > PHP_HTTP_NO_REQUEST_METHOD && meth < PHP_HTTP_MAX_REQUEST_METHOD) { + return php_http_strlist_find(php_http_request_methods, 1, meth); + } else { + zval **val, *cmp, res; + HashPosition pos; + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + + INIT_PZVAL(&res); + FOREACH_HASH_KEYVAL(pos, &php_http_request_class_entry->constants_table, key, val) { + MAKE_STD_ZVAL(cmp); + ZVAL_LONG(cmp, meth); + is_equal_function(&res, *val, cmp TSRMLS_CC); + zval_ptr_dtor(&cmp); + + if (Z_LVAL(res)) { + return key.str; + } + } + } + return NULL; +} + +PHP_HTTP_API STATUS php_http_request_method_register(const char *meth_str, size_t meth_len, long *id TSRMLS_DC) +{ + long num = zend_hash_num_elements(&php_http_request_class_entry->constants_table); + + if (SUCCESS == zend_declare_class_constant_long(php_http_request_method_class_entry, meth_str, meth_len, num TSRMLS_CC)) { + if (id) { + *id = num; + } + return SUCCESS; + } + return FAILURE; +} + +zend_class_entry *php_http_request_method_class_entry; + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpRequestMethod, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpRequestMethod, method, 0) +#define PHP_HTTP_REQMETH_ME(method, visibility) PHP_ME(HttpRequestMethod, method, PHP_HTTP_ARGS(HttpRequestMethod, method), visibility) + +#ifdef register +# undef register +#endif + +PHP_HTTP_BEGIN_ARGS(__construct, 1) + PHP_HTTP_ARG_VAL(name, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(__toString); + +PHP_HTTP_EMPTY_ARGS(getId); + +PHP_HTTP_BEGIN_ARGS(exists, 1) + PHP_HTTP_ARG_VAL(method, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(register, 1) + PHP_HTTP_ARG_VAL(method, 0) +PHP_HTTP_END_ARGS; + +zend_function_entry php_http_request_method_method_entry[] = { + PHP_HTTP_REQMETH_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_HTTP_REQMETH_ME(__toString, ZEND_ACC_PUBLIC) + PHP_HTTP_REQMETH_ME(getId, ZEND_ACC_PUBLIC) + PHP_HTTP_REQMETH_ME(exists, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + PHP_HTTP_REQMETH_ME(register, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) + + EMPTY_FUNCTION_ENTRY +}; + +PHP_METHOD(HttpRequestMethod, __construct) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + char *meth_str; + int meth_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &meth_str, &meth_len)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_method)) { + zval *zarg, *zret; + + if (SUCCESS == zend_get_parameters(ZEND_NUM_ARGS(), 1, &zarg)) { + if (zend_call_method_with_1_params(&getThis(), php_http_request_method_class_entry, NULL, "exists", &zret, zarg)) { + if (i_zend_is_true(zret)) { + zend_update_property_stringl(php_http_request_method_class_entry, getThis(), ZEND_STRL("name"), meth_str, meth_len TSRMLS_CC); + } else { + php_http_error(HE_THROW, PHP_HTTP_E_REQUEST_METHOD, "Unknown request method '%s'", meth_str); + } + zval_ptr_dtor(&zret); + } + } + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequestMethod, __toString) +{ + if (SUCCESS == zend_parse_parameters_none()) { + zval *retval = php_http_zsep(IS_STRING, zend_read_property(php_http_request_method_class_entry, getThis(), ZEND_STRL("name"), 0 TSRMLS_CC)); + + RETURN_ZVAL(retval, 0, 0); + } + RETURN_EMPTY_STRING(); +} + +PHP_METHOD(HttpRequestMethod, getId) +{ + if (SUCCESS == zend_parse_parameters_none()) { + zval **data, *meth = php_http_zsep(IS_STRING, zend_read_property(php_http_request_method_class_entry, getThis(), ZEND_STRL("name"), 0 TSRMLS_CC)); + + if (SUCCESS == zend_hash_find(&php_http_request_method_class_entry->constants_table, Z_STRVAL_P(meth), Z_STRLEN_P(meth) + 1, (void *) &data)) { + zval_ptr_dtor(&meth); + RETURN_ZVAL(*data, 1, 0); + } + zval_ptr_dtor(&meth); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestMethod, exists) +{ + char *meth_str; + int meth_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &meth_str, &meth_len)) { + zval **data; + + if (SUCCESS == zend_hash_find(&php_http_request_method_class_entry->constants_table, meth_str, meth_len + 1, (void *) &data)) { + RETURN_ZVAL(*data, 1, 0); + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestMethod, register) +{ + char *meth_str; + int meth_len; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &meth_str, &meth_len)) { + RETURN_SUCCESS(zend_declare_class_constant_long(php_http_request_method_class_entry, meth_str, meth_len, zend_hash_num_elements(&php_http_request_class_entry->constants_table) TSRMLS_CC)); + } + RETURN_FALSE; +} + +PHP_MINIT_FUNCTION(http_request_method) +{ + php_http_strlist_iterator_t std; + + PHP_HTTP_REGISTER_CLASS(http\\request, Method, http_request_method, php_http_object_class_entry, 0); + + zend_declare_property_null(php_http_request_method_class_entry, ZEND_STRL("name"), ZEND_ACC_PROTECTED TSRMLS_CC); + + php_http_strlist_iterator_init(&std, php_http_request_methods, 1); + do { + unsigned id; + const char *meth = php_http_strlist_iterator_this(&std, &id); + + zend_declare_class_constant_long(php_http_request_method_class_entry, meth, strlen(meth), id TSRMLS_CC); + } while (*php_http_strlist_iterator_next(&std)); + + return SUCCESS; +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_request_method.h b/php_http_request_method.h new file mode 100644 index 0000000..6c29e80 --- /dev/null +++ b/php_http_request_method.h @@ -0,0 +1,81 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_request_method_api.h 292841 2009-12-31 08:48:57Z mike $ */ + +#ifndef PHP_HTTP_REQUEST_METHOD_H +#define PHP_HTTP_REQUEST_METHOD_H + +typedef enum php_http_request_method { + /* force the enum to be signed */ + PHP_HTTP_NEG_REQUEST_METHOD =-1, + PHP_HTTP_NO_REQUEST_METHOD = 0, + /* HTTP/1.1 */ + PHP_HTTP_GET = 1, + PHP_HTTP_HEAD = 2, + PHP_HTTP_POST = 3, + PHP_HTTP_PUT = 4, + PHP_HTTP_DELETE = 5, + PHP_HTTP_OPTIONS = 6, + PHP_HTTP_TRACE = 7, + PHP_HTTP_CONNECT = 8, + /* WebDAV - RFC 2518 */ + PHP_HTTP_PROPFIND = 9, + PHP_HTTP_PROPPATCH = 10, + PHP_HTTP_MKCOL = 11, + PHP_HTTP_COPY = 12, + PHP_HTTP_MOVE = 13, + PHP_HTTP_LOCK = 14, + PHP_HTTP_UNLOCK = 15, + /* WebDAV Versioning - RFC 3253 */ + PHP_HTTP_VERSION_CONTROL = 16, + PHP_HTTP_REPORT = 17, + PHP_HTTP_CHECKOUT = 18, + PHP_HTTP_CHECKIN = 19, + PHP_HTTP_UNCHECKOUT = 20, + PHP_HTTP_MKWORKSPACE = 21, + PHP_HTTP_UPDATE = 22, + PHP_HTTP_LABEL = 23, + PHP_HTTP_MERGE = 24, + PHP_HTTP_BASELINE_CONTROL = 25, + PHP_HTTP_MKACTIVITY = 26, + /* WebDAV Access Control - RFC 3744 */ + PHP_HTTP_ACL = 27, + PHP_HTTP_MAX_REQUEST_METHOD = 28 +} php_http_request_method_t; + +PHP_HTTP_API const char *php_http_request_method_name(php_http_request_method_t meth); +PHP_HTTP_API STATUS php_http_request_method_register(const char *meth_str, size_t meth_len, long *id TSRMLS_DC); + +extern zend_class_entry *php_http_request_method_class_entry; +extern zend_function_entry php_http_request_method_method_entry[]; + +extern PHP_METHOD(HttpRequestMethod, __construct); +extern PHP_METHOD(HttpRequestMethod, __toString); +extern PHP_METHOD(HttpRequestMethod, getId); + +extern PHP_METHOD(HttpRequestMethod, exists); +extern PHP_METHOD(HttpRequestMethod, register); + +extern PHP_MINIT_FUNCTION(http_request_method); + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_request_pool.c b/php_http_request_pool.c new file mode 100644 index 0000000..8585d77 --- /dev/null +++ b/php_http_request_pool.c @@ -0,0 +1,958 @@ + +#include "php_http.h" + + +#ifndef PHP_HTTP_DEBUG_REQPOOLS +# define PHP_HTTP_DEBUG_REQPOOLS 0 +#endif + +#ifdef PHP_HTTP_HAVE_EVENT +typedef struct php_http_request_pool_event { + struct event evnt; + php_http_request_pool_t *pool; +} php_http_request_pool_event_t; + +static void php_http_request_pool_timeout_callback(int socket, short action, void *event_data); +static void php_http_request_pool_event_callback(int socket, short action, void *event_data); +static int php_http_request_pool_socket_callback(CURL *easy, curl_socket_t s, int action, void *, void *); +static void php_http_request_pool_timer_callback(CURLM *multi, long timeout_ms, void *timer_data); +#endif + +static int php_http_request_pool_compare_handles(void *h1, void *h2); + +PHP_HTTP_API php_http_request_pool_t *php_http_request_pool_init(php_http_request_pool_t *pool TSRMLS_DC) +{ + zend_bool free_pool; + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Initializing request pool %p\n", pool); +#endif + + if ((free_pool = (!pool))) { + pool = emalloc(sizeof(php_http_request_pool_t)); + pool->ch = NULL; + } + + if (SUCCESS != php_http_persistent_handle_acquire(ZEND_STRL("http_request_pool"), &pool->ch TSRMLS_CC)) { + if (free_pool) { + efree(pool); + } + return NULL; + } + + TSRMLS_SET_CTX(pool->ts); + +#ifdef PHP_HTTP_HAVE_EVENT + pool->timeout = ecalloc(1, sizeof(struct event)); + curl_multi_setopt(pool->ch, CURLMOPT_SOCKETDATA, pool); + curl_multi_setopt(pool->ch, CURLMOPT_SOCKETFUNCTION, php_http_request_pool_socket_callback); + curl_multi_setopt(pool->ch, CURLMOPT_TIMERDATA, pool); + curl_multi_setopt(pool->ch, CURLMOPT_TIMERFUNCTION, php_http_request_pool_timer_callback); +#endif + + pool->unfinished = 0; + zend_llist_init(&pool->finished, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0); + zend_llist_init(&pool->handles, sizeof(zval *), (llist_dtor_func_t) ZVAL_PTR_DTOR, 0); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Initialized request pool %p\n", pool); +#endif + + return pool; +} + +PHP_HTTP_API STATUS php_http_request_pool_attach(php_http_request_pool_t *pool, zval *request) +{ +#ifdef ZTS + TSRMLS_FETCH_FROM_CTX(pool->ts); +#endif + php_http_request_object_t *req = zend_object_store_get_object(request TSRMLS_CC); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Attaching HttpRequest(#%d) %p to pool %p\n", Z_OBJ_HANDLE_P(request), req, pool); +#endif + + if (req->pool) { + php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is already member of %s HttpRequestPool", Z_OBJ_HANDLE_P(request), req->pool == pool ? "this" : "another"); + } else if (SUCCESS != php_http_request_object_requesthandler(req, request)) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "Could not initialize HttpRequest object(#%d) for attaching to the HttpRequestPool", Z_OBJ_HANDLE_P(request)); + } else { + CURLMcode code = curl_multi_add_handle(pool->ch, req->request->ch); + + if (CURLM_OK != code) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "Could not attach HttpRequest object(#%d) to the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request), curl_multi_strerror(code)); + } else { + req->pool = pool; + + Z_ADDREF_P(request); + zend_llist_add_element(&pool->handles, &request); + ++pool->unfinished; + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "> %d HttpRequests attached to pool %p\n", zend_llist_count(&pool->handles), pool); +#endif + return SUCCESS; + } + } + return FAILURE; +} + +PHP_HTTP_API STATUS php_http_request_pool_detach(php_http_request_pool_t *pool, zval *request) +{ + CURLMcode code; +#ifdef ZTS + TSRMLS_FETCH_FROM_CTX(pool->ts); +#endif + php_http_request_object_t *req = zend_object_store_get_object(request TSRMLS_CC); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Detaching HttpRequest(#%d) %p from pool %p\n", Z_OBJ_HANDLE_P(request), req, pool); +#endif + + if (!req->pool) { + /* not attached to any pool */ +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "HttpRequest object(#%d) %p is not attached to any HttpRequestPool\n", Z_OBJ_HANDLE_P(request), req); +#endif + } else if (req->pool != pool) { + php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "HttpRequest object(#%d) is not attached to this HttpRequestPool", Z_OBJ_HANDLE_P(request)); + } else if (req->request->_progress.in_cb) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "HttpRequest object(#%d) cannot be detached from the HttpRequestPool while executing the progress callback", Z_OBJ_HANDLE_P(request)); + } else if (CURLM_OK != (code = curl_multi_remove_handle(pool->ch, req->request->ch))) { + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST_POOL, "Could not detach HttpRequest object(#%d) from the HttpRequestPool: %s", Z_OBJ_HANDLE_P(request), curl_multi_strerror(code)); + } else { + req->pool = NULL; + zend_llist_del_element(&pool->finished, request, php_http_request_pool_compare_handles); + zend_llist_del_element(&pool->handles, request, php_http_request_pool_compare_handles); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "> %d HttpRequests remaining in pool %p\n", zend_llist_count(&pool->handles), pool); +#endif + + return SUCCESS; + } + return FAILURE; +} + +PHP_HTTP_API void php_http_request_pool_apply(php_http_request_pool_t *pool, php_http_request_pool_apply_func_t cb) +{ + int count = zend_llist_count(&pool->handles); + + if (count) { + int i = 0; + zend_llist_position pos; + zval **handle, **handles = emalloc(count * sizeof(zval *)); + + for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) { + handles[i++] = *handle; + } + + /* should never happen */ + if (i != count) { + zend_error(E_ERROR, "number of fetched request handles do not match overall count"); + count = i; + } + + for (i = 0; i < count; ++i) { + if (cb(pool, handles[i])) { + break; + } + } + efree(handles); + } +} + +PHP_HTTP_API void php_http_request_pool_apply_with_arg(php_http_request_pool_t *pool, php_http_request_pool_apply_with_arg_func_t cb, void *arg) +{ + int count = zend_llist_count(&pool->handles); + + if (count) { + int i = 0; + zend_llist_position pos; + zval **handle, **handles = emalloc(count * sizeof(zval *)); + + for (handle = zend_llist_get_first_ex(&pool->handles, &pos); handle; handle = zend_llist_get_next_ex(&pool->handles, &pos)) { + handles[i++] = *handle; + } + + /* should never happen */ + if (i != count) { + zend_error(E_ERROR, "number of fetched request handles do not match overall count"); + count = i; + } + + for (i = 0; i < count; ++i) { + if (cb(pool, handles[i], arg)) { + break; + } + } + efree(handles); + } +} + +PHP_HTTP_API void php_http_request_pool_detach_all(php_http_request_pool_t *pool) +{ +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Detaching %d requests from pool %p\n", zend_llist_count(&pool->handles), pool); +#endif + php_http_request_pool_apply(pool, php_http_request_pool_detach); +} + +PHP_HTTP_API STATUS php_http_request_pool_send(php_http_request_pool_t *pool) +{ + TSRMLS_FETCH_FROM_CTX(pool->ts); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Attempt to send %d requests of pool %p\n", zend_llist_count(&pool->handles), pool); +#endif + +#ifdef PHP_HTTP_HAVE_EVENT + if (pool->useevents) { + do { +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "& Starting event dispatcher of pool %p\n", pool); +#endif + event_base_dispatch(PHP_HTTP_G->request_pool.event_base); + } while (pool->unfinished); + } else +#endif + { + while (php_http_request_pool_perform(pool)) { + if (SUCCESS != php_http_request_pool_select(pool, NULL)) { +#ifdef PHP_WIN32 + /* see http://msdn.microsoft.com/library/en-us/winsock/winsock/windows_sockets_error_codes_2.asp */ + php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "WinSock error: %d", WSAGetLastError()); +#else + php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, strerror(errno)); +#endif + return FAILURE; + } + } + } + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Finished sending %d HttpRequests of pool %p (still unfinished: %d)\n", zend_llist_count(&pool->handles), pool, pool->unfinished); +#endif + + return SUCCESS; +} + +PHP_HTTP_API void php_http_request_pool_dtor(php_http_request_pool_t *pool) +{ + TSRMLS_FETCH_FROM_CTX(pool->ts); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Destructing request pool %p\n", pool); +#endif + +#ifdef PHP_HTTP_HAVE_EVENT + efree(pool->timeout); +#endif + + php_http_request_pool_detach_all(pool); + + pool->unfinished = 0; + zend_llist_clean(&pool->finished); + zend_llist_clean(&pool->handles); + php_http_persistent_handle_release(ZEND_STRL("php_http_request_pool_t"), &pool->ch TSRMLS_CC); +} + +PHP_HTTP_API void php_http_request_pool_free(php_http_request_pool_t **pool) { + if (*pool) { + php_http_request_pool_dtor(*pool); + efree(*pool); + *pool = NULL; + } +} + +#ifdef PHP_WIN32 +# define SELECT_ERROR SOCKET_ERROR +#else +# define SELECT_ERROR -1 +#endif + +PHP_HTTP_API STATUS php_http_request_pool_select(php_http_request_pool_t *pool, struct timeval *custom_timeout) +{ + int MAX; + fd_set R, W, E; + struct timeval timeout; + +#ifdef PHP_HTTP_HAVE_EVENT + if (pool->useevents) { + TSRMLS_FETCH_FROM_CTX(pool->ts); + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented; use HttpRequest callbacks"); + return FAILURE; + } +#endif + + if (custom_timeout && timerisset(custom_timeout)) { + timeout = *custom_timeout; + } else { + php_http_request_pool_timeout(pool, &timeout); + } + + FD_ZERO(&R); + FD_ZERO(&W); + FD_ZERO(&E); + + if (CURLM_OK == curl_multi_fdset(pool->ch, &R, &W, &E, &MAX)) { + if (MAX == -1) { + php_http_sleep((double) timeout.tv_sec + (double) (timeout.tv_usec / PHP_HTTP_MCROSEC)); + return SUCCESS; + } else if (SELECT_ERROR != select(MAX + 1, &R, &W, &E, &timeout)) { + return SUCCESS; + } + } + return FAILURE; +} + +PHP_HTTP_API int php_http_request_pool_perform(php_http_request_pool_t *pool) +{ + TSRMLS_FETCH_FROM_CTX(pool->ts); + +#ifdef PHP_HTTP_HAVE_EVENT + if (pool->useevents) { + php_http_error(HE_WARNING, PHP_HTTP_E_RUNTIME, "not implemented; use HttpRequest callbacks"); + return FAILURE; + } +#endif + + while (CURLM_CALL_MULTI_PERFORM == curl_multi_perform(pool->ch, &pool->unfinished)); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "%u unfinished requests of pool %p remaining\n", pool->unfinished, pool); +#endif + + php_http_request_pool_responsehandler(pool); + + return pool->unfinished; +} + +void php_http_request_pool_responsehandler(php_http_request_pool_t *pool) +{ + CURLMsg *msg; + int remaining = 0; + TSRMLS_FETCH_FROM_CTX(pool->ts); + + do { + msg = curl_multi_info_read(pool->ch, &remaining); + if (msg && CURLMSG_DONE == msg->msg) { + if (CURLE_OK != msg->data.result) { + php_http_request_storage_t *st = php_http_request_storage_get(msg->easy_handle); + php_http_error(HE_WARNING, PHP_HTTP_E_REQUEST, "%s; %s (%s)", curl_easy_strerror(msg->data.result), STR_PTR(st->errorbuffer), STR_PTR(st->url)); + } + php_http_request_pool_apply_with_arg(pool, php_http_request_pool_apply_responsehandler, msg->easy_handle); + } + } while (remaining); +} + +int php_http_request_pool_apply_responsehandler(php_http_request_pool_t *pool, zval *req, void *ch) +{ +#ifdef ZTS + TSRMLS_FETCH_FROM_CTX(pool->ts); +#endif + php_http_request_object_t *obj = zend_object_store_get_object(req TSRMLS_CC); + + if ((!ch) || obj->request->ch == (CURL *) ch) { + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Fetching data from HttpRequest(#%d) %p of pool %p\n", Z_OBJ_HANDLE_P(req), obj, obj->pool); +#endif + + Z_ADDREF_P(req); + zend_llist_add_element(&obj->pool->finished, &req); + php_http_request_object_responsehandler(obj, req); + return 1; + } + return 0; +} + +struct timeval *php_http_request_pool_timeout(php_http_request_pool_t *pool, struct timeval *timeout) +{ +#ifdef HAVE_CURL_MULTI_TIMEOUT + long max_tout = 1000; + + if ((CURLM_OK == curl_multi_timeout(pool->ch, &max_tout)) && (max_tout > 0)) { + timeout->tv_sec = max_tout / 1000; + timeout->tv_usec = (max_tout % 1000) * 1000; + } else { +#endif + timeout->tv_sec = 0; + timeout->tv_usec = 1000; +#ifdef HAVE_CURL_MULTI_TIMEOUT + } +#endif + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Calculating timeout (%lu, %lu) of pool %p\n", (ulong) timeout->tv_sec, (ulong) timeout->tv_usec, pool); +#endif + + return timeout; +} + +/*#*/ + +static int php_http_request_pool_compare_handles(void *h1, void *h2) +{ + return (Z_OBJ_HANDLE_PP((zval **) h1) == Z_OBJ_HANDLE_P((zval *) h2)); +} + +#ifdef PHP_HTTP_HAVE_EVENT + +static void php_http_request_pool_timeout_callback(int socket, short action, void *event_data) +{ + php_http_request_pool_t *pool = event_data; + + if (pool->useevents) { + CURLMcode rc; + TSRMLS_FETCH_FROM_CTX(pool->ts); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Timeout occurred of pool %p\n", pool); +#endif + + while (CURLM_CALL_MULTI_PERFORM == (rc = curl_multi_socket(pool->ch, CURL_SOCKET_TIMEOUT, &pool->unfinished))); + + if (CURLM_OK != rc) { + php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, curl_multi_strerror(rc)); + } + + php_http_request_pool_responsehandler(pool); + } +} + +static void php_http_request_pool_event_callback(int socket, short action, void *event_data) +{ + php_http_request_pool_event_t *ev = event_data; + php_http_request_pool_t *pool = ev->pool; + + if (pool->useevents) { + CURLMcode rc = CURLE_OK; + TSRMLS_FETCH_FROM_CTX(ev->pool->ts); + +#if PHP_HTTP_DEBUG_REQPOOLS + { + static const char event_strings[][20] = {"NONE","TIMEOUT","READ","TIMEOUT|READ","WRITE","TIMEOUT|WRITE","READ|WRITE","TIMEOUT|READ|WRITE","SIGNAL"}; + fprintf(stderr, "Event on socket %d (%s) event %p of pool %p\n", socket, event_strings[action], ev, pool); + } +#endif + + /* don't use 'ev' below this loop as it might 've been freed in the socket callback */ + do { +#ifdef HAVE_CURL_MULTI_SOCKET_ACTION + switch (action & (EV_READ|EV_WRITE)) { + case EV_READ: + rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_IN, &pool->unfinished); + break; + case EV_WRITE: + rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_OUT, &pool->unfinished); + break; + case EV_READ|EV_WRITE: + rc = curl_multi_socket_action(pool->ch, socket, CURL_CSELECT_IN|CURL_CSELECT_OUT, &pool->unfinished); + break; + default: + php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "Unknown event %d", (int) action); + return; + } +#else + rc = curl_multi_socket(pool->ch, socket, &pool->unfinished); +#endif + } while (CURLM_CALL_MULTI_PERFORM == rc); + + switch (rc) { + case CURLM_BAD_SOCKET: +#if 0 + fprintf(stderr, "!!! Bad socket: %d (%d)\n", socket, (int) action); +#endif + case CURLM_OK: + break; + default: + php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, curl_multi_strerror(rc)); + break; + } + + php_http_request_pool_responsehandler(pool); + + /* remove timeout if there are no transfers left */ + if (!pool->unfinished && event_initialized(pool->timeout) && event_pending(pool->timeout, EV_TIMEOUT, NULL)) { + event_del(pool->timeout); +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Removed timeout of pool %p\n", pool); +#endif + } + } +} + +static int php_http_request_pool_socket_callback(CURL *easy, curl_socket_t sock, int action, void *socket_data, void *assign_data) +{ + php_http_request_pool_t *pool = socket_data; + + if (pool->useevents) { + int events = EV_PERSIST; + php_http_request_pool_event_t *ev = assign_data; + TSRMLS_FETCH_FROM_CTX(pool->ts); + + if (!ev) { + ev = ecalloc(1, sizeof(php_http_request_pool_event_t)); + ev->pool = pool; + curl_multi_assign(pool->ch, sock, ev); + event_base_set(PHP_HTTP_G->request_pool.event_base, &ev->evnt); + } else { + event_del(&ev->evnt); + } + +#if PHP_HTTP_DEBUG_REQPOOLS + { + static const char action_strings[][8] = {"NONE", "IN", "OUT", "INOUT", "REMOVE"}; + php_http_request_t *r; + curl_easy_getinfo(easy, CURLINFO_PRIVATE, &r); + fprintf(stderr, "Callback on socket %2d (%8s) event %p of pool %p (%d)\n", (int) sock, action_strings[action], ev, pool, pool->unfinished); + } +#endif + + switch (action) { + case CURL_POLL_IN: + events |= EV_READ; + break; + case CURL_POLL_OUT: + events |= EV_WRITE; + break; + case CURL_POLL_INOUT: + events |= EV_READ|EV_WRITE; + break; + + case CURL_POLL_REMOVE: + efree(ev); + case CURL_POLL_NONE: + return 0; + + default: + php_http_error(HE_WARNING, PHP_HTTP_E_SOCKET, "Unknown socket action %d", action); + return -1; + } + + event_set(&ev->evnt, sock, events, php_http_request_pool_event_callback, ev); + event_add(&ev->evnt, NULL); + } + + return 0; +} + +static void php_http_request_pool_timer_callback(CURLM *multi, long timeout_ms, void *timer_data) +{ + php_http_request_pool_t *pool = timer_data; + + if (pool->useevents) { + TSRMLS_FETCH_FROM_CTX(pool->ts); + struct timeval timeout; + + if (!event_initialized(pool->timeout)) { + event_set(pool->timeout, -1, 0, php_http_request_pool_timeout_callback, pool); + event_base_set(PHP_HTTP_G->request_pool.event_base, pool->timeout); + } else if (event_pending(pool->timeout, EV_TIMEOUT, NULL)) { + event_del(pool->timeout); + } + + if (timeout_ms > 0) { + timeout.tv_sec = timeout_ms / 1000; + timeout.tv_usec = (timeout_ms % 1000) * 1000; + } else { + php_http_request_pool_timeout(pool, &timeout); + } + + event_add(pool->timeout, &timeout); + +#if PHP_HTTP_DEBUG_REQPOOLS + fprintf(stderr, "Updating timeout %lu (%lu, %lu) of pool %p\n", (ulong) timeout_ms, (ulong) timeout.tv_sec, (ulong) timeout.tv_usec, pool); +#endif + } +} +#endif /* HAVE_EVENT */ + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpRequestPool, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpRequestPool, method, 0) +#define PHP_HTTP_REQPOOL_ME(method, visibility) PHP_ME(HttpRequestPool, method, PHP_HTTP_ARGS(HttpRequestPool, method), visibility) + +PHP_HTTP_EMPTY_ARGS(__construct); + +PHP_HTTP_EMPTY_ARGS(__destruct); +PHP_HTTP_EMPTY_ARGS(reset); + +PHP_HTTP_BEGIN_ARGS(attach, 1) + PHP_HTTP_ARG_OBJ(http\\Request, request, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(detach, 1) + PHP_HTTP_ARG_OBJ(http\\Request, request, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(send); +PHP_HTTP_EMPTY_ARGS(socketPerform); +PHP_HTTP_BEGIN_ARGS(socketSelect, 0) + PHP_HTTP_ARG_VAL(timeout, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_EMPTY_ARGS(valid); +PHP_HTTP_EMPTY_ARGS(current); +PHP_HTTP_EMPTY_ARGS(key); +PHP_HTTP_EMPTY_ARGS(next); +PHP_HTTP_EMPTY_ARGS(rewind); + +PHP_HTTP_EMPTY_ARGS(count); + +PHP_HTTP_EMPTY_ARGS(getAttachedRequests); +PHP_HTTP_EMPTY_ARGS(getFinishedRequests); + +PHP_HTTP_BEGIN_ARGS(enablePipelining, 0) + PHP_HTTP_ARG_VAL(enable, 0) +PHP_HTTP_END_ARGS; + +PHP_HTTP_BEGIN_ARGS(enableEvents, 0) + PHP_HTTP_ARG_VAL(enable, 0) +PHP_HTTP_END_ARGS; + +zend_class_entry *php_http_request_pool_class_entry; +zend_function_entry php_http_request_pool_method_entry[] = { + PHP_HTTP_REQPOOL_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_HTTP_REQPOOL_ME(__destruct, ZEND_ACC_PUBLIC|ZEND_ACC_DTOR) + PHP_HTTP_REQPOOL_ME(attach, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(detach, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(send, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(reset, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQPOOL_ME(socketPerform, ZEND_ACC_PROTECTED) + PHP_HTTP_REQPOOL_ME(socketSelect, ZEND_ACC_PROTECTED) + + /* implements Iterator */ + PHP_HTTP_REQPOOL_ME(valid, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(current, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(key, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(next, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(rewind, ZEND_ACC_PUBLIC) + + /* implmenents Countable */ + PHP_HTTP_REQPOOL_ME(count, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQPOOL_ME(getAttachedRequests, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(getFinishedRequests, ZEND_ACC_PUBLIC) + + PHP_HTTP_REQPOOL_ME(enablePipelining, ZEND_ACC_PUBLIC) + PHP_HTTP_REQPOOL_ME(enableEvents, ZEND_ACC_PUBLIC) + + EMPTY_FUNCTION_ENTRY +}; +static zend_object_handlers php_http_request_pool_object_handlers; + +zend_object_value php_http_request_pool_object_new(zend_class_entry *ce TSRMLS_DC) +{ + zend_object_value ov; + php_http_request_pool_object_t *o; + + o = ecalloc(1, sizeof(php_http_request_pool_object_t)); + zend_object_std_init((zend_object *) o, ce TSRMLS_CC); + object_properties_init((zend_object *) o, ce); + + php_http_request_pool_init(&o->pool TSRMLS_CC); + + ov.handle = zend_objects_store_put(o, NULL, php_http_request_pool_object_free, NULL TSRMLS_CC); + ov.handlers = &php_http_request_pool_object_handlers; + + return ov; +} + +void php_http_request_pool_object_free(void *object TSRMLS_DC) +{ + php_http_request_pool_object_t *o = (php_http_request_pool_object_t *) object; + + php_http_request_pool_dtor(&o->pool); + zend_object_std_dtor((zend_object *) o TSRMLS_CC); + efree(o); +} + +static void php_http_request_pool_object_llist2array(zval **req, zval *array TSRMLS_DC) +{ + Z_ADDREF_P(*req); + add_next_index_zval(array, *req); +} + +/* ### USERLAND ### */ + +PHP_METHOD(HttpRequestPool, __construct) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + int argc; + zval ***argv; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "*", &argv, &argc)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) { + int i; + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + for (i = 0; i < argc; ++i) { + if (Z_TYPE_PP(argv[i]) == IS_OBJECT && instanceof_function(Z_OBJCE_PP(argv[i]), php_http_request_class_entry TSRMLS_CC)) { + php_http_request_pool_attach(&obj->pool, *(argv[i])); + } + } + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequestPool, __destruct) +{ + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (SUCCESS != zend_parse_parameters_none()) { + ; /* we always want to clean up */ + } + + php_http_request_pool_detach_all(&obj->pool); +} + +PHP_METHOD(HttpRequestPool, reset) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + obj->iterator.pos = 0; + php_http_request_pool_detach_all(&obj->pool); + RETURN_TRUE; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, attach) +{ + RETVAL_FALSE; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + zval *request; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (obj->iterator.pos > 0 && obj->iterator.pos < zend_llist_count(&obj->pool.handles)) { + php_http_error(HE_THROW, PHP_HTTP_E_REQUEST_POOL, "Cannot attach to the HttpRequestPool while the iterator is active"); + } else { + RETVAL_SUCCESS(php_http_request_pool_attach(&obj->pool, request)); + } + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequestPool, detach) +{ + RETVAL_FALSE; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + zval *request; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "O", &request, php_http_request_class_entry)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + obj->iterator.pos = -1; + RETVAL_SUCCESS(php_http_request_pool_detach(&obj->pool, request)); + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequestPool, send) +{ + RETVAL_FALSE; + + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + if (SUCCESS == zend_parse_parameters_none()) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(request_pool)) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETVAL_SUCCESS(php_http_request_pool_send(&obj->pool)); + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpRequestPool, socketPerform) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (0 < php_http_request_pool_perform(&obj->pool)) { + RETURN_TRUE; + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, socketSelect) +{ + double timeout = 0; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|d", &timeout)) { + struct timeval timeout_val; + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + timeout_val.tv_sec = (time_t) timeout; + timeout_val.tv_usec = PHP_HTTP_USEC(timeout) % PHP_HTTP_MCROSEC; + + RETURN_SUCCESS(php_http_request_pool_select(&obj->pool, timeout ? &timeout_val : NULL)); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, valid) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_BOOL(obj->iterator.pos >= 0 && obj->iterator.pos < zend_llist_count(&obj->pool.handles)); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, current) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (obj->iterator.pos < zend_llist_count(&obj->pool.handles)) { + long pos = 0; + zval **current = NULL; + zend_llist_position lpos; + + for ( current = zend_llist_get_first_ex(&obj->pool.handles, &lpos); + current && obj->iterator.pos != pos++; + current = zend_llist_get_next_ex(&obj->pool.handles, &lpos)); + if (current) { + RETURN_OBJECT(*current, 1); + } + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, key) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_LONG(obj->iterator.pos); + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, next) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + ++obj->iterator.pos; + } +} + +PHP_METHOD(HttpRequestPool, rewind) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + obj->iterator.pos = 0; + } +} + +PHP_METHOD(HttpRequestPool, count) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + RETURN_LONG((long) zend_llist_count(&obj->pool.handles)); + } +} + +PHP_METHOD(HttpRequestPool, getAttachedRequests) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + array_init(return_value); + zend_llist_apply_with_argument(&obj->pool.handles, + (llist_apply_with_arg_func_t) php_http_request_pool_object_llist2array, + return_value TSRMLS_CC); + return; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, getFinishedRequests) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + array_init(return_value); + zend_llist_apply_with_argument(&obj->pool.finished, + (llist_apply_with_arg_func_t) php_http_request_pool_object_llist2array, + return_value TSRMLS_CC); + return; + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, enablePipelining) +{ + zend_bool enable = 1; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &enable)) { + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + if (CURLM_OK == curl_multi_setopt(obj->pool.ch, CURLMOPT_PIPELINING, (long) enable)) { + RETURN_TRUE; + } + } + RETURN_FALSE; +} + +PHP_METHOD(HttpRequestPool, enableEvents) +{ + zend_bool enable = 1; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b", &enable)) { +#if PHP_HTTP_HAVE_EVENT + php_http_request_pool_object_t *obj = zend_object_store_get_object(getThis() TSRMLS_CC); + + obj->pool.useevents = enable; + RETURN_TRUE; +#endif + } + RETURN_FALSE; +} + +PHP_MINIT_FUNCTION(http_request_pool) +{ + if (SUCCESS != php_http_persistent_handle_provide(ZEND_STRL("http_request_pool"), curl_multi_init, (php_http_persistent_handle_dtor_t) curl_multi_cleanup, NULL TSRMLS_CC)) { + return FAILURE; + } + + PHP_HTTP_REGISTER_CLASS(http\\request, Pool, http_request_pool, php_http_object_class_entry, 0); + php_http_request_pool_class_entry->create_object = php_http_request_pool_object_new; + memcpy(&php_http_request_pool_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); + php_http_request_pool_object_handlers.clone_obj = NULL; + + zend_class_implements(php_http_request_pool_class_entry TSRMLS_CC, 2, spl_ce_Countable, zend_ce_iterator); + + return SUCCESS; +} + +PHP_RINIT_FUNCTION(http_request_pool) +{ +#ifdef PHP_HTTP_HAVE_EVENT + if (!PHP_HTTP_G->request_pool.event_base && !(PHP_HTTP_G->request_pool.event_base = event_init())) { + return FAILURE; + } +#endif + + return SUCCESS; +} + + diff --git a/php_http_request_pool.h b/php_http_request_pool.h new file mode 100644 index 0000000..f7cedb6 --- /dev/null +++ b/php_http_request_pool.h @@ -0,0 +1,83 @@ + +#ifndef PHP_HTTP_REQUESTPOOL_H +#define PHP_HTTP_REQUESTPOOL_H + +struct php_http_request_pool_globals { + void *event_base; +}; + +typedef struct php_http_request_pool { + CURLM *ch; + zend_llist finished; + zend_llist handles; + int unfinished; /* int because of curl_multi_perform() */ +#ifdef ZTS + void ***ts; +#endif +#ifdef PHP_HTTP_HAVE_EVENT + struct event *timeout; + unsigned useevents:1; + unsigned runsocket:1; +#endif +} php_http_request_pool_t; + +typedef int (*php_http_request_pool_apply_func_t)(php_http_request_pool_t *pool, zval *request); +typedef int (*php_http_request_pool_apply_with_arg_func_t)(php_http_request_pool_t *pool, zval *request, void *arg); + +#ifdef PHP_HTTP_HAVE_EVENT +PHP_RINIT_FUNCTION(php_http_request_pool); +#endif + +extern struct timeval *php_http_request_pool_timeout(php_http_request_pool_t *pool, struct timeval *timeout); +extern void php_http_request_pool_responsehandler(php_http_request_pool_t *pool); +extern int php_http_request_pool_apply_responsehandler(php_http_request_pool_t *pool, zval *req, void *ch); + +PHP_HTTP_API php_http_request_pool_t *php_http_request_pool_init(php_http_request_pool_t *pool TSRMLS_DC); +PHP_HTTP_API STATUS php_http_request_pool_attach(php_http_request_pool_t *pool, zval *request); +PHP_HTTP_API STATUS php_http_request_pool_detach(php_http_request_pool_t *pool, zval *request); +PHP_HTTP_API void php_http_request_pool_apply(php_http_request_pool_t *pool, php_http_request_pool_apply_func_t cb); +PHP_HTTP_API void php_http_request_pool_apply_with_arg(php_http_request_pool_t *pool, php_http_request_pool_apply_with_arg_func_t cb, void *arg); +PHP_HTTP_API void php_http_request_pool_detach_all(php_http_request_pool_t *pool); +PHP_HTTP_API STATUS php_http_request_pool_send(php_http_request_pool_t *pool); +PHP_HTTP_API STATUS php_http_request_pool_select(php_http_request_pool_t *pool, struct timeval *custom_timeout); +PHP_HTTP_API int php_http_request_pool_perform(php_http_request_pool_t *pool); +PHP_HTTP_API void php_http_request_pool_dtor(php_http_request_pool_t *pool); +PHP_HTTP_API void php_http_request_pool_free(php_http_request_pool_t **pool); + +typedef struct php_http_request_pool_object { + zend_object zo; + php_http_request_pool_t pool; + struct { + long pos; + } iterator; +} php_http_request_pool_object_t; + +extern zend_class_entry *php_http_request_pool_class_entry; +extern zend_function_entry php_http_request_pool_method_entry[]; + +extern zend_object_value php_http_request_pool_object_new(zend_class_entry *ce TSRMLS_DC); +extern void php_http_request_pool_object_free(void *object TSRMLS_DC); + +PHP_METHOD(HttpRequestPool, __construct); +PHP_METHOD(HttpRequestPool, __destruct); +PHP_METHOD(HttpRequestPool, attach); +PHP_METHOD(HttpRequestPool, detach); +PHP_METHOD(HttpRequestPool, send); +PHP_METHOD(HttpRequestPool, reset); +PHP_METHOD(HttpRequestPool, socketPerform); +PHP_METHOD(HttpRequestPool, socketSelect); +PHP_METHOD(HttpRequestPool, valid); +PHP_METHOD(HttpRequestPool, current); +PHP_METHOD(HttpRequestPool, key); +PHP_METHOD(HttpRequestPool, next); +PHP_METHOD(HttpRequestPool, rewind); +PHP_METHOD(HttpRequestPool, count); +PHP_METHOD(HttpRequestPool, getAttachedRequests); +PHP_METHOD(HttpRequestPool, getFinishedRequests); +PHP_METHOD(HttpRequestPool, enablePipelining); +PHP_METHOD(HttpRequestPool, enableEvents); + +PHP_MINIT_FUNCTION(http_request_pool); +PHP_RINIT_FUNCTION(http_request_pool); + +#endif /* PHP_HTTP_REQUESTPOOL_H */ diff --git a/php_http_strlist.c b/php_http_strlist.c new file mode 100644 index 0000000..a00f201 --- /dev/null +++ b/php_http_strlist.c @@ -0,0 +1,80 @@ + +#include "php_http.h" + +PHP_HTTP_API php_http_strlist_iterator_t *php_http_strlist_iterator_init(php_http_strlist_iterator_t *iter, const char list[], unsigned factor) +{ + if (!iter) { + iter = emalloc(sizeof(*iter)); + } + memset(iter, 0, sizeof(*iter)); + + iter->p = &list[0]; + iter->factor = factor; + + return iter; +} + +PHP_HTTP_API const char *php_http_strlist_iterator_this(php_http_strlist_iterator_t *iter, unsigned *id) +{ + if (id) { + *id = iter->major * iter->factor + iter->minor; + } + + return iter->p; +} + +PHP_HTTP_API const char *php_http_strlist_iterator_next(php_http_strlist_iterator_t *iter) +{ + if (*iter->p) { + while (*iter->p) { + ++iter->p; + } + ++iter->p; + ++iter->minor; + + if (!*iter->p) { + ++iter->p; + ++iter->major; + } + } + + return iter->p; +} + +PHP_HTTP_API void php_http_strlist_iterator_dtor(php_http_strlist_iterator_t *iter) +{ + +} + +PHP_HTTP_API void php_http_strlist_iterator_free(php_http_strlist_iterator_t **iter) +{ + if (*iter) { + efree(*iter); + *iter = NULL; + } +} + +PHP_HTTP_API const char *php_http_strlist_find(const char list[], unsigned factor, unsigned item) +{ + unsigned M = 0, m = 0, major = (item / factor) - 1, minor = (item % factor); + const char *p = &list[0]; + + while (*p && major != M++) { + while (*p) { + while (*p) { + ++p; + } + ++p; + } + ++p; + } + + while (*p && minor != m++) { + while (*p) { + ++p; + } + ++p; + } + + return p; +} diff --git a/php_http_strlist.h b/php_http_strlist.h new file mode 100644 index 0000000..4bddbb2 --- /dev/null +++ b/php_http_strlist.h @@ -0,0 +1,27 @@ +#ifndef PHP_HTTP_STRLIST_H +#define PHP_HTTP_STRLIST_H + +#ifdef NUL +# undef NUL +#endif +#define NUL "\0" + +#define PHP_HTTP_STRLIST(name) const char name[] +#define PHP_HTTP_STRLIST_ITEM(item) item NUL +#define PHP_HTTP_STRLIST_NEXT NUL +#define PHP_HTTP_STRLIST_STOP NUL NUL + +PHP_HTTP_API const char *php_http_strlist_find(const char list[], unsigned factor, unsigned item); + +typedef struct php_http_strlist_iterator { + const char *p; + unsigned factor, major, minor; +} php_http_strlist_iterator_t; + +PHP_HTTP_API php_http_strlist_iterator_t *php_http_strlist_iterator_init(php_http_strlist_iterator_t *iter, const char list[], unsigned factor); +PHP_HTTP_API const char *php_http_strlist_iterator_this(php_http_strlist_iterator_t *iter, unsigned *id); +PHP_HTTP_API const char *php_http_strlist_iterator_next(php_http_strlist_iterator_t *iter); +PHP_HTTP_API void php_http_strlist_iterator_dtor(php_http_strlist_iterator_t *iter); +PHP_HTTP_API void php_http_strlist_iterator_free(php_http_strlist_iterator_t **iter); + +#endif /* PHP_HTTP_STRLIST_H */ diff --git a/php_http_url.c b/php_http_url.c new file mode 100644 index 0000000..8675fa6 --- /dev/null +++ b/php_http_url.c @@ -0,0 +1,578 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: http_url_api.c 292841 2009-12-31 08:48:57Z mike $ */ + +#include "php_http.h" + +static inline char *localhostname(void) +{ + char hostname[1024] = {0}; + +#ifdef PHP_WIN32 + if (SUCCESS == gethostname(hostname, lenof(hostname))) { + return estrdup(hostname); + } +#elif defined(HAVE_GETHOSTNAME) + if (SUCCESS == gethostname(hostname, lenof(hostname))) { +# if defined(HAVE_GETDOMAINNAME) + size_t hlen = strlen(hostname); + if (hlen <= lenof(hostname) - lenof("(none)")) { + hostname[hlen++] = '.'; + if (SUCCESS == getdomainname(&hostname[hlen], lenof(hostname) - hlen)) { + if (!strcmp(&hostname[hlen], "(none)")) { + hostname[hlen - 1] = '\0'; + } + return estrdup(hostname); + } + } +# endif + if (strcmp(hostname, "(none)")) { + return estrdup(hostname); + } + } +#endif + return estrndup("localhost", lenof("localhost")); +} + +PHP_HTTP_API char *php_http_url_absolute(const char *url, int flags TSRMLS_DC) +{ + char *abs = NULL; + php_url *purl = NULL; + + if (url) { + purl = php_url_parse(abs = estrdup(url)); + STR_SET(abs, NULL); + if (!purl) { + php_http_error(HE_WARNING, PHP_HTTP_E_URL, "Could not parse URL (%s)", url); + return NULL; + } + } + + php_http_url(flags, purl, NULL, NULL, &abs, NULL TSRMLS_CC); + + if (purl) { + php_url_free(purl); + } + + return abs; +} + +PHP_HTTP_API void php_http_url(int flags, const php_url *old_url, const php_url *new_url, php_url **url_ptr, char **url_str, size_t *url_len TSRMLS_DC) +{ +#if defined(HAVE_GETSERVBYPORT) || defined(HAVE_GETSERVBYNAME) + struct servent *se; +#endif + php_url *url = ecalloc(1, sizeof(php_url)); + +#define __URLSET(u,n) \ + ((u)&&(u)->n) +#define __URLCPY(n) \ + url->n = __URLSET(new_url,n) ? estrdup(new_url->n) : (__URLSET(old_url,n) ? estrdup(old_url->n) : NULL) + + if (!(flags & PHP_HTTP_URL_STRIP_PORT)) { + url->port = __URLSET(new_url, port) ? new_url->port : ((old_url) ? old_url->port : 0); + } + if (!(flags & PHP_HTTP_URL_STRIP_USER)) { + __URLCPY(user); + } + if (!(flags & PHP_HTTP_URL_STRIP_PASS)) { + __URLCPY(pass); + } + + __URLCPY(scheme); + __URLCPY(host); + + if (!(flags & PHP_HTTP_URL_STRIP_PATH)) { + if ((flags & PHP_HTTP_URL_JOIN_PATH) && __URLSET(old_url, path) && __URLSET(new_url, path) && *new_url->path != '/') { + size_t old_path_len = strlen(old_url->path), new_path_len = strlen(new_url->path); + + url->path = ecalloc(1, old_path_len + new_path_len + 1 + 1); + + strcat(url->path, old_url->path); + if (url->path[old_path_len - 1] != '/') { + php_dirname(url->path, old_path_len); + strcat(url->path, "/"); + } + strcat(url->path, new_url->path); + } else { + __URLCPY(path); + } + } + if (!(flags & PHP_HTTP_URL_STRIP_QUERY)) { + if ((flags & PHP_HTTP_URL_JOIN_QUERY) && __URLSET(new_url, query) && __URLSET(old_url, query)) { + zval qarr, qstr; + + INIT_PZVAL(&qstr); + INIT_PZVAL(&qarr); + array_init(&qarr); + + ZVAL_STRING(&qstr, old_url->query, 0); + php_http_querystring_modify(&qarr, &qstr TSRMLS_CC); + ZVAL_STRING(&qstr, new_url->query, 0); + php_http_querystring_modify(&qarr, &qstr TSRMLS_CC); + + ZVAL_NULL(&qstr); + php_http_querystring_update(&qarr, &qstr TSRMLS_CC); + url->query = Z_STRVAL(qstr); + zval_dtor(&qarr); + } else { + __URLCPY(query); + } + } + if (!(flags & PHP_HTTP_URL_STRIP_FRAGMENT)) { + __URLCPY(fragment); + } + + if (!url->scheme) { + if (flags & PHP_HTTP_URL_FROM_ENV) { + zval *https = php_http_env_get_server_var(ZEND_STRL("HTTPS"), 1 TSRMLS_CC); + if (https && !strcasecmp(Z_STRVAL_P(https), "ON")) { + url->scheme = estrndup("https", lenof("https")); + } else switch (url->port) { + case 443: + url->scheme = estrndup("https", lenof("https")); + break; + +#ifndef HAVE_GETSERVBYPORT + default: +#endif + case 80: + case 0: + url->scheme = estrndup("http", lenof("http")); + break; + +#ifdef HAVE_GETSERVBYPORT + default: + if ((se = getservbyport(htons(url->port), "tcp")) && se->s_name) { + url->scheme = estrdup(se->s_name); + } else { + url->scheme = estrndup("http", lenof("http")); + } + break; +#endif + } + } else { + url->scheme = estrndup("http", lenof("http")); + } + } + + if (!url->host) { + if (flags & PHP_HTTP_URL_FROM_ENV) { + zval *zhost; + + if ((((zhost = php_http_env_get_server_var(ZEND_STRL("HTTP_HOST"), 1 TSRMLS_CC)) || + (zhost = php_http_env_get_server_var(ZEND_STRL("SERVER_NAME"), 1 TSRMLS_CC)))) && Z_STRLEN_P(zhost)) { + url->host = estrndup(Z_STRVAL_P(zhost), Z_STRLEN_P(zhost)); + } else { + url->host = localhostname(); + } + } else { + url->host = estrndup("localhost", lenof("localhost")); + } + } + + if (!url->path) { + if ((flags & PHP_HTTP_URL_FROM_ENV) && SG(request_info).request_uri && SG(request_info).request_uri[0]) { + const char *q = strchr(SG(request_info).request_uri, '?'); + + if (q) { + url->path = estrndup(SG(request_info).request_uri, q - SG(request_info).request_uri); + } else { + url->path = estrdup(SG(request_info).request_uri); + } + } else { + url->path = estrndup("/", 1); + } + } else if (url->path[0] != '/') { + if ((flags & PHP_HTTP_URL_FROM_ENV) && SG(request_info).request_uri && SG(request_info).request_uri[0]) { + size_t ulen = strlen(SG(request_info).request_uri); + size_t plen = strlen(url->path); + char *path; + + if (SG(request_info).request_uri[ulen-1] != '/') { + for (--ulen; ulen && SG(request_info).request_uri[ulen - 1] != '/'; --ulen); + } + + path = emalloc(ulen + plen + 1); + memcpy(path, SG(request_info).request_uri, ulen); + memcpy(path + ulen, url->path, plen); + path[ulen + plen] = '\0'; + STR_SET(url->path, path); + } else { + size_t plen = strlen(url->path); + char *path = emalloc(plen + 1 + 1); + + path[0] = '/'; + memcpy(&path[1], url->path, plen + 1); + STR_SET(url->path, path); + } + } + /* replace directory references if path is not a single slash */ + if (url->path[0] && (url->path[0] != '/' || url->path[1])) { + char *ptr, *end = url->path + strlen(url->path) + 1; + + for (ptr = strstr(url->path, "/."); ptr; ptr = strstr(ptr, "/.")) { + switch (ptr[2]) { + case '\0': + ptr[1] = '\0'; + break; + + case '/': + memmove(&ptr[1], &ptr[3], end - &ptr[3]); + break; + + case '.': + if (ptr[3] == '/') { + char *pos = &ptr[4]; + while (ptr != url->path) { + if (*--ptr == '/') { + break; + } + } + memmove(&ptr[1], pos, end - pos); + break; + } else if (!ptr[3]) { + /* .. at the end */ + ptr[1] = '\0'; + } + /* fallthrough */ + + default: + /* something else */ + ++ptr; + break; + } + } + } + + if (url->port) { + if ( ((url->port == 80) && !strcmp(url->scheme, "http")) + || ((url->port ==443) && !strcmp(url->scheme, "https")) +#ifdef HAVE_GETSERVBYNAME + || ((se = getservbyname(url->scheme, "tcp")) && se->s_port && + (url->port == ntohs(se->s_port))) +#endif + ) { + url->port = 0; + } + } + + if (url_str) { + size_t len; + + *url_str = emalloc(PHP_HTTP_URL_MAXLEN + 1); + + **url_str = '\0'; + strlcat(*url_str, url->scheme, PHP_HTTP_URL_MAXLEN); + strlcat(*url_str, "://", PHP_HTTP_URL_MAXLEN); + + if (url->user && *url->user) { + strlcat(*url_str, url->user, PHP_HTTP_URL_MAXLEN); + if (url->pass && *url->pass) { + strlcat(*url_str, ":", PHP_HTTP_URL_MAXLEN); + strlcat(*url_str, url->pass, PHP_HTTP_URL_MAXLEN); + } + strlcat(*url_str, "@", PHP_HTTP_URL_MAXLEN); + } + + strlcat(*url_str, url->host, PHP_HTTP_URL_MAXLEN); + + if (url->port) { + char port_str[8]; + + snprintf(port_str, sizeof(port_str), "%d", (int) url->port); + strlcat(*url_str, ":", PHP_HTTP_URL_MAXLEN); + strlcat(*url_str, port_str, PHP_HTTP_URL_MAXLEN); + } + + strlcat(*url_str, url->path, PHP_HTTP_URL_MAXLEN); + + if (url->query && *url->query) { + strlcat(*url_str, "?", PHP_HTTP_URL_MAXLEN); + strlcat(*url_str, url->query, PHP_HTTP_URL_MAXLEN); + } + + if (url->fragment && *url->fragment) { + strlcat(*url_str, "#", PHP_HTTP_URL_MAXLEN); + strlcat(*url_str, url->fragment, PHP_HTTP_URL_MAXLEN); + } + + if (PHP_HTTP_URL_MAXLEN == (len = strlen(*url_str))) { + php_http_error(HE_NOTICE, PHP_HTTP_E_URL, "Length of URL exceeds PHP_HTTP_URL_MAXLEN"); + } + if (url_len) { + *url_len = len; + } + } + + if (url_ptr) { + *url_ptr = url; + } else { + php_url_free(url); + } +} + +PHP_HTTP_API STATUS php_http_url_encode_hash(HashTable *hash, zend_bool override_argsep, char *pre_encoded_data, size_t pre_encoded_len, char **encoded_data, size_t *encoded_len TSRMLS_DC) +{ + char *arg_sep; + size_t arg_sep_len; + php_http_buffer *qstr = php_http_buffer_new(); + + if (override_argsep || !(arg_sep_len = strlen(arg_sep = INI_STR("arg_separator.output")))) { + arg_sep = PHP_HTTP_URL_ARGSEP; + arg_sep_len = lenof(PHP_HTTP_URL_ARGSEP); + } + + if (pre_encoded_len && pre_encoded_data) { + php_http_buffer_append(qstr, pre_encoded_data, pre_encoded_len); + } + + if (SUCCESS != php_http_url_encode_hash_recursive(hash, qstr, arg_sep, arg_sep_len, NULL, 0)) { + php_http_buffer_free(&qstr); + return FAILURE; + } + + php_http_buffer_data(qstr, encoded_data, encoded_len); + php_http_buffer_free(&qstr); + + return SUCCESS; +} + +PHP_HTTP_API STATUS php_http_url_encode_hash_recursive(HashTable *ht, php_http_buffer *str, const char *arg_sep, size_t arg_sep_len, const char *prefix, size_t prefix_len TSRMLS_DC) +{ + php_http_array_hashkey_t key = php_http_array_hashkey_init(0); + zval **data = NULL; + HashPosition pos; + + if (!ht || !str) { + php_http_error(HE_WARNING, PHP_HTTP_E_INVALID_PARAM, "Invalid parameters"); + return FAILURE; + } + if (ht->nApplyCount > 0) { + return SUCCESS; + } + + FOREACH_HASH_KEYVAL(pos, ht, key, data) { + char *encoded_key; + int encoded_len; + php_http_buffer new_prefix; + + if (!data || !*data) { + php_http_buffer_dtor(str); + return FAILURE; + } + + if (key.type == HASH_KEY_IS_STRING) { + if (!*key.str) { + /* only public properties */ + continue; + } + if (key.len && key.str[key.len - 1] == '\0') { + --key.len; + } + encoded_key = php_url_encode(key.str, key.len, &encoded_len); + } else { + encoded_len = spprintf(&encoded_key, 0, "%ld", key.num); + } + + { + php_http_buffer_init(&new_prefix); + if (prefix && prefix_len) { + php_http_buffer_append(&new_prefix, prefix, prefix_len); + php_http_buffer_appends(&new_prefix, "%5B"); + } + + php_http_buffer_append(&new_prefix, encoded_key, encoded_len); + efree(encoded_key); + + if (prefix && prefix_len) { + php_http_buffer_appends(&new_prefix, "%5D"); + } + php_http_buffer_fix(&new_prefix); + } + + if (Z_TYPE_PP(data) == IS_ARRAY || Z_TYPE_PP(data) == IS_OBJECT) { + STATUS status; + ++ht->nApplyCount; + status = php_http_url_encode_hash_recursive(HASH_OF(*data), str, arg_sep, arg_sep_len, PHP_HTTP_BUFFER_VAL(&new_prefix), PHP_HTTP_BUFFER_LEN(&new_prefix) TSRMLS_CC); + --ht->nApplyCount; + if (SUCCESS != status) { + php_http_buffer_dtor(&new_prefix); + php_http_buffer_dtor(str); + return FAILURE; + } + } else { + zval *val = php_http_zsep(IS_STRING, *data); + + if (PHP_HTTP_BUFFER_LEN(str)) { + php_http_buffer_append(str, arg_sep, arg_sep_len); + } + php_http_buffer_append(str, PHP_HTTP_BUFFER_VAL(&new_prefix), PHP_HTTP_BUFFER_LEN(&new_prefix)); + php_http_buffer_appends(str, "="); + + if (Z_STRLEN_P(val) && Z_STRVAL_P(val)) { + char *encoded_val; + int encoded_len; + + encoded_val = php_url_encode(Z_STRVAL_P(val), Z_STRLEN_P(val), &encoded_len); + php_http_buffer_append(str, encoded_val, encoded_len); + efree(encoded_val); + } + + zval_ptr_dtor(&val); + } + php_http_buffer_dtor(&new_prefix); + } + return SUCCESS; +} + +#define PHP_HTTP_BEGIN_ARGS(method, req_args) PHP_HTTP_BEGIN_ARGS_EX(HttpUrl, method, 0, req_args) +#define PHP_HTTP_EMPTY_ARGS(method) PHP_HTTP_EMPTY_ARGS_EX(HttpUrl, method, 0) +#define PHP_HTTP_URL_ME(method, visibility) PHP_ME(HttpUrl, method, PHP_HTTP_ARGS(HttpUrl, method), visibility) + +PHP_HTTP_BEGIN_ARGS(__construct, 0) + PHP_HTTP_ARG_VAL(old_url, 0) + PHP_HTTP_ARG_VAL(new_url, 0) + PHP_HTTP_ARG_VAL(flags, 0) +PHP_HTTP_END_ARGS; +PHP_HTTP_EMPTY_ARGS(toString); + +zend_class_entry *php_http_url_class_entry; +zend_function_entry php_http_url_method_entry[] = { + PHP_HTTP_URL_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) + PHP_HTTP_URL_ME(toString, ZEND_ACC_PUBLIC) + ZEND_MALIAS(HttpUrl, __toString, toString, PHP_HTTP_ARGS(HttpUrl, toString), ZEND_ACC_PUBLIC) + EMPTY_FUNCTION_ENTRY +}; + +PHP_METHOD(HttpUrl, __construct) +{ + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(runtime)) { + zval *new_url = NULL, *old_url = NULL; + long flags = PHP_HTTP_URL_FROM_ENV; + + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|z!z!l", &old_url, &new_url, &flags)) { + with_error_handling(EH_THROW, PHP_HTTP_EX_CE(url)) { + php_url *res_purl, *new_purl = NULL, *old_purl = NULL; + + if (new_url) { + switch (Z_TYPE_P(new_url)) { + case IS_OBJECT: + case IS_ARRAY: + new_purl = php_http_url_from_struct(NULL, HASH_OF(new_url) TSRMLS_CC); + break; + default: { + zval *cpy = php_http_zsep(IS_STRING, new_url); + + new_purl = php_url_parse(Z_STRVAL_P(new_url)); + zval_ptr_dtor(&cpy); + break; + } + } + if (!new_purl) { + return; + } + } + if (old_url) { + switch (Z_TYPE_P(old_url)) { + case IS_OBJECT: + case IS_ARRAY: + old_purl = php_http_url_from_struct(NULL, HASH_OF(old_url) TSRMLS_CC); + break; + default: { + zval *cpy = php_http_zsep(IS_STRING, old_url); + + old_purl = php_url_parse(Z_STRVAL_P(old_url)); + zval_ptr_dtor(&cpy); + break; + } + } + if (!old_purl) { + if (new_purl) { + php_url_free(new_purl); + } + return; + } + } + + php_http_url(flags, old_purl, new_purl, &res_purl, NULL, NULL TSRMLS_CC); + php_http_url_to_struct(res_purl, getThis() TSRMLS_CC); + + php_url_free(res_purl); + if (old_purl) { + php_url_free(old_purl); + } + if (new_purl) { + php_url_free(new_purl); + } + } end_error_handling(); + } + } end_error_handling(); +} + +PHP_METHOD(HttpUrl, toString) +{ + if (SUCCESS == zend_parse_parameters_none()) { + php_url *purl; + + if ((purl = php_http_url_from_struct(NULL, HASH_OF(getThis()) TSRMLS_CC))) { + char *str; + size_t len; + + php_http_url(0, purl, NULL, NULL, &str, &len TSRMLS_CC); + php_url_free(purl); + RETURN_STRINGL(str, len, 0); + } + } + RETURN_EMPTY_STRING(); +} + +PHP_MINIT_FUNCTION(http_url) +{ + PHP_HTTP_REGISTER_CLASS(http, Url, http_url, php_http_object_class_entry, 0); + + zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("scheme"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("user"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("pass"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("host"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("port"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("path"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("query"), ZEND_ACC_PUBLIC TSRMLS_CC); + zend_declare_property_null(php_http_url_class_entry, ZEND_STRL("fragment"), ZEND_ACC_PUBLIC TSRMLS_CC); + + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("REPLACE"), PHP_HTTP_URL_REPLACE TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_PATH"), PHP_HTTP_URL_JOIN_PATH TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("JOIN_QUERY"), PHP_HTTP_URL_JOIN_QUERY TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_USER"), PHP_HTTP_URL_STRIP_USER TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PASS"), PHP_HTTP_URL_STRIP_PASS TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_AUTH"), PHP_HTTP_URL_STRIP_AUTH TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PORT"), PHP_HTTP_URL_STRIP_PORT TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_PATH"), PHP_HTTP_URL_STRIP_PATH TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_QUERY"), PHP_HTTP_URL_STRIP_QUERY TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_FRAGMENT"), PHP_HTTP_URL_STRIP_FRAGMENT TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("STRIP_ALL"), PHP_HTTP_URL_STRIP_ALL TSRMLS_CC); + zend_declare_class_constant_long(php_http_url_class_entry, ZEND_STRL("FROM_ENV"), PHP_HTTP_URL_FROM_ENV TSRMLS_CC); + + return SUCCESS; +} + + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_url.h b/php_http_url.h new file mode 100644 index 0000000..bbb6177 --- /dev/null +++ b/php_http_url.h @@ -0,0 +1,165 @@ +/* + +--------------------------------------------------------------------+ + | PECL :: http | + +--------------------------------------------------------------------+ + | Redistribution and use in source and binary forms, with or without | + | modification, are permitted provided that the conditions mentioned | + | in the accompanying LICENSE file are met. | + +--------------------------------------------------------------------+ + | Copyright (c) 2004-2010, Michael Wallner | + +--------------------------------------------------------------------+ +*/ + +/* $Id: php_http_url_api.h 292841 2009-12-31 08:48:57Z mike $ */ + +#ifndef PHP_HTTP_URL_H +#define PHP_HTTP_URL_H + +#define PHP_HTTP_URL_REPLACE 0x000 +#define PHP_HTTP_URL_JOIN_PATH 0x001 +#define PHP_HTTP_URL_JOIN_QUERY 0x002 +#define PHP_HTTP_URL_STRIP_USER 0x004 +#define PHP_HTTP_URL_STRIP_PASS 0x008 +#define PHP_HTTP_URL_STRIP_AUTH (PHP_HTTP_URL_STRIP_USER|PHP_HTTP_URL_STRIP_PASS) +#define PHP_HTTP_URL_STRIP_PORT 0x020 +#define PHP_HTTP_URL_STRIP_PATH 0x040 +#define PHP_HTTP_URL_STRIP_QUERY 0x080 +#define PHP_HTTP_URL_STRIP_FRAGMENT 0x100 +#define PHP_HTTP_URL_STRIP_ALL ( \ + PHP_HTTP_URL_STRIP_AUTH | \ + PHP_HTTP_URL_STRIP_PORT | \ + PHP_HTTP_URL_STRIP_PATH | \ + PHP_HTTP_URL_STRIP_QUERY | \ + PHP_HTTP_URL_STRIP_FRAGMENT \ +) +#define PHP_HTTP_URL_FROM_ENV 0x1000 + +PHP_HTTP_API void php_http_url(int flags, const php_url *old_url, const php_url *new_url, php_url **url_ptr, char **url_str, size_t *url_len TSRMLS_DC); +PHP_HTTP_API char *php_http_url_absolute(const char *url, int flags TSRMLS_DC); + +PHP_HTTP_API STATUS php_http_url_encode_hash(HashTable *hash, zend_bool override_argsep, char *pre_encoded_data, size_t pre_encoded_len, char **encoded_data, size_t *encoded_len TSRMLS_DC); +PHP_HTTP_API STATUS php_http_url_encode_hash_recursive(HashTable *ht, php_http_buffer *str, const char *arg_sep, size_t arg_sep_len, const char *prefix, size_t prefix_len TSRMLS_DC); + +static inline php_url *php_http_url_from_struct(php_url *url, HashTable *ht TSRMLS_DC) +{ + zval **e; + + if (!url) { + url = emalloc(sizeof(*url)); + } + memset(url, 0, sizeof(*url)); + + if (SUCCESS == zend_hash_find(ht, "scheme", sizeof("scheme"), (void *) &e)) { + zval *cpy = php_http_zsep(IS_STRING, *e); + url->scheme = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)); + zval_ptr_dtor(&cpy); + } + if (SUCCESS == zend_hash_find(ht, "user", sizeof("user"), (void *) &e)) { + zval *cpy = php_http_zsep(IS_STRING, *e); + url->user = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)); + zval_ptr_dtor(&cpy); + } + if (SUCCESS == zend_hash_find(ht, "pass", sizeof("pass"), (void *) &e)) { + zval *cpy = php_http_zsep(IS_STRING, *e); + url->pass = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)); + zval_ptr_dtor(&cpy); + } + if (SUCCESS == zend_hash_find(ht, "host", sizeof("host"), (void *) &e)) { + zval *cpy = php_http_zsep(IS_STRING, *e); + url->host = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)); + zval_ptr_dtor(&cpy); + } + if (SUCCESS == zend_hash_find(ht, "path", sizeof("path"), (void *) &e)) { + zval *cpy = php_http_zsep(IS_STRING, *e); + url->path = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)); + zval_ptr_dtor(&cpy); + } + if (SUCCESS == zend_hash_find(ht, "query", sizeof("query"), (void *) &e)) { + zval *cpy = php_http_zsep(IS_STRING, *e); + url->query = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)); + zval_ptr_dtor(&cpy); + } + if (SUCCESS == zend_hash_find(ht, "fragment", sizeof("fragment"), (void *) &e)) { + zval *cpy = php_http_zsep(IS_STRING, *e); + url->fragment = estrndup(Z_STRVAL_P(cpy), Z_STRLEN_P(cpy)); + zval_ptr_dtor(&cpy); + } + if (SUCCESS == zend_hash_find(ht, "port", sizeof("port"), (void *) &e)) { + zval *cpy = php_http_zsep(IS_LONG, *e); + url->port = (unsigned short) Z_LVAL_P(cpy); + zval_ptr_dtor(&cpy); + } + + return url; +} + +static inline HashTable *php_http_url_to_struct(php_url *url, zval *strct TSRMLS_DC) +{ + zval arr; + + if (strct) { + switch (Z_TYPE_P(strct)) { + default: + zval_dtor(strct); + array_init(strct); + case IS_ARRAY: + case IS_OBJECT: + INIT_PZVAL_ARRAY((&arr), HASH_OF(strct)); + } + } else { + INIT_PZVAL(&arr); + array_init(&arr); + } + + if (url) { + if (url->scheme) { + add_assoc_string(&arr, "scheme", url->scheme, 1); + } + if (url->user) { + add_assoc_string(&arr, "user", url->user, 1); + } + if (url->pass) { + add_assoc_string(&arr, "pass", url->pass, 1); + } + if (url->host) { + add_assoc_string(&arr, "host", url->host, 1); + } + if (url->port) { + add_assoc_long(&arr, "port", (long) url->port); + } + if (url->path) { + add_assoc_string(&arr, "path", url->path, 1); + } + if (url->query) { + add_assoc_string(&arr, "query", url->query, 1); + } + if (url->fragment) { + add_assoc_string(&arr, "fragment", url->fragment, 1); + } + } + + return Z_ARRVAL(arr); +} + +extern zend_class_entry *php_http_url_class_entry; +extern zend_function_entry php_http_url_method_entry[]; + +#define php_http_url_object_new php_http_object_new +#define php_http_url_object_new_ex php_http_object_new_ex + +PHP_METHOD(HttpUrl, __construct); +PHP_METHOD(HttpUrl, toString); + +extern PHP_MINIT_FUNCTION(http_url); + +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/php_http_version.c b/php_http_version.c new file mode 100644 index 0000000..8c1637c --- /dev/null +++ b/php_http_version.c @@ -0,0 +1,59 @@ +#include "php_http.h" + +PHP_HTTP_API php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major, unsigned minor TSRMLS_DC) +{ + if (!v) { + v = emalloc(sizeof(*v)); + } + + v->major = major; + v->minor = minor; + + return v; +} + +PHP_HTTP_API php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str TSRMLS_DC) +{ + php_http_version_t tmp; + char separator = 0; + + if (3 != sscanf(str, "HTTP/%u%c%u", &tmp.major, &separator, &tmp.minor) + && 3 != sscanf(str, "%u%c%u", &tmp.major, &separator, &tmp.minor)) { + php_http_error(HE_WARNING, PHP_HTTP_E_MALFORMED_HEADERS, "Could not parse HTTP protocol version '%s'", str); + return NULL; + } + + if (separator && separator != '.' && separator != ',') { + php_http_error(HE_NOTICE, PHP_HTTP_E_MALFORMED_HEADERS, "Non-standard version separator '%c' in HTTP protocol version '%s'", separator, str); + } + + return php_http_version_init(v, tmp.major, tmp.minor TSRMLS_CC); +} + +PHP_HTTP_API void php_http_version_to_string(php_http_version_t *v, char **str, size_t *len, const char *pre, const char *post TSRMLS_DC) +{ + *len = spprintf(str, 0, "%s%u.%u%s", pre ? pre : "", v->major, v->minor, post ? post : ""); +} + +PHP_HTTP_API void php_http_version_to_struct(php_http_version_t *v, HashTable *strct TSRMLS_DC) +{ + zval tmp; + + INIT_PZVAL_ARRAY(&tmp, strct); + add_assoc_long(&tmp, "major", v->major); + add_assoc_long(&tmp, "minor", v->minor); +} + +PHP_HTTP_API void php_http_version_dtor(php_http_version_t *v) +{ + (void) v; +} + +PHP_HTTP_API void php_http_version_free(php_http_version_t **v) +{ + if (*v) { + php_http_version_dtor(*v); + efree(*v); + *v = NULL; + } +} diff --git a/php_http_version.h b/php_http_version.h new file mode 100644 index 0000000..6f02ad7 --- /dev/null +++ b/php_http_version.h @@ -0,0 +1,18 @@ + +#ifndef PHP_HTTP_VERSION_H +#define PHP_HTTP_VERSION_H + +typedef struct php_http_version { + unsigned major; + unsigned minor; +} php_http_version_t; + +PHP_HTTP_API php_http_version_t *php_http_version_init(php_http_version_t *v, unsigned major, unsigned minor TSRMLS_DC); +PHP_HTTP_API php_http_version_t *php_http_version_parse(php_http_version_t *v, const char *str TSRMLS_DC); +PHP_HTTP_API void php_http_version_to_string(php_http_version_t *v, char **str, size_t *len, const char *pre, const char *post TSRMLS_DC); +PHP_HTTP_API void php_http_version_to_struct(php_http_version_t *v, HashTable *strct TSRMLS_DC); +PHP_HTTP_API void php_http_version_dtor(php_http_version_t *v); +PHP_HTTP_API void php_http_version_free(php_http_version_t **v); + +#endif /* PHP_HTTP_VERSION_H */ + -- 2.30.2