[FEATURE] Include HTTP Request2 for better HTTP handling
authorPhilipp Gampe <forge.typo3.org@philippgampe.info>
Fri, 8 Jul 2011 08:12:01 +0000 (10:12 +0200)
committerSteffen Gebert <steffen.gebert@typo3.org>
Sun, 31 Jul 2011 18:12:46 +0000 (20:12 +0200)
Add the pear packages HTTP/Request2 end Net/URL2.
Also add PEAR/Exception.php.

Add t3lib_http_Request as wrapper for Request2.
This sets the default options from TYPO3_CONF_VARS.

Additionally add new TYPO3_CONF_VARS options and deprecate the old
cURL options.

Extend include_path that it also works with pear packages.

Change-Id: I430685159cc966e13f1e833ca19ee8f0c75e400a
Resolves: #28344
Releases: 4.5,4.6
Reviewed-on: http://review.typo3.org/3437
Reviewed-by: Markus Klein
Reviewed-by: Jigal van Hemert
Tested-by: Jigal van Hemert
Reviewed-by: Stefan Neufeind
Reviewed-by: Steffen Gebert
Tested-by: Steffen Gebert
16 files changed:
t3lib/config_default.php
t3lib/core_autoload.php
t3lib/http/class.t3lib_http_request.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/Adapter.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/Adapter/Curl.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/Adapter/Mock.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/Adapter/Socket.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/CookieJar.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/Exception.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/MultipartBody.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/Observer/Log.php [new file with mode: 0644]
typo3/contrib/pear/HTTP/Request2/Response.php [new file with mode: 0644]
typo3/contrib/pear/Net/URL2.php [new file with mode: 0644]
typo3/contrib/pear/PEAR/Exception.php [new file with mode: 0644]
typo3/init.php

index cfc3970..598ae48 100644 (file)
@@ -88,10 +88,14 @@ $TYPO3_CONF_VARS = array(
                'loginCopyrightWarrantyURL' => '',              // String: Add the URL where you explain the extend of the warranty you provide. This URL is displayed in the login dialog as the place where people can learn more about the conditions of your warranty. Must be set (more than 10 chars) in addition with the 'loginCopyrightWarrantyProvider' message.
                'loginCopyrightShowVersion' => FALSE,   // Boolean: If set, the current TYPO3 version is shown.
                'curlUse' => FALSE,                                             // Boolean: If set, try to use cURL to fetch external URLs
-               'curlProxyServer' => '',                                // String: Proxyserver as http://proxy:port/.
-               'curlProxyTunnel' => FALSE,                             // Boolean: If set, use a tunneled connection through the proxy (usefull for websense etc.).
-               'curlProxyUserPass' => '',                              // String: Proxyserver authentication user:pass.
-               'curlTimeout' => 0,                                             // Integer: Timeout value for cURL requests in seconds. 0 means to wait indefinitely.
+               /** @deprecated Deprecated since 4.6 - will be removed in 4.8. */
+               'curlProxyServer' => '',                                // String: Proxyserver as http://proxy:port/. Deprecated since 4.6 - will be removed in 4.8. See below for http options.
+               /** @deprecated Deprecated since 4.6 - will be removed in 4.8. */
+               'curlProxyTunnel' => FALSE,                             // Boolean: If set, use a tunneled connection through the proxy (usefull for websense etc.). Deprecated since 4.6 - will be removed in 4.8. See below for http options.
+               /** @deprecated Deprecated since 4.6 - will be removed in 4.8. */
+               'curlProxyUserPass' => '',                              // String: Proxyserver authentication user:pass. Deprecated since 4.6 - will be removed in 4.8. See below for http options.
+               /** @deprecated Deprecated since 4.6 - will be removed in 4.8. */
+               'curlTimeout' => 0,                                             // Integer: Timeout value for cURL requests in seconds. 0 means to wait indefinitely. Deprecated since 4.6 - will be removed in 4.8. See below for http options.
                'form_enctype' => 'multipart/form-data',        // String: This is the default form encoding type for most forms in TYPO3. It allows for file uploads to be in the form. However if file-upload is disabled for your PHP version even ordinary data sent with this encryption will not get to the server. So if you have file_upload disabled, you will have to change this to eg. 'application/x-www-form-urlencoded'
                'textfile_ext' => 'txt,html,htm,css,tmpl,js,sql,xml,csv,' . PHP_EXTENSIONS_DEFAULT,     // Text file extensions. Those that can be edited. Executable PHP files may not be editable in webspace if disallowed!
                'contentTable' => '',                                   // This is the page-content table (Normally 'tt_content')
@@ -585,6 +589,27 @@ $TYPO3_CONF_VARS = array(
                'defaultMailFromAddress' => '',                 // String: This default email address is used when no other "from" address is set for a TYPO3-generated email. You can specify an email address only (ex. info@example.org).
                'defaultMailFromName' => '',                    // String: This default name is used when no other "from" name is set for a TYPO3-generated email.
        ),
+       'HTTP' => array(                                                // HTTP configuration to tune how TYPO3 behaves on HTTP request. Have a look at <a href="http://pear.php.net/manual/en/package.http.http-request2.config.php>HTTP_Request2 Manual</a> for some background information on those settings.
+               'adapter' => 'socket',                                  // String: Default adapter - either "socket" or "curl".
+               'connect_timeout' => 10,                                // Integer: Default timeout for connection. Exception will be thrown if connecting to remote host takes more than this number of seconds.
+               'timeout' => 0,                                                 // Integer: Default timeout for whole request. Exception will be thrown if sending the request takes more than this number of seconds. Should be greater than connection timeout (see above) or "0" to not set a limit. Defaults to "0".
+               'protocol_version' => '1.1',                    // String: Default HTTP protocol version. Use either "1.0" or "1.1".
+               'follow_redirects' => FALSE,                    // Boolean: If set, redirects are followed by default. If number of tries are exceeded, an exception is thrown.
+               'max_redirects' => 5,                                   // Integer: Maximum number of tries before an exception is thrown.
+               'strict_redirects' => FALSE,                    // Boolean: Whether to keep request method on redirects via status 301 and 302 (TRUE, needed for compatibility with <a href="http://www.faqs.org/rfcs/rfc2616">RFC 2616</a>) or switch to GET (FALSE, needed for compatibility with most browsers). There are some <a href="http://pear.php.net/manual/en/package.http.http-request2.adapters.php#package.http.http-request2.adapters.curl">issues with cURL adapter</a>. Defaults to FALSE.
+               'proxy_host' => '',                                             // String: Default proxy server as "http://proxy.example.org" (You must not set the port here. Set the port below.)
+               'proxy_port' => '',                                             // Integer: Default proxy server port.
+               'proxy_user' => '',                                             // String: Default user name.
+               'proxy_password' => '',                                         // String: Default password.
+               'proxy_auth_scheme' => 'basic',                                 // String: Default authentication method. Can either be "basic" or "digest". Defaults to "basic".
+               'ssl_verify_peer' => TRUE,                              // Boolean: Whether to verify peer's SSL certificate. Note that this is on by default, to follow the behaviour of modern browsers and current cURL version. There are some <a href="http://pear.php.net/manual/en/package.http.http-request2.adapters.php#package.http.http-request2.adapters.socket">issues with Socket Adapter</a>.
+               'ssl_verify_host' => TRUE,                              // Boolean: Whether to check that Common Name in SSL certificate matches host name. There are some <a href="http://pear.php.net/manual/en/package.http.http-request2.adapters.php#package.http.http-request2.adapters.socket">issues with Socket Adapter</a>.
+               'ssl_cafile' => '',                                             // String: Certificate Authority file to verify the peer with (use when ssl_verify_peer is TRUE).
+               'ssl_capath' => '',                                             // String: Directory holding multiple Certificate Authority files.
+               'ssl_local_cert' => '',                                 // String: Name of a file containing local certificate.
+               'ssl_passphrase' => '',                                 // String: Passphrase with which local certificate was encoded.
+               'userAgent' => '',                                              // String: Default user agent. If empty, this will be "TYPO3/4.x", while x is the current branch version. This overrides the constant <em>TYPO3_user_agent</em>.
+       ),
        'MODS' => array(                // Backend Module Configuration (obsolete, make extension instead)
        ),
        'USER' => array(                // Here you may define your own setup-vars for use in your include-scripts. (obsolete, make extension instead)
@@ -687,7 +712,13 @@ $TYPO_VERSION = '4.6-dev'; // @deprecated: Will be removed in TYPO3 4.8. Use the
 define('TYPO3_version', $TYPO_VERSION);
 define('TYPO3_branch', '4.6');
 define('TYPO3_copyright_year', '1998-2011');
-define('TYPO3_user_agent', 'User-Agent: TYPO3/'.TYPO3_version);
+
+       // Handle $GLOBALS['TYPO3_CONF_VARS']['HTTP']['userAgent']. We can not set the default above
+       // because TYPO3_version is not yet defined.
+if (empty($GLOBALS['TYPO3_CONF_VARS']['HTTP']['userAgent'])) {
+       $GLOBALS['TYPO3_CONF_VARS']['HTTP']['userAgent'] = 'TYPO3/' . TYPO3_version;
+}
+define('TYPO3_user_agent', 'User-Agent: '. $GLOBALS['TYPO3_CONF_VARS']['HTTP']['userAgent']);
 
 // Database-variables are cleared!
 $typo_db = '';                                 // The database name
@@ -817,6 +848,40 @@ define('TYPO3_UseCachingFramework', TRUE);
 $GLOBALS['TYPO3_CONF_VARS']['SYS']['useCachingFramework'] = TRUE;
 
 
+/**
+ * Parse old curl options and set new http ones instead
+ *
+ * @deprecated Deprecated since 4.6 - will be removed in 4.8.
+ */
+if (!empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer'])) {
+       $proxyParts = explode(':', $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyServer'], 2);
+       $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_host'] = $proxyParts[0];
+       $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_port'] = $proxyParts[1];
+    /* TODO: uncomment after refactoring getUrl()
+       t3lib_div::deprecationLog(
+               'This TYPO3 installation is using the $TYPO3_CONF_VARS[\'SYS\'][\'curlProxyServer\'] property with the following value: ' .
+               $TYPO3_CONF_VARS['SYS']['curlProxyServer'] . LF . 'Please make sure to set $TYPO3_CONF_VARS[\'HTTP\'][\'proxy_host\']' .
+               ' and $TYPO3_CONF_VARS[\'HTTP\'][\'proxy_port\'] instead.' . LF . 'Remove this line from your localconf.php.'
+       );*/
+}
+if (!empty($GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass'])) {
+       $userPassParts = explode(':', $GLOBALS['TYPO3_CONF_VARS']['SYS']['curlProxyUserPass'], 2);
+       $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_user'] = $userPassParts[0];
+       $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_password'] = $userPassParts[1];
+       /* TODO: uncomment after refactoring getUrl()
+       t3lib_div::deprecationLog(
+               'This TYPO3 installation is using the $TYPO3_CONF_VARS[\'SYS\'][\'curlProxyUserPass\'] property with the following value: ' .
+               $TYPO3_CONF_VARS['SYS']['curlProxyUserPass'] . LF . 'Please make sure to set $TYPO3_CONF_VARS[\'HTTP\'][\'proxy_user\']' .
+               ' and $TYPO3_CONF_VARS[\'HTTP\'][\'proxy_password\'] instead.' . LF . 'Remove this line from your localconf.php.'
+       );*/
+}
+
+       // ['HTTP']['proxy_auth_scheme'] can only be 'digest' or 'basic'
+$GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_auth_scheme'] === 'digest' ?
+       $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_auth_scheme'] = 'digest' :
+       $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_auth_scheme'] = 'basic';
+
+
 $timeZone = $GLOBALS['TYPO3_CONF_VARS']['SYS']['phpTimeZone'];
 if (empty($timeZone)) {
                // time zone from the server environment (TZ env or OS query)
@@ -1040,4 +1105,4 @@ $SIM_EXEC_TIME = $EXEC_TIME;                      // $SIM_EXEC_TIME is set to $EXEC_TIME but can be
 $ACCESS_TIME = $EXEC_TIME - ($EXEC_TIME % 60);         // $ACCESS_TIME is a common time in minutes for access control
 $SIM_ACCESS_TIME = $ACCESS_TIME;               // if $SIM_EXEC_TIME is changed this value must be set accordingly
 
-?>
\ No newline at end of file
+?>
index d5d60a4..91122af 100644 (file)
@@ -229,9 +229,10 @@ $t3libClasses = array(
        't3lib_tree_pagetree_indicator' => PATH_t3lib . 'tree/pagetree/class.t3lib_tree_pagetree_indicator.php',
        't3lib_tree_pagetree_indicatorprovider' => PATH_t3lib . 'tree/pagetree/interfaces/interface.t3lib_tree_pagetree_interfaces_indicatorprovider.php',
        't3lib_tree_pagetree_interfaces_collectionprocessor' => PATH_t3lib . 'tree/pagetree/interfaces/interface.t3lib_tree_pagetree_interfaces_collectionprocessor.php',
+       't3lib_http_request' => PATH_t3lib . 'http/class.t3lib_http_request.php',
 );
 
 $tslibClasses = require(PATH_typo3 . 'sysext/cms/ext_autoload.php');
 
 return array_merge($t3libClasses, $tslibClasses);
-?>
+?>
\ No newline at end of file
diff --git a/t3lib/http/class.t3lib_http_request.php b/t3lib/http/class.t3lib_http_request.php
new file mode 100644 (file)
index 0000000..0492910
--- /dev/null
@@ -0,0 +1,97 @@
+<?php
+/***************************************************************
+ *  Copyright notice
+ *
+ *  (c) 2011 Philipp Gampe <dev.typo3@philippgampe.info>
+ *  All rights reserved
+ *
+ *  This script is part of the TYPO3 project. The TYPO3 project is
+ *  free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  The GNU General Public License can be found at
+ *  http://www.gnu.org/copyleft/gpl.html.
+ *
+ *  This script is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  This copyright notice MUST APPEAR in all copies of the script!
+ ***************************************************************/
+
+t3lib_div::requireOnce(PATH_typo3 . 'contrib/pear/HTTP/Request2.php');
+
+/**
+ * HTTP Request Utility class
+ *
+ * Extends HTTP_Request2 and sets TYPO3 environment defaults
+ *
+ * @author Philipp Gampe <dev.typo3@philippgampe.info>
+ * @package TYPO3
+ * @subpackage t3lib
+ */
+class t3lib_http_Request extends HTTP_Request2 {
+
+       /**
+        * Default constructor - sets TYPO3 defaults
+        *
+        * @param string|Net_Url2 $url Request URL
+        * @param string $method Request Method (GET, HEAD or POST). Redirects reset this to GET unless "strict_redirects" is set.
+        * @param array $config Configuration for this request instance
+        * @link http://pear.php.net/manual/en/package.http.http-request2.config.php
+        */
+       public function __construct($url = NULL, $method = self::METHOD_GET, array $config = array()) {
+
+               parent::__construct($url, $method);
+               $this->setConfiguration($config);
+       }
+
+       /**
+       * Sets the configuration
+       * Merges default values with provided $config and overrides all not provided values
+       * with there defaults from localconf.php or config_default.php.
+       *
+       * @param array $config Configuration options which override the default configuration
+       * @return void
+       * @link http://pear.php.net/manual/en/package.http.http-request2.config.php
+       */
+       public function setConfiguration(array $config = array()) {
+                       // set a branded user-agent
+               $this->setHeader('user-agent', $GLOBALS['TYPO3_CONF_VARS']['HTTP']['userAgent']);
+
+                       // set defaults from localconf.php or config_default.php
+               $default = array(
+                       'adapter' =>                    $GLOBALS['TYPO3_CONF_VARS']['HTTP']['adapter'],
+                       'connect_timeout' =>    $GLOBALS['TYPO3_CONF_VARS']['HTTP']['connect_timeout'],
+                       'timeout' =>                    $GLOBALS['TYPO3_CONF_VARS']['HTTP']['timeout'],
+                       'protocol_version' =>   $GLOBALS['TYPO3_CONF_VARS']['HTTP']['protocol_version'],
+
+                       'follow_redirects' =>   $GLOBALS['TYPO3_CONF_VARS']['HTTP']['follow_redirects'],
+                       'max_redirects' =>              $GLOBALS['TYPO3_CONF_VARS']['HTTP']['max_redirects'],
+                       'strict_redirects' =>   $GLOBALS['TYPO3_CONF_VARS']['HTTP']['strict_redirects'],
+
+                       'proxy_host' =>                 $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_host'],
+                       'proxy_port' =>                 $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_port'],
+                       'proxy_user' =>                 $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_user'],
+                       'proxy_password' =>     $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_password'],
+                       'proxy_auth_scheme' =>  $GLOBALS['TYPO3_CONF_VARS']['HTTP']['proxy_auth_scheme'],
+
+                       'ssl_verify_peer' =>    $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_verify_peer'],
+                       'ssl_verify_host' =>    $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_verify_host'],
+                       'ssl_cafile' =>                 $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_cafile'],
+                       'ssl_capath' =>                 $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_capath'],
+                       'ssl_local_cert' =>     $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_local_cert'],
+                       'ssl_passphrase' =>     $GLOBALS['TYPO3_CONF_VARS']['HTTP']['ssl_passphrase'],
+                       );
+
+               $configuration = array_merge($default, $config);
+
+               $this->setConfig($configuration);
+
+       }
+
+}
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/HTTP/Request2.php b/typo3/contrib/pear/HTTP/Request2.php
new file mode 100644 (file)
index 0000000..2af96a7
--- /dev/null
@@ -0,0 +1,1015 @@
+<?php
+/**
+ * Class representing a HTTP request message
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: Request2.php 308735 2011-02-27 20:31:28Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * A class representing an URL as per RFC 3986.
+ */
+require_once 'Net/URL2.php';
+
+/**
+ * Exception class for HTTP_Request2 package
+ */
+require_once 'HTTP/Request2/Exception.php';
+
+/**
+ * Class representing a HTTP request message
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @version    Release: 2.0.0RC1
+ * @link       http://tools.ietf.org/html/rfc2616#section-5
+ */
+class HTTP_Request2 implements SplSubject
+{
+   /**#@+
+    * Constants for HTTP request methods
+    *
+    * @link http://tools.ietf.org/html/rfc2616#section-5.1.1
+    */
+    const METHOD_OPTIONS = 'OPTIONS';
+    const METHOD_GET     = 'GET';
+    const METHOD_HEAD    = 'HEAD';
+    const METHOD_POST    = 'POST';
+    const METHOD_PUT     = 'PUT';
+    const METHOD_DELETE  = 'DELETE';
+    const METHOD_TRACE   = 'TRACE';
+    const METHOD_CONNECT = 'CONNECT';
+   /**#@-*/
+
+   /**#@+
+    * Constants for HTTP authentication schemes
+    *
+    * @link http://tools.ietf.org/html/rfc2617
+    */
+    const AUTH_BASIC  = 'basic';
+    const AUTH_DIGEST = 'digest';
+   /**#@-*/
+
+   /**
+    * Regular expression used to check for invalid symbols in RFC 2616 tokens
+    * @link http://pear.php.net/bugs/bug.php?id=15630
+    */
+    const REGEXP_INVALID_TOKEN = '![\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]!';
+
+   /**
+    * Regular expression used to check for invalid symbols in cookie strings
+    * @link http://pear.php.net/bugs/bug.php?id=15630
+    * @link http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html
+    */
+    const REGEXP_INVALID_COOKIE = '/[\s,;]/';
+
+   /**
+    * Fileinfo magic database resource
+    * @var  resource
+    * @see  detectMimeType()
+    */
+    private static $_fileinfoDb;
+
+   /**
+    * Observers attached to the request (instances of SplObserver)
+    * @var  array
+    */
+    protected $observers = array();
+
+   /**
+    * Request URL
+    * @var  Net_URL2
+    */
+    protected $url;
+
+   /**
+    * Request method
+    * @var  string
+    */
+    protected $method = self::METHOD_GET;
+
+   /**
+    * Authentication data
+    * @var  array
+    * @see  getAuth()
+    */
+    protected $auth;
+
+   /**
+    * Request headers
+    * @var  array
+    */
+    protected $headers = array();
+
+   /**
+    * Configuration parameters
+    * @var  array
+    * @see  setConfig()
+    */
+    protected $config = array(
+        'adapter'           => 'HTTP_Request2_Adapter_Socket',
+        'connect_timeout'   => 10,
+        'timeout'           => 0,
+        'use_brackets'      => true,
+        'protocol_version'  => '1.1',
+        'buffer_size'       => 16384,
+        'store_body'        => true,
+
+        'proxy_host'        => '',
+        'proxy_port'        => '',
+        'proxy_user'        => '',
+        'proxy_password'    => '',
+        'proxy_auth_scheme' => self::AUTH_BASIC,
+
+        'ssl_verify_peer'   => true,
+        'ssl_verify_host'   => true,
+        'ssl_cafile'        => null,
+        'ssl_capath'        => null,
+        'ssl_local_cert'    => null,
+        'ssl_passphrase'    => null,
+
+        'digest_compat_ie'  => false,
+
+        'follow_redirects'  => false,
+        'max_redirects'     => 5,
+        'strict_redirects'  => false
+    );
+
+   /**
+    * Last event in request / response handling, intended for observers
+    * @var  array
+    * @see  getLastEvent()
+    */
+    protected $lastEvent = array(
+        'name' => 'start',
+        'data' => null
+    );
+
+   /**
+    * Request body
+    * @var  string|resource
+    * @see  setBody()
+    */
+    protected $body = '';
+
+   /**
+    * Array of POST parameters
+    * @var  array
+    */
+    protected $postParams = array();
+
+   /**
+    * Array of file uploads (for multipart/form-data POST requests)
+    * @var  array
+    */
+    protected $uploads = array();
+
+   /**
+    * Adapter used to perform actual HTTP request
+    * @var  HTTP_Request2_Adapter
+    */
+    protected $adapter;
+
+   /**
+    * Cookie jar to persist cookies between requests
+    * @var HTTP_Request2_CookieJar
+    */
+    protected $cookieJar = null;
+
+   /**
+    * Constructor. Can set request URL, method and configuration array.
+    *
+    * Also sets a default value for User-Agent header.
+    *
+    * @param    string|Net_Url2     Request URL
+    * @param    string              Request method
+    * @param    array               Configuration for this Request instance
+    */
+    public function __construct($url = null, $method = self::METHOD_GET, array $config = array())
+    {
+        $this->setConfig($config);
+        if (!empty($url)) {
+            $this->setUrl($url);
+        }
+        if (!empty($method)) {
+            $this->setMethod($method);
+        }
+        $this->setHeader('user-agent', 'HTTP_Request2/2.0.0RC1 ' .
+                         '(http://pear.php.net/package/http_request2) ' .
+                         'PHP/' . phpversion());
+    }
+
+   /**
+    * Sets the URL for this request
+    *
+    * If the URL has userinfo part (username & password) these will be removed
+    * and converted to auth data. If the URL does not have a path component,
+    * that will be set to '/'.
+    *
+    * @param    string|Net_URL2 Request URL
+    * @return   HTTP_Request2
+    * @throws   HTTP_Request2_LogicException
+    */
+    public function setUrl($url)
+    {
+        if (is_string($url)) {
+            $url = new Net_URL2(
+                $url, array(Net_URL2::OPTION_USE_BRACKETS => $this->config['use_brackets'])
+            );
+        }
+        if (!$url instanceof Net_URL2) {
+            throw new HTTP_Request2_LogicException(
+                'Parameter is not a valid HTTP URL',
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+        // URL contains username / password?
+        if ($url->getUserinfo()) {
+            $username = $url->getUser();
+            $password = $url->getPassword();
+            $this->setAuth(rawurldecode($username), $password? rawurldecode($password): '');
+            $url->setUserinfo('');
+        }
+        if ('' == $url->getPath()) {
+            $url->setPath('/');
+        }
+        $this->url = $url;
+
+        return $this;
+    }
+
+   /**
+    * Returns the request URL
+    *
+    * @return   Net_URL2
+    */
+    public function getUrl()
+    {
+        return $this->url;
+    }
+
+   /**
+    * Sets the request method
+    *
+    * @param    string
+    * @return   HTTP_Request2
+    * @throws   HTTP_Request2_LogicException if the method name is invalid
+    */
+    public function setMethod($method)
+    {
+        // Method name should be a token: http://tools.ietf.org/html/rfc2616#section-5.1.1
+        if (preg_match(self::REGEXP_INVALID_TOKEN, $method)) {
+            throw new HTTP_Request2_LogicException(
+                "Invalid request method '{$method}'",
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+        $this->method = $method;
+
+        return $this;
+    }
+
+   /**
+    * Returns the request method
+    *
+    * @return   string
+    */
+    public function getMethod()
+    {
+        return $this->method;
+    }
+
+   /**
+    * Sets the configuration parameter(s)
+    *
+    * The following parameters are available:
+    * <ul>
+    *   <li> 'adapter'           - adapter to use (string)</li>
+    *   <li> 'connect_timeout'   - Connection timeout in seconds (integer)</li>
+    *   <li> 'timeout'           - Total number of seconds a request can take.
+    *                              Use 0 for no limit, should be greater than
+    *                              'connect_timeout' if set (integer)</li>
+    *   <li> 'use_brackets'      - Whether to append [] to array variable names (bool)</li>
+    *   <li> 'protocol_version'  - HTTP Version to use, '1.0' or '1.1' (string)</li>
+    *   <li> 'buffer_size'       - Buffer size to use for reading and writing (int)</li>
+    *   <li> 'store_body'        - Whether to store response body in response object.
+    *                              Set to false if receiving a huge response and
+    *                              using an Observer to save it (boolean)</li>
+    *   <li> 'proxy_host'        - Proxy server host (string)</li>
+    *   <li> 'proxy_port'        - Proxy server port (integer)</li>
+    *   <li> 'proxy_user'        - Proxy auth username (string)</li>
+    *   <li> 'proxy_password'    - Proxy auth password (string)</li>
+    *   <li> 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)</li>
+    *   <li> 'ssl_verify_peer'   - Whether to verify peer's SSL certificate (bool)</li>
+    *   <li> 'ssl_verify_host'   - Whether to check that Common Name in SSL
+    *                              certificate matches host name (bool)</li>
+    *   <li> 'ssl_cafile'        - Cerificate Authority file to verify the peer
+    *                              with (use with 'ssl_verify_peer') (string)</li>
+    *   <li> 'ssl_capath'        - Directory holding multiple Certificate
+    *                              Authority files (string)</li>
+    *   <li> 'ssl_local_cert'    - Name of a file containing local cerificate (string)</li>
+    *   <li> 'ssl_passphrase'    - Passphrase with which local certificate
+    *                              was encoded (string)</li>
+    *   <li> 'digest_compat_ie'  - Whether to imitate behaviour of MSIE 5 and 6
+    *                              in using URL without query string in digest
+    *                              authentication (boolean)</li>
+    *   <li> 'follow_redirects'  - Whether to automatically follow HTTP Redirects (boolean)</li>
+    *   <li> 'max_redirects'     - Maximum number of redirects to follow (integer)</li>
+    *   <li> 'strict_redirects'  - Whether to keep request method on redirects via status 301 and
+    *                              302 (true, needed for compatibility with RFC 2616)
+    *                              or switch to GET (false, needed for compatibility with most
+    *                              browsers) (boolean)</li>
+    * </ul>
+    *
+    * @param    string|array    configuration parameter name or array
+    *                           ('parameter name' => 'parameter value')
+    * @param    mixed           parameter value if $nameOrConfig is not an array
+    * @return   HTTP_Request2
+    * @throws   HTTP_Request2_LogicException If the parameter is unknown
+    */
+    public function setConfig($nameOrConfig, $value = null)
+    {
+        if (is_array($nameOrConfig)) {
+            foreach ($nameOrConfig as $name => $value) {
+                $this->setConfig($name, $value);
+            }
+
+        } else {
+            if (!array_key_exists($nameOrConfig, $this->config)) {
+                throw new HTTP_Request2_LogicException(
+                    "Unknown configuration parameter '{$nameOrConfig}'",
+                    HTTP_Request2_Exception::INVALID_ARGUMENT
+                );
+            }
+            $this->config[$nameOrConfig] = $value;
+        }
+
+        return $this;
+    }
+
+   /**
+    * Returns the value(s) of the configuration parameter(s)
+    *
+    * @param    string  parameter name
+    * @return   mixed   value of $name parameter, array of all configuration
+    *                   parameters if $name is not given
+    * @throws   HTTP_Request2_LogicException If the parameter is unknown
+    */
+    public function getConfig($name = null)
+    {
+        if (null === $name) {
+            return $this->config;
+        } elseif (!array_key_exists($name, $this->config)) {
+            throw new HTTP_Request2_LogicException(
+                "Unknown configuration parameter '{$name}'",
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+        return $this->config[$name];
+    }
+
+   /**
+    * Sets the autentification data
+    *
+    * @param    string  user name
+    * @param    string  password
+    * @param    string  authentication scheme
+    * @return   HTTP_Request2
+    */
+    public function setAuth($user, $password = '', $scheme = self::AUTH_BASIC)
+    {
+        if (empty($user)) {
+            $this->auth = null;
+        } else {
+            $this->auth = array(
+                'user'     => (string)$user,
+                'password' => (string)$password,
+                'scheme'   => $scheme
+            );
+        }
+
+        return $this;
+    }
+
+   /**
+    * Returns the authentication data
+    *
+    * The array has the keys 'user', 'password' and 'scheme', where 'scheme'
+    * is one of the HTTP_Request2::AUTH_* constants.
+    *
+    * @return   array
+    */
+    public function getAuth()
+    {
+        return $this->auth;
+    }
+
+   /**
+    * Sets request header(s)
+    *
+    * The first parameter may be either a full header string 'header: value' or
+    * header name. In the former case $value parameter is ignored, in the latter
+    * the header's value will either be set to $value or the header will be
+    * removed if $value is null. The first parameter can also be an array of
+    * headers, in that case method will be called recursively.
+    *
+    * Note that headers are treated case insensitively as per RFC 2616.
+    *
+    * <code>
+    * $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar'
+    * $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz'
+    * $req->setHeader(array('foo' => 'Quux')); // sets the value of 'Foo' header to 'Quux'
+    * $req->setHeader('FOO'); // removes 'Foo' header from request
+    * </code>
+    *
+    * @param    string|array    header name, header string ('Header: value')
+    *                           or an array of headers
+    * @param    string|array|null header value if $name is not an array,
+    *                           header will be removed if value is null
+    * @param    bool            whether to replace previous header with the
+    *                           same name or append to its value
+    * @return   HTTP_Request2
+    * @throws   HTTP_Request2_LogicException
+    */
+    public function setHeader($name, $value = null, $replace = true)
+    {
+        if (is_array($name)) {
+            foreach ($name as $k => $v) {
+                if (is_string($k)) {
+                    $this->setHeader($k, $v, $replace);
+                } else {
+                    $this->setHeader($v, null, $replace);
+                }
+            }
+        } else {
+            if (null === $value && strpos($name, ':')) {
+                list($name, $value) = array_map('trim', explode(':', $name, 2));
+            }
+            // Header name should be a token: http://tools.ietf.org/html/rfc2616#section-4.2
+            if (preg_match(self::REGEXP_INVALID_TOKEN, $name)) {
+                throw new HTTP_Request2_LogicException(
+                    "Invalid header name '{$name}'",
+                    HTTP_Request2_Exception::INVALID_ARGUMENT
+                );
+            }
+            // Header names are case insensitive anyway
+            $name = strtolower($name);
+            if (null === $value) {
+                unset($this->headers[$name]);
+
+            } else {
+                if (is_array($value)) {
+                    $value = implode(', ', array_map('trim', $value));
+                } elseif (is_string($value)) {
+                    $value = trim($value);
+                }
+                if (!isset($this->headers[$name]) || $replace) {
+                    $this->headers[$name] = $value;
+                } else {
+                    $this->headers[$name] .= ', ' . $value;
+                }
+            }
+        }
+
+        return $this;
+    }
+
+   /**
+    * Returns the request headers
+    *
+    * The array is of the form ('header name' => 'header value'), header names
+    * are lowercased
+    *
+    * @return   array
+    */
+    public function getHeaders()
+    {
+        return $this->headers;
+    }
+
+   /**
+    * Adds a cookie to the request
+    *
+    * If the request does not have a CookieJar object set, this method simply
+    * appends a cookie to "Cookie:" header.
+    *
+    * If a CookieJar object is available, the cookie is stored in that object.
+    * Data from request URL will be used for setting its 'domain' and 'path'
+    * parameters, 'expires' and 'secure' will be set to null and false,
+    * respectively. If you need further control, use CookieJar's methods.
+    *
+    * @param    string  cookie name
+    * @param    string  cookie value
+    * @return   HTTP_Request2
+    * @throws   HTTP_Request2_LogicException
+    * @see      setCookieJar()
+    */
+    public function addCookie($name, $value)
+    {
+        if (!empty($this->cookieJar)) {
+            $this->cookieJar->store(array('name' => $name, 'value' => $value),
+                                    $this->url);
+
+        } else {
+            $cookie = $name . '=' . $value;
+            if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) {
+                throw new HTTP_Request2_LogicException(
+                    "Invalid cookie: '{$cookie}'",
+                    HTTP_Request2_Exception::INVALID_ARGUMENT
+                );
+            }
+            $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; ';
+            $this->setHeader('cookie', $cookies . $cookie);
+        }
+
+        return $this;
+    }
+
+   /**
+    * Sets the request body
+    *
+    * If you provide file pointer rather than file name, it should support
+    * fstat() and rewind() operations.
+    *
+    * @param    string|resource|HTTP_Request2_MultipartBody  Either a string
+    *               with the body or filename containing body or pointer to
+    *               an open file or object with multipart body data
+    * @param    bool    Whether first parameter is a filename
+    * @return   HTTP_Request2
+    * @throws   HTTP_Request2_LogicException
+    */
+    public function setBody($body, $isFilename = false)
+    {
+        if (!$isFilename && !is_resource($body)) {
+            if (!$body instanceof HTTP_Request2_MultipartBody) {
+                $this->body = (string)$body;
+            } else {
+                $this->body = $body;
+            }
+        } else {
+            $fileData = $this->fopenWrapper($body, empty($this->headers['content-type']));
+            $this->body = $fileData['fp'];
+            if (empty($this->headers['content-type'])) {
+                $this->setHeader('content-type', $fileData['type']);
+            }
+        }
+        $this->postParams = $this->uploads = array();
+
+        return $this;
+    }
+
+   /**
+    * Returns the request body
+    *
+    * @return   string|resource|HTTP_Request2_MultipartBody
+    */
+    public function getBody()
+    {
+        if (self::METHOD_POST == $this->method &&
+            (!empty($this->postParams) || !empty($this->uploads))
+        ) {
+            if (0 === strpos($this->headers['content-type'], 'application/x-www-form-urlencoded')) {
+                $body = http_build_query($this->postParams, '', '&');
+                if (!$this->getConfig('use_brackets')) {
+                    $body = preg_replace('/%5B\d+%5D=/', '=', $body);
+                }
+                // support RFC 3986 by not encoding '~' symbol (request #15368)
+                return str_replace('%7E', '~', $body);
+
+            } elseif (0 === strpos($this->headers['content-type'], 'multipart/form-data')) {
+                require_once 'HTTP/Request2/MultipartBody.php';
+                return new HTTP_Request2_MultipartBody(
+                    $this->postParams, $this->uploads, $this->getConfig('use_brackets')
+                );
+            }
+        }
+        return $this->body;
+    }
+
+   /**
+    * Adds a file to form-based file upload
+    *
+    * Used to emulate file upload via a HTML form. The method also sets
+    * Content-Type of HTTP request to 'multipart/form-data'.
+    *
+    * If you just want to send the contents of a file as the body of HTTP
+    * request you should use setBody() method.
+    *
+    * If you provide file pointers rather than file names, they should support
+    * fstat() and rewind() operations.
+    *
+    * @param    string  name of file-upload field
+    * @param    string|resource|array   full name of local file, pointer to
+    *               open file or an array of files
+    * @param    string  filename to send in the request
+    * @param    string  content-type of file being uploaded
+    * @return   HTTP_Request2
+    * @throws   HTTP_Request2_LogicException
+    */
+    public function addUpload($fieldName, $filename, $sendFilename = null,
+                              $contentType = null)
+    {
+        if (!is_array($filename)) {
+            $fileData = $this->fopenWrapper($filename, empty($contentType));
+            $this->uploads[$fieldName] = array(
+                'fp'        => $fileData['fp'],
+                'filename'  => !empty($sendFilename)? $sendFilename
+                                :(is_string($filename)? basename($filename): 'anonymous.blob') ,
+                'size'      => $fileData['size'],
+                'type'      => empty($contentType)? $fileData['type']: $contentType
+            );
+        } else {
+            $fps = $names = $sizes = $types = array();
+            foreach ($filename as $f) {
+                if (!is_array($f)) {
+                    $f = array($f);
+                }
+                $fileData = $this->fopenWrapper($f[0], empty($f[2]));
+                $fps[]   = $fileData['fp'];
+                $names[] = !empty($f[1])? $f[1]
+                            :(is_string($f[0])? basename($f[0]): 'anonymous.blob');
+                $sizes[] = $fileData['size'];
+                $types[] = empty($f[2])? $fileData['type']: $f[2];
+            }
+            $this->uploads[$fieldName] = array(
+                'fp' => $fps, 'filename' => $names, 'size' => $sizes, 'type' => $types
+            );
+        }
+        if (empty($this->headers['content-type']) ||
+            'application/x-www-form-urlencoded' == $this->headers['content-type']
+        ) {
+            $this->setHeader('content-type', 'multipart/form-data');
+        }
+
+        return $this;
+    }
+
+   /**
+    * Adds POST parameter(s) to the request.
+    *
+    * @param    string|array    parameter name or array ('name' => 'value')
+    * @param    mixed           parameter value (can be an array)
+    * @return   HTTP_Request2
+    */
+    public function addPostParameter($name, $value = null)
+    {
+        if (!is_array($name)) {
+            $this->postParams[$name] = $value;
+        } else {
+            foreach ($name as $k => $v) {
+                $this->addPostParameter($k, $v);
+            }
+        }
+        if (empty($this->headers['content-type'])) {
+            $this->setHeader('content-type', 'application/x-www-form-urlencoded');
+        }
+
+        return $this;
+    }
+
+   /**
+    * Attaches a new observer
+    *
+    * @param    SplObserver
+    */
+    public function attach(SplObserver $observer)
+    {
+        foreach ($this->observers as $attached) {
+            if ($attached === $observer) {
+                return;
+            }
+        }
+        $this->observers[] = $observer;
+    }
+
+   /**
+    * Detaches an existing observer
+    *
+    * @param    SplObserver
+    */
+    public function detach(SplObserver $observer)
+    {
+        foreach ($this->observers as $key => $attached) {
+            if ($attached === $observer) {
+                unset($this->observers[$key]);
+                return;
+            }
+        }
+    }
+
+   /**
+    * Notifies all observers
+    */
+    public function notify()
+    {
+        foreach ($this->observers as $observer) {
+            $observer->update($this);
+        }
+    }
+
+   /**
+    * Sets the last event
+    *
+    * Adapters should use this method to set the current state of the request
+    * and notify the observers.
+    *
+    * @param    string  event name
+    * @param    mixed   event data
+    */
+    public function setLastEvent($name, $data = null)
+    {
+        $this->lastEvent = array(
+            'name' => $name,
+            'data' => $data
+        );
+        $this->notify();
+    }
+
+   /**
+    * Returns the last event
+    *
+    * Observers should use this method to access the last change in request.
+    * The following event names are possible:
+    * <ul>
+    *   <li>'connect'                 - after connection to remote server,
+    *                                   data is the destination (string)</li>
+    *   <li>'disconnect'              - after disconnection from server</li>
+    *   <li>'sentHeaders'             - after sending the request headers,
+    *                                   data is the headers sent (string)</li>
+    *   <li>'sentBodyPart'            - after sending a part of the request body,
+    *                                   data is the length of that part (int)</li>
+    *   <li>'sentBody'                - after sending the whole request body,
+    *                                   data is request body length (int)</li>
+    *   <li>'receivedHeaders'         - after receiving the response headers,
+    *                                   data is HTTP_Request2_Response object</li>
+    *   <li>'receivedBodyPart'        - after receiving a part of the response
+    *                                   body, data is that part (string)</li>
+    *   <li>'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still
+    *                                   encoded by Content-Encoding</li>
+    *   <li>'receivedBody'            - after receiving the complete response
+    *                                   body, data is HTTP_Request2_Response object</li>
+    * </ul>
+    * Different adapters may not send all the event types. Mock adapter does
+    * not send any events to the observers.
+    *
+    * @return   array   The array has two keys: 'name' and 'data'
+    */
+    public function getLastEvent()
+    {
+        return $this->lastEvent;
+    }
+
+   /**
+    * Sets the adapter used to actually perform the request
+    *
+    * You can pass either an instance of a class implementing HTTP_Request2_Adapter
+    * or a class name. The method will only try to include a file if the class
+    * name starts with HTTP_Request2_Adapter_, it will also try to prepend this
+    * prefix to the class name if it doesn't contain any underscores, so that
+    * <code>
+    * $request->setAdapter('curl');
+    * </code>
+    * will work.
+    *
+    * @param    string|HTTP_Request2_Adapter
+    * @return   HTTP_Request2
+    * @throws   HTTP_Request2_LogicException
+    */
+    public function setAdapter($adapter)
+    {
+        if (is_string($adapter)) {
+            if (!class_exists($adapter, false)) {
+                if (false === strpos($adapter, '_')) {
+                    $adapter = 'HTTP_Request2_Adapter_' . ucfirst($adapter);
+                }
+                if (preg_match('/^HTTP_Request2_Adapter_([a-zA-Z0-9]+)$/', $adapter)) {
+                    include_once str_replace('_', DIRECTORY_SEPARATOR, $adapter) . '.php';
+                }
+                if (!class_exists($adapter, false)) {
+                    throw new HTTP_Request2_LogicException(
+                        "Class {$adapter} not found",
+                        HTTP_Request2_Exception::MISSING_VALUE
+                    );
+                }
+            }
+            $adapter = new $adapter;
+        }
+        if (!$adapter instanceof HTTP_Request2_Adapter) {
+            throw new HTTP_Request2_LogicException(
+                'Parameter is not a HTTP request adapter',
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+        $this->adapter = $adapter;
+
+        return $this;
+    }
+
+   /**
+    * Sets the cookie jar
+    *
+    * A cookie jar is used to maintain cookies across HTTP requests and
+    * responses. Cookies from jar will be automatically added to the request
+    * headers based on request URL.
+    *
+    * @param HTTP_Request2_CookieJar|bool   Existing CookieJar object, true to
+    *                                       create a new one, false to remove
+    */
+    public function setCookieJar($jar = true)
+    {
+        if (!class_exists('HTTP_Request2_CookieJar', false)) {
+            require_once 'HTTP/Request2/CookieJar.php';
+        }
+
+        if ($jar instanceof HTTP_Request2_CookieJar) {
+            $this->cookieJar = $jar;
+        } elseif (true === $jar) {
+            $this->cookieJar = new HTTP_Request2_CookieJar();
+        } elseif (!$jar) {
+            $this->cookieJar = null;
+        } else {
+            throw new HTTP_Request2_LogicException(
+                'Invalid parameter passed to setCookieJar()',
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+
+        return $this;
+    }
+
+   /**
+    * Returns current CookieJar object or null if none
+    *
+    * @return HTTP_Request2_CookieJar|null
+    */
+    public function getCookieJar()
+    {
+        return $this->cookieJar;
+    }
+
+   /**
+    * Sends the request and returns the response
+    *
+    * @throws   HTTP_Request2_Exception
+    * @return   HTTP_Request2_Response
+    */
+    public function send()
+    {
+        // Sanity check for URL
+        if (!$this->url instanceof Net_URL2
+            || !$this->url->isAbsolute()
+            || !in_array(strtolower($this->url->getScheme()), array('https', 'http'))
+        ) {
+            throw new HTTP_Request2_LogicException(
+                'HTTP_Request2 needs an absolute HTTP(S) request URL, '
+                . ($this->url instanceof Net_URL2
+                   ? 'none' : "'" . $this->url->__toString() . "'")
+                . ' given',
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+        if (empty($this->adapter)) {
+            $this->setAdapter($this->getConfig('adapter'));
+        }
+        // magic_quotes_runtime may break file uploads and chunked response
+        // processing; see bug #4543. Don't use ini_get() here; see bug #16440.
+        if ($magicQuotes = get_magic_quotes_runtime()) {
+            set_magic_quotes_runtime(false);
+        }
+        // force using single byte encoding if mbstring extension overloads
+        // strlen() and substr(); see bug #1781, bug #10605
+        if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
+            $oldEncoding = mb_internal_encoding();
+            mb_internal_encoding('iso-8859-1');
+        }
+
+        try {
+            $response = $this->adapter->sendRequest($this);
+        } catch (Exception $e) {
+        }
+        // cleanup in either case (poor man's "finally" clause)
+        if ($magicQuotes) {
+            set_magic_quotes_runtime(true);
+        }
+        if (!empty($oldEncoding)) {
+            mb_internal_encoding($oldEncoding);
+        }
+        // rethrow the exception
+        if (!empty($e)) {
+            throw $e;
+        }
+        return $response;
+    }
+
+   /**
+    * Wrapper around fopen()/fstat() used by setBody() and addUpload()
+    *
+    * @param  string|resource file name or pointer to open file
+    * @param  bool            whether to try autodetecting MIME type of file,
+    *                         will only work if $file is a filename, not pointer
+    * @return array array('fp' => file pointer, 'size' => file size, 'type' => MIME type)
+    * @throws HTTP_Request2_LogicException
+    */
+    protected function fopenWrapper($file, $detectType = false)
+    {
+        if (!is_string($file) && !is_resource($file)) {
+            throw new HTTP_Request2_LogicException(
+                "Filename or file pointer resource expected",
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+        $fileData = array(
+            'fp'   => is_string($file)? null: $file,
+            'type' => 'application/octet-stream',
+            'size' => 0
+        );
+        if (is_string($file)) {
+            $track = @ini_set('track_errors', 1);
+            if (!($fileData['fp'] = @fopen($file, 'rb'))) {
+                $e = new HTTP_Request2_LogicException(
+                    $php_errormsg, HTTP_Request2_Exception::READ_ERROR
+                );
+            }
+            @ini_set('track_errors', $track);
+            if (isset($e)) {
+                throw $e;
+            }
+            if ($detectType) {
+                $fileData['type'] = self::detectMimeType($file);
+            }
+        }
+        if (!($stat = fstat($fileData['fp']))) {
+            throw new HTTP_Request2_LogicException(
+                "fstat() call failed", HTTP_Request2_Exception::READ_ERROR
+            );
+        }
+        $fileData['size'] = $stat['size'];
+
+        return $fileData;
+    }
+
+   /**
+    * Tries to detect MIME type of a file
+    *
+    * The method will try to use fileinfo extension if it is available,
+    * deprecated mime_content_type() function in the other case. If neither
+    * works, default 'application/octet-stream' MIME type is returned
+    *
+    * @param    string  filename
+    * @return   string  file MIME type
+    */
+    protected static function detectMimeType($filename)
+    {
+        // finfo extension from PECL available
+        if (function_exists('finfo_open')) {
+            if (!isset(self::$_fileinfoDb)) {
+                self::$_fileinfoDb = @finfo_open(FILEINFO_MIME);
+            }
+            if (self::$_fileinfoDb) {
+                $info = finfo_file(self::$_fileinfoDb, $filename);
+            }
+        }
+        // (deprecated) mime_content_type function available
+        if (empty($info) && function_exists('mime_content_type')) {
+            return mime_content_type($filename);
+        }
+        return empty($info)? 'application/octet-stream': $info;
+    }
+}
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/HTTP/Request2/Adapter.php b/typo3/contrib/pear/HTTP/Request2/Adapter.php
new file mode 100644 (file)
index 0000000..a0e86a1
--- /dev/null
@@ -0,0 +1,154 @@
+<?php
+/**
+ * Base class for HTTP_Request2 adapters
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: Adapter.php 308322 2011-02-14 13:58:03Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Class representing a HTTP response
+ */
+require_once 'HTTP/Request2/Response.php';
+
+/**
+ * Base class for HTTP_Request2 adapters
+ *
+ * HTTP_Request2 class itself only defines methods for aggregating the request
+ * data, all actual work of sending the request to the remote server and
+ * receiving its response is performed by adapters.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @version    Release: 2.0.0RC1
+ */
+abstract class HTTP_Request2_Adapter
+{
+   /**
+    * A list of methods that MUST NOT have a request body, per RFC 2616
+    * @var  array
+    */
+    protected static $bodyDisallowed = array('TRACE');
+
+   /**
+    * Methods having defined semantics for request body
+    *
+    * Content-Length header (indicating that the body follows, section 4.3 of
+    * RFC 2616) will be sent for these methods even if no body was added
+    *
+    * @var  array
+    * @link http://pear.php.net/bugs/bug.php?id=12900
+    * @link http://pear.php.net/bugs/bug.php?id=14740
+    */
+    protected static $bodyRequired = array('POST', 'PUT');
+
+   /**
+    * Request being sent
+    * @var  HTTP_Request2
+    */
+    protected $request;
+
+   /**
+    * Request body
+    * @var  string|resource|HTTP_Request2_MultipartBody
+    * @see  HTTP_Request2::getBody()
+    */
+    protected $requestBody;
+
+   /**
+    * Length of the request body
+    * @var  integer
+    */
+    protected $contentLength;
+
+   /**
+    * Sends request to the remote server and returns its response
+    *
+    * @param    HTTP_Request2
+    * @return   HTTP_Request2_Response
+    * @throws   HTTP_Request2_Exception
+    */
+    abstract public function sendRequest(HTTP_Request2 $request);
+
+   /**
+    * Calculates length of the request body, adds proper headers
+    *
+    * @param    array   associative array of request headers, this method will
+    *                   add proper 'Content-Length' and 'Content-Type' headers
+    *                   to this array (or remove them if not needed)
+    */
+    protected function calculateRequestLength(&$headers)
+    {
+        $this->requestBody = $this->request->getBody();
+
+        if (is_string($this->requestBody)) {
+            $this->contentLength = strlen($this->requestBody);
+        } elseif (is_resource($this->requestBody)) {
+            $stat = fstat($this->requestBody);
+            $this->contentLength = $stat['size'];
+            rewind($this->requestBody);
+        } else {
+            $this->contentLength = $this->requestBody->getLength();
+            $headers['content-type'] = 'multipart/form-data; boundary=' .
+                                       $this->requestBody->getBoundary();
+            $this->requestBody->rewind();
+        }
+
+        if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
+            0 == $this->contentLength
+        ) {
+            // No body: send a Content-Length header nonetheless (request #12900),
+            // but do that only for methods that require a body (bug #14740)
+            if (in_array($this->request->getMethod(), self::$bodyRequired)) {
+                $headers['content-length'] = 0;
+            } else {
+                unset($headers['content-length']);
+                // if the method doesn't require a body and doesn't have a
+                // body, don't send a Content-Type header. (request #16799)
+                unset($headers['content-type']);
+            }
+        } else {
+            if (empty($headers['content-type'])) {
+                $headers['content-type'] = 'application/x-www-form-urlencoded';
+            }
+            $headers['content-length'] = $this->contentLength;
+        }
+    }
+}
+?>
diff --git a/typo3/contrib/pear/HTTP/Request2/Adapter/Curl.php b/typo3/contrib/pear/HTTP/Request2/Adapter/Curl.php
new file mode 100644 (file)
index 0000000..5d2341a
--- /dev/null
@@ -0,0 +1,560 @@
+<?php
+/**
+ * Adapter for HTTP_Request2 wrapping around cURL extension
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: Curl.php 310800 2011-05-06 07:29:56Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Base class for HTTP_Request2 adapters
+ */
+require_once 'HTTP/Request2/Adapter.php';
+
+/**
+ * Adapter for HTTP_Request2 wrapping around cURL extension
+ *
+ * @category    HTTP
+ * @package     HTTP_Request2
+ * @author      Alexey Borzov <avb@php.net>
+ * @version     Release: 2.0.0RC1
+ */
+class HTTP_Request2_Adapter_Curl extends HTTP_Request2_Adapter
+{
+   /**
+    * Mapping of header names to cURL options
+    * @var  array
+    */
+    protected static $headerMap = array(
+        'accept-encoding' => CURLOPT_ENCODING,
+        'cookie'          => CURLOPT_COOKIE,
+        'referer'         => CURLOPT_REFERER,
+        'user-agent'      => CURLOPT_USERAGENT
+    );
+
+   /**
+    * Mapping of SSL context options to cURL options
+    * @var  array
+    */
+    protected static $sslContextMap = array(
+        'ssl_verify_peer' => CURLOPT_SSL_VERIFYPEER,
+        'ssl_cafile'      => CURLOPT_CAINFO,
+        'ssl_capath'      => CURLOPT_CAPATH,
+        'ssl_local_cert'  => CURLOPT_SSLCERT,
+        'ssl_passphrase'  => CURLOPT_SSLCERTPASSWD
+   );
+
+   /**
+    * Mapping of CURLE_* constants to Exception subclasses and error codes
+    * @var  array
+    */
+    protected static $errorMap = array(
+        CURLE_UNSUPPORTED_PROTOCOL  => array('HTTP_Request2_MessageException',
+                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),
+        CURLE_COULDNT_RESOLVE_PROXY => array('HTTP_Request2_ConnectionException'),
+        CURLE_COULDNT_RESOLVE_HOST  => array('HTTP_Request2_ConnectionException'),
+        CURLE_COULDNT_CONNECT       => array('HTTP_Request2_ConnectionException'),
+        // error returned from write callback
+        CURLE_WRITE_ERROR           => array('HTTP_Request2_MessageException',
+                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),
+        CURLE_OPERATION_TIMEOUTED   => array('HTTP_Request2_MessageException',
+                                             HTTP_Request2_Exception::TIMEOUT),
+        CURLE_HTTP_RANGE_ERROR      => array('HTTP_Request2_MessageException'),
+        CURLE_SSL_CONNECT_ERROR     => array('HTTP_Request2_ConnectionException'),
+        CURLE_LIBRARY_NOT_FOUND     => array('HTTP_Request2_LogicException',
+                                             HTTP_Request2_Exception::MISCONFIGURATION),
+        CURLE_FUNCTION_NOT_FOUND    => array('HTTP_Request2_LogicException',
+                                             HTTP_Request2_Exception::MISCONFIGURATION),
+        CURLE_ABORTED_BY_CALLBACK   => array('HTTP_Request2_MessageException',
+                                             HTTP_Request2_Exception::NON_HTTP_REDIRECT),
+        CURLE_TOO_MANY_REDIRECTS    => array('HTTP_Request2_MessageException',
+                                             HTTP_Request2_Exception::TOO_MANY_REDIRECTS),
+        CURLE_SSL_PEER_CERTIFICATE  => array('HTTP_Request2_ConnectionException'),
+        CURLE_GOT_NOTHING           => array('HTTP_Request2_MessageException'),
+        CURLE_SSL_ENGINE_NOTFOUND   => array('HTTP_Request2_LogicException',
+                                             HTTP_Request2_Exception::MISCONFIGURATION),
+        CURLE_SSL_ENGINE_SETFAILED  => array('HTTP_Request2_LogicException',
+                                             HTTP_Request2_Exception::MISCONFIGURATION),
+        CURLE_SEND_ERROR            => array('HTTP_Request2_MessageException'),
+        CURLE_RECV_ERROR            => array('HTTP_Request2_MessageException'),
+        CURLE_SSL_CERTPROBLEM       => array('HTTP_Request2_LogicException',
+                                             HTTP_Request2_Exception::INVALID_ARGUMENT),
+        CURLE_SSL_CIPHER            => array('HTTP_Request2_ConnectionException'),
+        CURLE_SSL_CACERT            => array('HTTP_Request2_ConnectionException'),
+        CURLE_BAD_CONTENT_ENCODING  => array('HTTP_Request2_MessageException'),
+    );
+
+   /**
+    * Response being received
+    * @var  HTTP_Request2_Response
+    */
+    protected $response;
+
+   /**
+    * Whether 'sentHeaders' event was sent to observers
+    * @var  boolean
+    */
+    protected $eventSentHeaders = false;
+
+   /**
+    * Whether 'receivedHeaders' event was sent to observers
+    * @var boolean
+    */
+    protected $eventReceivedHeaders = false;
+
+   /**
+    * Position within request body
+    * @var  integer
+    * @see  callbackReadBody()
+    */
+    protected $position = 0;
+
+   /**
+    * Information about last transfer, as returned by curl_getinfo()
+    * @var  array
+    */
+    protected $lastInfo;
+
+   /**
+    * Creates a subclass of HTTP_Request2_Exception from curl error data
+    *
+    * @param resource curl handle
+    * @return HTTP_Request2_Exception
+    */
+    protected static function wrapCurlError($ch)
+    {
+        $nativeCode = curl_errno($ch);
+        $message    = 'Curl error: ' . curl_error($ch);
+        if (!isset(self::$errorMap[$nativeCode])) {
+            return new HTTP_Request2_Exception($message, 0, $nativeCode);
+        } else {
+            $class = self::$errorMap[$nativeCode][0];
+            $code  = empty(self::$errorMap[$nativeCode][1])
+                     ? 0 : self::$errorMap[$nativeCode][1];
+            return new $class($message, $code, $nativeCode);
+        }
+    }
+
+   /**
+    * Sends request to the remote server and returns its response
+    *
+    * @param    HTTP_Request2
+    * @return   HTTP_Request2_Response
+    * @throws   HTTP_Request2_Exception
+    */
+    public function sendRequest(HTTP_Request2 $request)
+    {
+        if (!extension_loaded('curl')) {
+            throw new HTTP_Request2_LogicException(
+                'cURL extension not available', HTTP_Request2_Exception::MISCONFIGURATION
+            );
+        }
+
+        $this->request              = $request;
+        $this->response             = null;
+        $this->position             = 0;
+        $this->eventSentHeaders     = false;
+        $this->eventReceivedHeaders = false;
+
+        try {
+            if (false === curl_exec($ch = $this->createCurlHandle())) {
+                $e = self::wrapCurlError($ch);
+            }
+        } catch (Exception $e) {
+        }
+        if (isset($ch)) {
+            $this->lastInfo = curl_getinfo($ch);
+            curl_close($ch);
+        }
+
+        $response = $this->response;
+        unset($this->request, $this->requestBody, $this->response);
+
+        if (!empty($e)) {
+            throw $e;
+        }
+
+        if ($jar = $request->getCookieJar()) {
+            $jar->addCookiesFromResponse($response, $request->getUrl());
+        }
+
+        if (0 < $this->lastInfo['size_download']) {
+            $request->setLastEvent('receivedBody', $response);
+        }
+        return $response;
+    }
+
+   /**
+    * Returns information about last transfer
+    *
+    * @return   array   associative array as returned by curl_getinfo()
+    */
+    public function getInfo()
+    {
+        return $this->lastInfo;
+    }
+
+   /**
+    * Creates a new cURL handle and populates it with data from the request
+    *
+    * @return   resource    a cURL handle, as created by curl_init()
+    * @throws   HTTP_Request2_LogicException
+    */
+    protected function createCurlHandle()
+    {
+        $ch = curl_init();
+
+        curl_setopt_array($ch, array(
+            // setup write callbacks
+            CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'),
+            CURLOPT_WRITEFUNCTION  => array($this, 'callbackWriteBody'),
+            // buffer size
+            CURLOPT_BUFFERSIZE     => $this->request->getConfig('buffer_size'),
+            // connection timeout
+            CURLOPT_CONNECTTIMEOUT => $this->request->getConfig('connect_timeout'),
+            // save full outgoing headers, in case someone is interested
+            CURLINFO_HEADER_OUT    => true,
+            // request url
+            CURLOPT_URL            => $this->request->getUrl()->getUrl()
+        ));
+
+        // set up redirects
+        if (!$this->request->getConfig('follow_redirects')) {
+            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
+        } else {
+            if (!@curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true)) {
+                throw new HTTP_Request2_LogicException(
+                    'Redirect support in curl is unavailable due to open_basedir or safe_mode setting',
+                    HTTP_Request2_Exception::MISCONFIGURATION
+                );
+            }
+            curl_setopt($ch, CURLOPT_MAXREDIRS, $this->request->getConfig('max_redirects'));
+            // limit redirects to http(s), works in 5.2.10+
+            if (defined('CURLOPT_REDIR_PROTOCOLS')) {
+                curl_setopt($ch, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
+            }
+            // works in 5.3.2+, http://bugs.php.net/bug.php?id=49571
+            if ($this->request->getConfig('strict_redirects') && defined('CURLOPT_POSTREDIR')) {
+                curl_setopt($ch, CURLOPT_POSTREDIR, 3);
+            }
+        }
+
+        // request timeout
+        if ($timeout = $this->request->getConfig('timeout')) {
+            curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
+        }
+
+        // set HTTP version
+        switch ($this->request->getConfig('protocol_version')) {
+            case '1.0':
+                curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
+                break;
+            case '1.1':
+                curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
+        }
+
+        // set request method
+        switch ($this->request->getMethod()) {
+            case HTTP_Request2::METHOD_GET:
+                curl_setopt($ch, CURLOPT_HTTPGET, true);
+                break;
+            case HTTP_Request2::METHOD_POST:
+                curl_setopt($ch, CURLOPT_POST, true);
+                break;
+            case HTTP_Request2::METHOD_HEAD:
+                curl_setopt($ch, CURLOPT_NOBODY, true);
+                break;
+            case HTTP_Request2::METHOD_PUT:
+                curl_setopt($ch, CURLOPT_UPLOAD, true);
+                break;
+            default:
+                curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $this->request->getMethod());
+        }
+
+        // set proxy, if needed
+        if ($host = $this->request->getConfig('proxy_host')) {
+            if (!($port = $this->request->getConfig('proxy_port'))) {
+                throw new HTTP_Request2_LogicException(
+                    'Proxy port not provided', HTTP_Request2_Exception::MISSING_VALUE
+                );
+            }
+            curl_setopt($ch, CURLOPT_PROXY, $host . ':' . $port);
+            if ($user = $this->request->getConfig('proxy_user')) {
+                curl_setopt($ch, CURLOPT_PROXYUSERPWD, $user . ':' .
+                            $this->request->getConfig('proxy_password'));
+                switch ($this->request->getConfig('proxy_auth_scheme')) {
+                    case HTTP_Request2::AUTH_BASIC:
+                        curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_BASIC);
+                        break;
+                    case HTTP_Request2::AUTH_DIGEST:
+                        curl_setopt($ch, CURLOPT_PROXYAUTH, CURLAUTH_DIGEST);
+                }
+            }
+        }
+
+        // set authentication data
+        if ($auth = $this->request->getAuth()) {
+            curl_setopt($ch, CURLOPT_USERPWD, $auth['user'] . ':' . $auth['password']);
+            switch ($auth['scheme']) {
+                case HTTP_Request2::AUTH_BASIC:
+                    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
+                    break;
+                case HTTP_Request2::AUTH_DIGEST:
+                    curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
+            }
+        }
+
+        // set SSL options
+        foreach ($this->request->getConfig() as $name => $value) {
+            if ('ssl_verify_host' == $name && null !== $value) {
+                curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, $value? 2: 0);
+            } elseif (isset(self::$sslContextMap[$name]) && null !== $value) {
+                curl_setopt($ch, self::$sslContextMap[$name], $value);
+            }
+        }
+
+        $headers = $this->request->getHeaders();
+        // make cURL automagically send proper header
+        if (!isset($headers['accept-encoding'])) {
+            $headers['accept-encoding'] = '';
+        }
+
+        if (($jar = $this->request->getCookieJar())
+            && ($cookies = $jar->getMatching($this->request->getUrl(), true))
+        ) {
+            $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
+        }
+
+        // set headers having special cURL keys
+        foreach (self::$headerMap as $name => $option) {
+            if (isset($headers[$name])) {
+                curl_setopt($ch, $option, $headers[$name]);
+                unset($headers[$name]);
+            }
+        }
+
+        $this->calculateRequestLength($headers);
+        if (isset($headers['content-length'])) {
+            $this->workaroundPhpBug47204($ch, $headers);
+        }
+
+        // set headers not having special keys
+        $headersFmt = array();
+        foreach ($headers as $name => $value) {
+            $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
+            $headersFmt[]  = $canonicalName . ': ' . $value;
+        }
+        curl_setopt($ch, CURLOPT_HTTPHEADER, $headersFmt);
+
+        return $ch;
+    }
+
+   /**
+    * Workaround for PHP bug #47204 that prevents rewinding request body
+    *
+    * The workaround consists of reading the entire request body into memory
+    * and setting it as CURLOPT_POSTFIELDS, so it isn't recommended for large
+    * file uploads, use Socket adapter instead.
+    *
+    * @param    resource    cURL handle
+    * @param    array       Request headers
+    */
+    protected function workaroundPhpBug47204($ch, &$headers)
+    {
+        // no redirects, no digest auth -> probably no rewind needed
+        if (!$this->request->getConfig('follow_redirects')
+            && (!($auth = $this->request->getAuth())
+                || HTTP_Request2::AUTH_DIGEST != $auth['scheme'])
+        ) {
+            curl_setopt($ch, CURLOPT_READFUNCTION, array($this, 'callbackReadBody'));
+
+        // rewind may be needed, read the whole body into memory
+        } else {
+            if ($this->requestBody instanceof HTTP_Request2_MultipartBody) {
+                $this->requestBody = $this->requestBody->__toString();
+
+            } elseif (is_resource($this->requestBody)) {
+                $fp = $this->requestBody;
+                $this->requestBody = '';
+                while (!feof($fp)) {
+                    $this->requestBody .= fread($fp, 16384);
+                }
+            }
+            // curl hangs up if content-length is present
+            unset($headers['content-length']);
+            curl_setopt($ch, CURLOPT_POSTFIELDS, $this->requestBody);
+        }
+    }
+
+   /**
+    * Callback function called by cURL for reading the request body
+    *
+    * @param    resource    cURL handle
+    * @param    resource    file descriptor (not used)
+    * @param    integer     maximum length of data to return
+    * @return   string      part of the request body, up to $length bytes
+    */
+    protected function callbackReadBody($ch, $fd, $length)
+    {
+        if (!$this->eventSentHeaders) {
+            $this->request->setLastEvent(
+                'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
+            );
+            $this->eventSentHeaders = true;
+        }
+        if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
+            0 == $this->contentLength || $this->position >= $this->contentLength
+        ) {
+            return '';
+        }
+        if (is_string($this->requestBody)) {
+            $string = substr($this->requestBody, $this->position, $length);
+        } elseif (is_resource($this->requestBody)) {
+            $string = fread($this->requestBody, $length);
+        } else {
+            $string = $this->requestBody->read($length);
+        }
+        $this->request->setLastEvent('sentBodyPart', strlen($string));
+        $this->position += strlen($string);
+        return $string;
+    }
+
+   /**
+    * Callback function called by cURL for saving the response headers
+    *
+    * @param    resource    cURL handle
+    * @param    string      response header (with trailing CRLF)
+    * @return   integer     number of bytes saved
+    * @see      HTTP_Request2_Response::parseHeaderLine()
+    */
+    protected function callbackWriteHeader($ch, $string)
+    {
+        // we may receive a second set of headers if doing e.g. digest auth
+        if ($this->eventReceivedHeaders || !$this->eventSentHeaders) {
+            // don't bother with 100-Continue responses (bug #15785)
+            if (!$this->eventSentHeaders ||
+                $this->response->getStatus() >= 200
+            ) {
+                $this->request->setLastEvent(
+                    'sentHeaders', curl_getinfo($ch, CURLINFO_HEADER_OUT)
+                );
+            }
+            $upload = curl_getinfo($ch, CURLINFO_SIZE_UPLOAD);
+            // if body wasn't read by a callback, send event with total body size
+            if ($upload > $this->position) {
+                $this->request->setLastEvent(
+                    'sentBodyPart', $upload - $this->position
+                );
+                $this->position = $upload;
+            }
+            if ($upload && (!$this->eventSentHeaders
+                            || $this->response->getStatus() >= 200)
+            ) {
+                $this->request->setLastEvent('sentBody', $upload);
+            }
+            $this->eventSentHeaders = true;
+            // we'll need a new response object
+            if ($this->eventReceivedHeaders) {
+                $this->eventReceivedHeaders = false;
+                $this->response             = null;
+            }
+        }
+        if (empty($this->response)) {
+            $this->response = new HTTP_Request2_Response(
+                $string, false, curl_getinfo($ch, CURLINFO_EFFECTIVE_URL)
+            );
+        } else {
+            $this->response->parseHeaderLine($string);
+            if ('' == trim($string)) {
+                // don't bother with 100-Continue responses (bug #15785)
+                if (200 <= $this->response->getStatus()) {
+                    $this->request->setLastEvent('receivedHeaders', $this->response);
+                }
+
+                if ($this->request->getConfig('follow_redirects') && $this->response->isRedirect()) {
+                    $redirectUrl = new Net_URL2($this->response->getHeader('location'));
+
+                    // for versions lower than 5.2.10, check the redirection URL protocol
+                    if (!defined('CURLOPT_REDIR_PROTOCOLS') && $redirectUrl->isAbsolute()
+                        && !in_array($redirectUrl->getScheme(), array('http', 'https'))
+                    ) {
+                        return -1;
+                    }
+
+                    if ($jar = $this->request->getCookieJar()) {
+                        $jar->addCookiesFromResponse($this->response, $this->request->getUrl());
+                        if (!$redirectUrl->isAbsolute()) {
+                            $redirectUrl = $this->request->getUrl()->resolve($redirectUrl);
+                        }
+                        if ($cookies = $jar->getMatching($redirectUrl, true)) {
+                            curl_setopt($ch, CURLOPT_COOKIE, $cookies);
+                        }
+                    }
+                }
+                $this->eventReceivedHeaders = true;
+            }
+        }
+        return strlen($string);
+    }
+
+   /**
+    * Callback function called by cURL for saving the response body
+    *
+    * @param    resource    cURL handle (not used)
+    * @param    string      part of the response body
+    * @return   integer     number of bytes saved
+    * @see      HTTP_Request2_Response::appendBody()
+    */
+    protected function callbackWriteBody($ch, $string)
+    {
+        // cURL calls WRITEFUNCTION callback without calling HEADERFUNCTION if
+        // response doesn't start with proper HTTP status line (see bug #15716)
+        if (empty($this->response)) {
+            throw new HTTP_Request2_MessageException(
+                "Malformed response: {$string}",
+                HTTP_Request2_Exception::MALFORMED_RESPONSE
+            );
+        }
+        if ($this->request->getConfig('store_body')) {
+            $this->response->appendBody($string);
+        }
+        $this->request->setLastEvent('receivedBodyPart', $string);
+        return strlen($string);
+    }
+}
+?>
diff --git a/typo3/contrib/pear/HTTP/Request2/Adapter/Mock.php b/typo3/contrib/pear/HTTP/Request2/Adapter/Mock.php
new file mode 100644 (file)
index 0000000..f29144f
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+/**
+ * Mock adapter intended for testing
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: Mock.php 308322 2011-02-14 13:58:03Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Base class for HTTP_Request2 adapters
+ */
+require_once 'HTTP/Request2/Adapter.php';
+
+/**
+ * Mock adapter intended for testing
+ *
+ * Can be used to test applications depending on HTTP_Request2 package without
+ * actually performing any HTTP requests. This adapter will return responses
+ * previously added via addResponse()
+ * <code>
+ * $mock = new HTTP_Request2_Adapter_Mock();
+ * $mock->addResponse("HTTP/1.1 ... ");
+ *
+ * $request = new HTTP_Request2();
+ * $request->setAdapter($mock);
+ *
+ * // This will return the response set above
+ * $response = $req->send();
+ * </code>
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @version    Release: 2.0.0RC1
+ */
+class HTTP_Request2_Adapter_Mock extends HTTP_Request2_Adapter
+{
+   /**
+    * A queue of responses to be returned by sendRequest()
+    * @var  array
+    */
+    protected $responses = array();
+
+   /**
+    * Returns the next response from the queue built by addResponse()
+    *
+    * If the queue is empty it will return default empty response with status 400,
+    * if an Exception object was added to the queue it will be thrown.
+    *
+    * @param    HTTP_Request2
+    * @return   HTTP_Request2_Response
+    * @throws   Exception
+    */
+    public function sendRequest(HTTP_Request2 $request)
+    {
+        if (count($this->responses) > 0) {
+            $response = array_shift($this->responses);
+            if ($response instanceof HTTP_Request2_Response) {
+                return $response;
+            } else {
+                // rethrow the exception
+                $class   = get_class($response);
+                $message = $response->getMessage();
+                $code    = $response->getCode();
+                throw new $class($message, $code);
+            }
+        } else {
+            return self::createResponseFromString("HTTP/1.1 400 Bad Request\r\n\r\n");
+        }
+    }
+
+   /**
+    * Adds response to the queue
+    *
+    * @param    mixed   either a string, a pointer to an open file,
+    *                   an instance of HTTP_Request2_Response or Exception
+    * @throws   HTTP_Request2_Exception
+    */
+    public function addResponse($response)
+    {
+        if (is_string($response)) {
+            $response = self::createResponseFromString($response);
+        } elseif (is_resource($response)) {
+            $response = self::createResponseFromFile($response);
+        } elseif (!$response instanceof HTTP_Request2_Response &&
+                  !$response instanceof Exception
+        ) {
+            throw new HTTP_Request2_Exception('Parameter is not a valid response');
+        }
+        $this->responses[] = $response;
+    }
+
+   /**
+    * Creates a new HTTP_Request2_Response object from a string
+    *
+    * @param    string
+    * @return   HTTP_Request2_Response
+    * @throws   HTTP_Request2_Exception
+    */
+    public static function createResponseFromString($str)
+    {
+        $parts       = preg_split('!(\r?\n){2}!m', $str, 2);
+        $headerLines = explode("\n", $parts[0]);
+        $response    = new HTTP_Request2_Response(array_shift($headerLines));
+        foreach ($headerLines as $headerLine) {
+            $response->parseHeaderLine($headerLine);
+        }
+        $response->parseHeaderLine('');
+        if (isset($parts[1])) {
+            $response->appendBody($parts[1]);
+        }
+        return $response;
+    }
+
+   /**
+    * Creates a new HTTP_Request2_Response object from a file
+    *
+    * @param    resource    file pointer returned by fopen()
+    * @return   HTTP_Request2_Response
+    * @throws   HTTP_Request2_Exception
+    */
+    public static function createResponseFromFile($fp)
+    {
+        $response = new HTTP_Request2_Response(fgets($fp));
+        do {
+            $headerLine = fgets($fp);
+            $response->parseHeaderLine($headerLine);
+        } while ('' != trim($headerLine));
+
+        while (!feof($fp)) {
+            $response->appendBody(fread($fp, 8192));
+        }
+        return $response;
+    }
+}
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/HTTP/Request2/Adapter/Socket.php b/typo3/contrib/pear/HTTP/Request2/Adapter/Socket.php
new file mode 100644 (file)
index 0000000..cd7d29c
--- /dev/null
@@ -0,0 +1,1084 @@
+<?php
+/**
+ * Socket-based adapter for HTTP_Request2
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: Socket.php 309921 2011-04-03 16:43:02Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Base class for HTTP_Request2 adapters
+ */
+require_once 'HTTP/Request2/Adapter.php';
+
+/**
+ * Socket-based adapter for HTTP_Request2
+ *
+ * This adapter uses only PHP sockets and will work on almost any PHP
+ * environment. Code is based on original HTTP_Request PEAR package.
+ *
+ * @category    HTTP
+ * @package     HTTP_Request2
+ * @author      Alexey Borzov <avb@php.net>
+ * @version     Release: 2.0.0RC1
+ */
+class HTTP_Request2_Adapter_Socket extends HTTP_Request2_Adapter
+{
+   /**
+    * Regular expression for 'token' rule from RFC 2616
+    */
+    const REGEXP_TOKEN = '[^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+';
+
+   /**
+    * Regular expression for 'quoted-string' rule from RFC 2616
+    */
+    const REGEXP_QUOTED_STRING = '"(?:\\\\.|[^\\\\"])*"';
+
+   /**
+    * Connected sockets, needed for Keep-Alive support
+    * @var  array
+    * @see  connect()
+    */
+    protected static $sockets = array();
+
+   /**
+    * Data for digest authentication scheme
+    *
+    * The keys for the array are URL prefixes.
+    *
+    * The values are associative arrays with data (realm, nonce, nonce-count,
+    * opaque...) needed for digest authentication. Stored here to prevent making
+    * duplicate requests to digest-protected resources after we have already
+    * received the challenge.
+    *
+    * @var  array
+    */
+    protected static $challenges = array();
+
+   /**
+    * Connected socket
+    * @var  resource
+    * @see  connect()
+    */
+    protected $socket;
+
+   /**
+    * Challenge used for server digest authentication
+    * @var  array
+    */
+    protected $serverChallenge;
+
+   /**
+    * Challenge used for proxy digest authentication
+    * @var  array
+    */
+    protected $proxyChallenge;
+
+   /**
+    * Sum of start time and global timeout, exception will be thrown if request continues past this time
+    * @var  integer
+    */
+    protected $deadline = null;
+
+   /**
+    * Remaining length of the current chunk, when reading chunked response
+    * @var  integer
+    * @see  readChunked()
+    */
+    protected $chunkLength = 0;
+
+   /**
+    * Remaining amount of redirections to follow
+    *
+    * Starts at 'max_redirects' configuration parameter and is reduced on each
+    * subsequent redirect. An Exception will be thrown once it reaches zero.
+    *
+    * @var  integer
+    */
+    protected $redirectCountdown = null;
+
+   /**
+    * Sends request to the remote server and returns its response
+    *
+    * @param    HTTP_Request2
+    * @return   HTTP_Request2_Response
+    * @throws   HTTP_Request2_Exception
+    */
+    public function sendRequest(HTTP_Request2 $request)
+    {
+        $this->request = $request;
+
+        // Use global request timeout if given, see feature requests #5735, #8964
+        if ($timeout = $request->getConfig('timeout')) {
+            $this->deadline = time() + $timeout;
+        } else {
+            $this->deadline = null;
+        }
+
+        try {
+            $keepAlive = $this->connect();
+            $headers   = $this->prepareHeaders();
+            if (false === @fwrite($this->socket, $headers, strlen($headers))) {
+                throw new HTTP_Request2_MessageException('Error writing request');
+            }
+            // provide request headers to the observer, see request #7633
+            $this->request->setLastEvent('sentHeaders', $headers);
+            $this->writeBody();
+
+            if ($this->deadline && time() > $this->deadline) {
+                throw new HTTP_Request2_MessageException(
+                    'Request timed out after ' .
+                    $request->getConfig('timeout') . ' second(s)',
+                    HTTP_Request2_Exception::TIMEOUT
+                );
+            }
+
+            $response = $this->readResponse();
+
+            if ($jar = $request->getCookieJar()) {
+                $jar->addCookiesFromResponse($response, $request->getUrl());
+            }
+
+            if (!$this->canKeepAlive($keepAlive, $response)) {
+                $this->disconnect();
+            }
+
+            if ($this->shouldUseProxyDigestAuth($response)) {
+                return $this->sendRequest($request);
+            }
+            if ($this->shouldUseServerDigestAuth($response)) {
+                return $this->sendRequest($request);
+            }
+            if ($authInfo = $response->getHeader('authentication-info')) {
+                $this->updateChallenge($this->serverChallenge, $authInfo);
+            }
+            if ($proxyInfo = $response->getHeader('proxy-authentication-info')) {
+                $this->updateChallenge($this->proxyChallenge, $proxyInfo);
+            }
+
+        } catch (Exception $e) {
+            $this->disconnect();
+        }
+
+        unset($this->request, $this->requestBody);
+
+        if (!empty($e)) {
+            $this->redirectCountdown = null;
+            throw $e;
+        }
+
+        if (!$request->getConfig('follow_redirects') || !$response->isRedirect()) {
+            $this->redirectCountdown = null;
+            return $response;
+        } else {
+            return $this->handleRedirect($request, $response);
+        }
+    }
+
+   /**
+    * Connects to the remote server
+    *
+    * @return   bool    whether the connection can be persistent
+    * @throws   HTTP_Request2_Exception
+    */
+    protected function connect()
+    {
+        $secure  = 0 == strcasecmp($this->request->getUrl()->getScheme(), 'https');
+        $tunnel  = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
+        $headers = $this->request->getHeaders();
+        $reqHost = $this->request->getUrl()->getHost();
+        if (!($reqPort = $this->request->getUrl()->getPort())) {
+            $reqPort = $secure? 443: 80;
+        }
+
+        if ($host = $this->request->getConfig('proxy_host')) {
+            if (!($port = $this->request->getConfig('proxy_port'))) {
+                throw new HTTP_Request2_LogicException(
+                    'Proxy port not provided',
+                    HTTP_Request2_Exception::MISSING_VALUE
+                );
+            }
+            $proxy = true;
+        } else {
+            $host  = $reqHost;
+            $port  = $reqPort;
+            $proxy = false;
+        }
+
+        if ($tunnel && !$proxy) {
+            throw new HTTP_Request2_LogicException(
+                "Trying to perform CONNECT request without proxy",
+                HTTP_Request2_Exception::MISSING_VALUE
+            );
+        }
+        if ($secure && !in_array('ssl', stream_get_transports())) {
+            throw new HTTP_Request2_LogicException(
+                'Need OpenSSL support for https:// requests',
+                HTTP_Request2_Exception::MISCONFIGURATION
+            );
+        }
+
+        // RFC 2068, section 19.7.1: A client MUST NOT send the Keep-Alive
+        // connection token to a proxy server...
+        if ($proxy && !$secure &&
+            !empty($headers['connection']) && 'Keep-Alive' == $headers['connection']
+        ) {
+            $this->request->setHeader('connection');
+        }
+
+        $keepAlive = ('1.1' == $this->request->getConfig('protocol_version') &&
+                      empty($headers['connection'])) ||
+                     (!empty($headers['connection']) &&
+                      'Keep-Alive' == $headers['connection']);
+        $host = ((!$secure || $proxy)? 'tcp://': 'ssl://') . $host;
+
+        $options = array();
+        if ($secure || $tunnel) {
+            foreach ($this->request->getConfig() as $name => $value) {
+                if ('ssl_' == substr($name, 0, 4) && null !== $value) {
+                    if ('ssl_verify_host' == $name) {
+                        if ($value) {
+                            $options['CN_match'] = $reqHost;
+                        }
+                    } else {
+                        $options[substr($name, 4)] = $value;
+                    }
+                }
+            }
+            ksort($options);
+        }
+
+        // Changing SSL context options after connection is established does *not*
+        // work, we need a new connection if options change
+        $remote    = $host . ':' . $port;
+        $socketKey = $remote . (($secure && $proxy)? "->{$reqHost}:{$reqPort}": '') .
+                     (empty($options)? '': ':' . serialize($options));
+        unset($this->socket);
+
+        // We use persistent connections and have a connected socket?
+        // Ensure that the socket is still connected, see bug #16149
+        if ($keepAlive && !empty(self::$sockets[$socketKey]) &&
+            !feof(self::$sockets[$socketKey])
+        ) {
+            $this->socket =& self::$sockets[$socketKey];
+
+        } elseif ($secure && $proxy && !$tunnel) {
+            $this->establishTunnel();
+            $this->request->setLastEvent(
+                'connect', "ssl://{$reqHost}:{$reqPort} via {$host}:{$port}"
+            );
+            self::$sockets[$socketKey] =& $this->socket;
+
+        } else {
+            // Set SSL context options if doing HTTPS request or creating a tunnel
+            $context = stream_context_create();
+            foreach ($options as $name => $value) {
+                if (!stream_context_set_option($context, 'ssl', $name, $value)) {
+                    throw new HTTP_Request2_LogicException(
+                        "Error setting SSL context option '{$name}'"
+                    );
+                }
+            }
+            $track = @ini_set('track_errors', 1);
+            $this->socket = @stream_socket_client(
+                $remote, $errno, $errstr,
+                $this->request->getConfig('connect_timeout'),
+                STREAM_CLIENT_CONNECT, $context
+            );
+            if (!$this->socket) {
+                $e = new HTTP_Request2_ConnectionException(
+                    "Unable to connect to {$remote}. Error: "
+                     . (empty($errstr)? $php_errormsg: $errstr), 0, $errno
+                );
+            }
+            @ini_set('track_errors', $track);
+            if (isset($e)) {
+                throw $e;
+            }
+            $this->request->setLastEvent('connect', $remote);
+            self::$sockets[$socketKey] =& $this->socket;
+        }
+        return $keepAlive;
+    }
+
+   /**
+    * Establishes a tunnel to a secure remote server via HTTP CONNECT request
+    *
+    * This method will fail if 'ssl_verify_peer' is enabled. Probably because PHP
+    * sees that we are connected to a proxy server (duh!) rather than the server
+    * that presents its certificate.
+    *
+    * @link     http://tools.ietf.org/html/rfc2817#section-5.2
+    * @throws   HTTP_Request2_Exception
+    */
+    protected function establishTunnel()
+    {
+        $donor   = new self;
+        $connect = new HTTP_Request2(
+            $this->request->getUrl(), HTTP_Request2::METHOD_CONNECT,
+            array_merge($this->request->getConfig(),
+                        array('adapter' => $donor))
+        );
+        $response = $connect->send();
+        // Need any successful (2XX) response
+        if (200 > $response->getStatus() || 300 <= $response->getStatus()) {
+            throw new HTTP_Request2_ConnectionException(
+                'Failed to connect via HTTPS proxy. Proxy response: ' .
+                $response->getStatus() . ' ' . $response->getReasonPhrase()
+            );
+        }
+        $this->socket = $donor->socket;
+
+        $modes = array(
+            STREAM_CRYPTO_METHOD_TLS_CLIENT,
+            STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
+            STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
+            STREAM_CRYPTO_METHOD_SSLv2_CLIENT
+        );
+
+        foreach ($modes as $mode) {
+            if (stream_socket_enable_crypto($this->socket, true, $mode)) {
+                return;
+            }
+        }
+        throw new HTTP_Request2_ConnectionException(
+            'Failed to enable secure connection when connecting through proxy'
+        );
+    }
+
+   /**
+    * Checks whether current connection may be reused or should be closed
+    *
+    * @param    boolean                 whether connection could be persistent
+    *                                   in the first place
+    * @param    HTTP_Request2_Response  response object to check
+    * @return   boolean
+    */
+    protected function canKeepAlive($requestKeepAlive, HTTP_Request2_Response $response)
+    {
+        // Do not close socket on successful CONNECT request
+        if (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() &&
+            200 <= $response->getStatus() && 300 > $response->getStatus()
+        ) {
+            return true;
+        }
+
+        $lengthKnown = 'chunked' == strtolower($response->getHeader('transfer-encoding'))
+                       || null !== $response->getHeader('content-length')
+                       // no body possible for such responses, see also request #17031
+                       || HTTP_Request2::METHOD_HEAD == $this->request->getMethod()
+                       || in_array($response->getStatus(), array(204, 304));
+        $persistent  = 'keep-alive' == strtolower($response->getHeader('connection')) ||
+                       (null === $response->getHeader('connection') &&
+                        '1.1' == $response->getVersion());
+        return $requestKeepAlive && $lengthKnown && $persistent;
+    }
+
+   /**
+    * Disconnects from the remote server
+    */
+    protected function disconnect()
+    {
+        if (is_resource($this->socket)) {
+            fclose($this->socket);
+            $this->socket = null;
+            $this->request->setLastEvent('disconnect');
+        }
+    }
+
+   /**
+    * Handles HTTP redirection
+    *
+    * This method will throw an Exception if redirect to a non-HTTP(S) location
+    * is attempted, also if number of redirects performed already is equal to
+    * 'max_redirects' configuration parameter.
+    *
+    * @param    HTTP_Request2               Original request
+    * @param    HTTP_Request2_Response      Response containing redirect
+    * @return   HTTP_Request2_Response      Response from a new location
+    * @throws   HTTP_Request2_Exception
+    */
+    protected function handleRedirect(HTTP_Request2 $request,
+                                      HTTP_Request2_Response $response)
+    {
+        if (is_null($this->redirectCountdown)) {
+            $this->redirectCountdown = $request->getConfig('max_redirects');
+        }
+        if (0 == $this->redirectCountdown) {
+            $this->redirectCountdown = null;
+            // Copying cURL behaviour
+            throw new HTTP_Request2_MessageException (
+                'Maximum (' . $request->getConfig('max_redirects') . ') redirects followed',
+                HTTP_Request2_Exception::TOO_MANY_REDIRECTS
+            );
+        }
+        $redirectUrl = new Net_URL2(
+            $response->getHeader('location'),
+            array(Net_URL2::OPTION_USE_BRACKETS => $request->getConfig('use_brackets'))
+        );
+        // refuse non-HTTP redirect
+        if ($redirectUrl->isAbsolute()
+            && !in_array($redirectUrl->getScheme(), array('http', 'https'))
+        ) {
+            $this->redirectCountdown = null;
+            throw new HTTP_Request2_MessageException(
+                'Refusing to redirect to a non-HTTP URL ' . $redirectUrl->__toString(),
+                HTTP_Request2_Exception::NON_HTTP_REDIRECT
+            );
+        }
+        // Theoretically URL should be absolute (see http://tools.ietf.org/html/rfc2616#section-14.30),
+        // but in practice it is often not
+        if (!$redirectUrl->isAbsolute()) {
+            $redirectUrl = $request->getUrl()->resolve($redirectUrl);
+        }
+        $redirect = clone $request;
+        $redirect->setUrl($redirectUrl);
+        if (303 == $response->getStatus() || (!$request->getConfig('strict_redirects')
+             && in_array($response->getStatus(), array(301, 302)))
+        ) {
+            $redirect->setMethod(HTTP_Request2::METHOD_GET);
+            $redirect->setBody('');
+        }
+
+        if (0 < $this->redirectCountdown) {
+            $this->redirectCountdown--;
+        }
+        return $this->sendRequest($redirect);
+    }
+
+   /**
+    * Checks whether another request should be performed with server digest auth
+    *
+    * Several conditions should be satisfied for it to return true:
+    *   - response status should be 401
+    *   - auth credentials should be set in the request object
+    *   - response should contain WWW-Authenticate header with digest challenge
+    *   - there is either no challenge stored for this URL or new challenge
+    *     contains stale=true parameter (in other case we probably just failed
+    *     due to invalid username / password)
+    *
+    * The method stores challenge values in $challenges static property
+    *
+    * @param    HTTP_Request2_Response  response to check
+    * @return   boolean whether another request should be performed
+    * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters
+    */
+    protected function shouldUseServerDigestAuth(HTTP_Request2_Response $response)
+    {
+        // no sense repeating a request if we don't have credentials
+        if (401 != $response->getStatus() || !$this->request->getAuth()) {
+            return false;
+        }
+        if (!$challenge = $this->parseDigestChallenge($response->getHeader('www-authenticate'))) {
+            return false;
+        }
+
+        $url    = $this->request->getUrl();
+        $scheme = $url->getScheme();
+        $host   = $scheme . '://' . $url->getHost();
+        if ($port = $url->getPort()) {
+            if ((0 == strcasecmp($scheme, 'http') && 80 != $port) ||
+                (0 == strcasecmp($scheme, 'https') && 443 != $port)
+            ) {
+                $host .= ':' . $port;
+            }
+        }
+
+        if (!empty($challenge['domain'])) {
+            $prefixes = array();
+            foreach (preg_split('/\\s+/', $challenge['domain']) as $prefix) {
+                // don't bother with different servers
+                if ('/' == substr($prefix, 0, 1)) {
+                    $prefixes[] = $host . $prefix;
+                }
+            }
+        }
+        if (empty($prefixes)) {
+            $prefixes = array($host . '/');
+        }
+
+        $ret = true;
+        foreach ($prefixes as $prefix) {
+            if (!empty(self::$challenges[$prefix]) &&
+                (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
+            ) {
+                // probably credentials are invalid
+                $ret = false;
+            }
+            self::$challenges[$prefix] =& $challenge;
+        }
+        return $ret;
+    }
+
+   /**
+    * Checks whether another request should be performed with proxy digest auth
+    *
+    * Several conditions should be satisfied for it to return true:
+    *   - response status should be 407
+    *   - proxy auth credentials should be set in the request object
+    *   - response should contain Proxy-Authenticate header with digest challenge
+    *   - there is either no challenge stored for this proxy or new challenge
+    *     contains stale=true parameter (in other case we probably just failed
+    *     due to invalid username / password)
+    *
+    * The method stores challenge values in $challenges static property
+    *
+    * @param    HTTP_Request2_Response  response to check
+    * @return   boolean whether another request should be performed
+    * @throws   HTTP_Request2_Exception in case of unsupported challenge parameters
+    */
+    protected function shouldUseProxyDigestAuth(HTTP_Request2_Response $response)
+    {
+        if (407 != $response->getStatus() || !$this->request->getConfig('proxy_user')) {
+            return false;
+        }
+        if (!($challenge = $this->parseDigestChallenge($response->getHeader('proxy-authenticate')))) {
+            return false;
+        }
+
+        $key = 'proxy://' . $this->request->getConfig('proxy_host') .
+               ':' . $this->request->getConfig('proxy_port');
+
+        if (!empty(self::$challenges[$key]) &&
+            (empty($challenge['stale']) || strcasecmp('true', $challenge['stale']))
+        ) {
+            $ret = false;
+        } else {
+            $ret = true;
+        }
+        self::$challenges[$key] = $challenge;
+        return $ret;
+    }
+
+   /**
+    * Extracts digest method challenge from (WWW|Proxy)-Authenticate header value
+    *
+    * There is a problem with implementation of RFC 2617: several of the parameters
+    * are defined as quoted-string there and thus may contain backslash escaped
+    * double quotes (RFC 2616, section 2.2). However, RFC 2617 defines unq(X) as
+    * just value of quoted-string X without surrounding quotes, it doesn't speak
+    * about removing backslash escaping.
+    *
+    * Now realm parameter is user-defined and human-readable, strange things
+    * happen when it contains quotes:
+    *   - Apache allows quotes in realm, but apparently uses realm value without
+    *     backslashes for digest computation
+    *   - Squid allows (manually escaped) quotes there, but it is impossible to
+    *     authorize with either escaped or unescaped quotes used in digest,
+    *     probably it can't parse the response (?)
+    *   - Both IE and Firefox display realm value with backslashes in
+    *     the password popup and apparently use the same value for digest
+    *
+    * HTTP_Request2 follows IE and Firefox (and hopefully RFC 2617) in
+    * quoted-string handling, unfortunately that means failure to authorize
+    * sometimes
+    *
+    * @param    string  value of WWW-Authenticate or Proxy-Authenticate header
+    * @return   mixed   associative array with challenge parameters, false if
+    *                   no challenge is present in header value
+    * @throws   HTTP_Request2_NotImplementedException in case of unsupported challenge parameters
+    */
+    protected function parseDigestChallenge($headerValue)
+    {
+        $authParam   = '(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
+                       self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')';
+        $challenge   = "!(?<=^|\\s|,)Digest ({$authParam}\\s*(,\\s*|$))+!";
+        if (!preg_match($challenge, $headerValue, $matches)) {
+            return false;
+        }
+
+        preg_match_all('!' . $authParam . '!', $matches[0], $params);
+        $paramsAry   = array();
+        $knownParams = array('realm', 'domain', 'nonce', 'opaque', 'stale',
+                             'algorithm', 'qop');
+        for ($i = 0; $i < count($params[0]); $i++) {
+            // section 3.2.1: Any unrecognized directive MUST be ignored.
+            if (in_array($params[1][$i], $knownParams)) {
+                if ('"' == substr($params[2][$i], 0, 1)) {
+                    $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
+                } else {
+                    $paramsAry[$params[1][$i]] = $params[2][$i];
+                }
+            }
+        }
+        // we only support qop=auth
+        if (!empty($paramsAry['qop']) &&
+            !in_array('auth', array_map('trim', explode(',', $paramsAry['qop'])))
+        ) {
+            throw new HTTP_Request2_NotImplementedException(
+                "Only 'auth' qop is currently supported in digest authentication, " .
+                "server requested '{$paramsAry['qop']}'"
+            );
+        }
+        // we only support algorithm=MD5
+        if (!empty($paramsAry['algorithm']) && 'MD5' != $paramsAry['algorithm']) {
+            throw new HTTP_Request2_NotImplementedException(
+                "Only 'MD5' algorithm is currently supported in digest authentication, " .
+                "server requested '{$paramsAry['algorithm']}'"
+            );
+        }
+
+        return $paramsAry;
+    }
+
+   /**
+    * Parses [Proxy-]Authentication-Info header value and updates challenge
+    *
+    * @param    array   challenge to update
+    * @param    string  value of [Proxy-]Authentication-Info header
+    * @todo     validate server rspauth response
+    */
+    protected function updateChallenge(&$challenge, $headerValue)
+    {
+        $authParam   = '!(' . self::REGEXP_TOKEN . ')\\s*=\\s*(' .
+                       self::REGEXP_TOKEN . '|' . self::REGEXP_QUOTED_STRING . ')!';
+        $paramsAry   = array();
+
+        preg_match_all($authParam, $headerValue, $params);
+        for ($i = 0; $i < count($params[0]); $i++) {
+            if ('"' == substr($params[2][$i], 0, 1)) {
+                $paramsAry[$params[1][$i]] = substr($params[2][$i], 1, -1);
+            } else {
+                $paramsAry[$params[1][$i]] = $params[2][$i];
+            }
+        }
+        // for now, just update the nonce value
+        if (!empty($paramsAry['nextnonce'])) {
+            $challenge['nonce'] = $paramsAry['nextnonce'];
+            $challenge['nc']    = 1;
+        }
+    }
+
+   /**
+    * Creates a value for [Proxy-]Authorization header when using digest authentication
+    *
+    * @param    string  user name
+    * @param    string  password
+    * @param    string  request URL
+    * @param    array   digest challenge parameters
+    * @return   string  value of [Proxy-]Authorization request header
+    * @link     http://tools.ietf.org/html/rfc2617#section-3.2.2
+    */
+    protected function createDigestResponse($user, $password, $url, &$challenge)
+    {
+        if (false !== ($q = strpos($url, '?')) &&
+            $this->request->getConfig('digest_compat_ie')
+        ) {
+            $url = substr($url, 0, $q);
+        }
+
+        $a1 = md5($user . ':' . $challenge['realm'] . ':' . $password);
+        $a2 = md5($this->request->getMethod() . ':' . $url);
+
+        if (empty($challenge['qop'])) {
+            $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $a2);
+        } else {
+            $challenge['cnonce'] = 'Req2.' . rand();
+            if (empty($challenge['nc'])) {
+                $challenge['nc'] = 1;
+            }
+            $nc     = sprintf('%08x', $challenge['nc']++);
+            $digest = md5($a1 . ':' . $challenge['nonce'] . ':' . $nc . ':' .
+                          $challenge['cnonce'] . ':auth:' . $a2);
+        }
+        return 'Digest username="' . str_replace(array('\\', '"'), array('\\\\', '\\"'), $user) . '", ' .
+               'realm="' . $challenge['realm'] . '", ' .
+               'nonce="' . $challenge['nonce'] . '", ' .
+               'uri="' . $url . '", ' .
+               'response="' . $digest . '"' .
+               (!empty($challenge['opaque'])?
+                ', opaque="' . $challenge['opaque'] . '"':
+                '') .
+               (!empty($challenge['qop'])?
+                ', qop="auth", nc=' . $nc . ', cnonce="' . $challenge['cnonce'] . '"':
+                '');
+    }
+
+   /**
+    * Adds 'Authorization' header (if needed) to request headers array
+    *
+    * @param    array   request headers
+    * @param    string  request host (needed for digest authentication)
+    * @param    string  request URL (needed for digest authentication)
+    * @throws   HTTP_Request2_NotImplementedException
+    */
+    protected function addAuthorizationHeader(&$headers, $requestHost, $requestUrl)
+    {
+        if (!($auth = $this->request->getAuth())) {
+            return;
+        }
+        switch ($auth['scheme']) {
+            case HTTP_Request2::AUTH_BASIC:
+                $headers['authorization'] =
+                    'Basic ' . base64_encode($auth['user'] . ':' . $auth['password']);
+                break;
+
+            case HTTP_Request2::AUTH_DIGEST:
+                unset($this->serverChallenge);
+                $fullUrl = ('/' == $requestUrl[0])?
+                           $this->request->getUrl()->getScheme() . '://' .
+                            $requestHost . $requestUrl:
+                           $requestUrl;
+                foreach (array_keys(self::$challenges) as $key) {
+                    if ($key == substr($fullUrl, 0, strlen($key))) {
+                        $headers['authorization'] = $this->createDigestResponse(
+                            $auth['user'], $auth['password'],
+                            $requestUrl, self::$challenges[$key]
+                        );
+                        $this->serverChallenge =& self::$challenges[$key];
+                        break;
+                    }
+                }
+                break;
+
+            default:
+                throw new HTTP_Request2_NotImplementedException(
+                    "Unknown HTTP authentication scheme '{$auth['scheme']}'"
+                );
+        }
+    }
+
+   /**
+    * Adds 'Proxy-Authorization' header (if needed) to request headers array
+    *
+    * @param    array   request headers
+    * @param    string  request URL (needed for digest authentication)
+    * @throws   HTTP_Request2_NotImplementedException
+    */
+    protected function addProxyAuthorizationHeader(&$headers, $requestUrl)
+    {
+        if (!$this->request->getConfig('proxy_host') ||
+            !($user = $this->request->getConfig('proxy_user')) ||
+            (0 == strcasecmp('https', $this->request->getUrl()->getScheme()) &&
+             HTTP_Request2::METHOD_CONNECT != $this->request->getMethod())
+        ) {
+            return;
+        }
+
+        $password = $this->request->getConfig('proxy_password');
+        switch ($this->request->getConfig('proxy_auth_scheme')) {
+            case HTTP_Request2::AUTH_BASIC:
+                $headers['proxy-authorization'] =
+                    'Basic ' . base64_encode($user . ':' . $password);
+                break;
+
+            case HTTP_Request2::AUTH_DIGEST:
+                unset($this->proxyChallenge);
+                $proxyUrl = 'proxy://' . $this->request->getConfig('proxy_host') .
+                            ':' . $this->request->getConfig('proxy_port');
+                if (!empty(self::$challenges[$proxyUrl])) {
+                    $headers['proxy-authorization'] = $this->createDigestResponse(
+                        $user, $password,
+                        $requestUrl, self::$challenges[$proxyUrl]
+                    );
+                    $this->proxyChallenge =& self::$challenges[$proxyUrl];
+                }
+                break;
+
+            default:
+                throw new HTTP_Request2_NotImplementedException(
+                    "Unknown HTTP authentication scheme '" .
+                    $this->request->getConfig('proxy_auth_scheme') . "'"
+                );
+        }
+    }
+
+
+   /**
+    * Creates the string with the Request-Line and request headers
+    *
+    * @return   string
+    * @throws   HTTP_Request2_Exception
+    */
+    protected function prepareHeaders()
+    {
+        $headers = $this->request->getHeaders();
+        $url     = $this->request->getUrl();
+        $connect = HTTP_Request2::METHOD_CONNECT == $this->request->getMethod();
+        $host    = $url->getHost();
+
+        $defaultPort = 0 == strcasecmp($url->getScheme(), 'https')? 443: 80;
+        if (($port = $url->getPort()) && $port != $defaultPort || $connect) {
+            $host .= ':' . (empty($port)? $defaultPort: $port);
+        }
+        // Do not overwrite explicitly set 'Host' header, see bug #16146
+        if (!isset($headers['host'])) {
+            $headers['host'] = $host;
+        }
+
+        if ($connect) {
+            $requestUrl = $host;
+
+        } else {
+            if (!$this->request->getConfig('proxy_host') ||
+                0 == strcasecmp($url->getScheme(), 'https')
+            ) {
+                $requestUrl = '';
+            } else {
+                $requestUrl = $url->getScheme() . '://' . $host;
+            }
+            $path        = $url->getPath();
+            $query       = $url->getQuery();
+            $requestUrl .= (empty($path)? '/': $path) . (empty($query)? '': '?' . $query);
+        }
+
+        if ('1.1' == $this->request->getConfig('protocol_version') &&
+            extension_loaded('zlib') && !isset($headers['accept-encoding'])
+        ) {
+            $headers['accept-encoding'] = 'gzip, deflate';
+        }
+        if (($jar = $this->request->getCookieJar())
+            && ($cookies = $jar->getMatching($this->request->getUrl(), true))
+        ) {
+            $headers['cookie'] = (empty($headers['cookie'])? '': $headers['cookie'] . '; ') . $cookies;
+        }
+
+        $this->addAuthorizationHeader($headers, $host, $requestUrl);
+        $this->addProxyAuthorizationHeader($headers, $requestUrl);
+        $this->calculateRequestLength($headers);
+
+        $headersStr = $this->request->getMethod() . ' ' . $requestUrl . ' HTTP/' .
+                      $this->request->getConfig('protocol_version') . "\r\n";
+        foreach ($headers as $name => $value) {
+            $canonicalName = implode('-', array_map('ucfirst', explode('-', $name)));
+            $headersStr   .= $canonicalName . ': ' . $value . "\r\n";
+        }
+        return $headersStr . "\r\n";
+    }
+
+   /**
+    * Sends the request body
+    *
+    * @throws   HTTP_Request2_MessageException
+    */
+    protected function writeBody()
+    {
+        if (in_array($this->request->getMethod(), self::$bodyDisallowed) ||
+            0 == $this->contentLength
+        ) {
+            return;
+        }
+
+        $position   = 0;
+        $bufferSize = $this->request->getConfig('buffer_size');
+        while ($position < $this->contentLength) {
+            if (is_string($this->requestBody)) {
+                $str = substr($this->requestBody, $position, $bufferSize);
+            } elseif (is_resource($this->requestBody)) {
+                $str = fread($this->requestBody, $bufferSize);
+            } else {
+                $str = $this->requestBody->read($bufferSize);
+            }
+            if (false === @fwrite($this->socket, $str, strlen($str))) {
+                throw new HTTP_Request2_MessageException('Error writing request');
+            }
+            // Provide the length of written string to the observer, request #7630
+            $this->request->setLastEvent('sentBodyPart', strlen($str));
+            $position += strlen($str);
+        }
+        $this->request->setLastEvent('sentBody', $this->contentLength);
+    }
+
+   /**
+    * Reads the remote server's response
+    *
+    * @return   HTTP_Request2_Response
+    * @throws   HTTP_Request2_Exception
+    */
+    protected function readResponse()
+    {
+        $bufferSize = $this->request->getConfig('buffer_size');
+
+        do {
+            $response = new HTTP_Request2_Response(
+                $this->readLine($bufferSize), true, $this->request->getUrl()
+            );
+            do {
+                $headerLine = $this->readLine($bufferSize);
+                $response->parseHeaderLine($headerLine);
+            } while ('' != $headerLine);
+        } while (in_array($response->getStatus(), array(100, 101)));
+
+        $this->request->setLastEvent('receivedHeaders', $response);
+
+        // No body possible in such responses
+        if (HTTP_Request2::METHOD_HEAD == $this->request->getMethod() ||
+            (HTTP_Request2::METHOD_CONNECT == $this->request->getMethod() &&
+             200 <= $response->getStatus() && 300 > $response->getStatus()) ||
+            in_array($response->getStatus(), array(204, 304))
+        ) {
+            return $response;
+        }
+
+        $chunked = 'chunked' == $response->getHeader('transfer-encoding');
+        $length  = $response->getHeader('content-length');
+        $hasBody = false;
+        if ($chunked || null === $length || 0 < intval($length)) {
+            // RFC 2616, section 4.4:
+            // 3. ... If a message is received with both a
+            // Transfer-Encoding header field and a Content-Length header field,
+            // the latter MUST be ignored.
+            $toRead = ($chunked || null === $length)? null: $length;
+            $this->chunkLength = 0;
+
+            while (!feof($this->socket) && (is_null($toRead) || 0 < $toRead)) {
+                if ($chunked) {
+                    $data = $this->readChunked($bufferSize);
+                } elseif (is_null($toRead)) {
+                    $data = $this->fread($bufferSize);
+                } else {
+                    $data    = $this->fread(min($toRead, $bufferSize));
+                    $toRead -= strlen($data);
+                }
+                if ('' == $data && (!$this->chunkLength || feof($this->socket))) {
+                    break;
+                }
+
+                $hasBody = true;
+                if ($this->request->getConfig('store_body')) {
+                    $response->appendBody($data);
+                }
+                if (!in_array($response->getHeader('content-encoding'), array('identity', null))) {
+                    $this->request->setLastEvent('receivedEncodedBodyPart', $data);
+                } else {
+                    $this->request->setLastEvent('receivedBodyPart', $data);
+                }
+            }
+        }
+
+        if ($hasBody) {
+            $this->request->setLastEvent('receivedBody', $response);
+        }
+        return $response;
+    }
+
+   /**
+    * Reads until either the end of the socket or a newline, whichever comes first
+    *
+    * Strips the trailing newline from the returned data, handles global
+    * request timeout. Method idea borrowed from Net_Socket PEAR package.
+    *
+    * @param    int     buffer size to use for reading
+    * @return   Available data up to the newline (not including newline)
+    * @throws   HTTP_Request2_MessageException     In case of timeout
+    */
+    protected function readLine($bufferSize)
+    {
+        $line = '';
+        while (!feof($this->socket)) {
+            if ($this->deadline) {
+                stream_set_timeout($this->socket, max($this->deadline - time(), 1));
+            }
+            $line .= @fgets($this->socket, $bufferSize);
+            $info  = stream_get_meta_data($this->socket);
+            if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {
+                $reason = $this->deadline
+                          ? 'after ' . $this->request->getConfig('timeout') . ' second(s)'
+                          : 'due to default_socket_timeout php.ini setting';
+                throw new HTTP_Request2_MessageException(
+                    "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT
+                );
+            }
+            if (substr($line, -1) == "\n") {
+                return rtrim($line, "\r\n");
+            }
+        }
+        return $line;
+    }
+
+   /**
+    * Wrapper around fread(), handles global request timeout
+    *
+    * @param    int     Reads up to this number of bytes
+    * @return   Data read from socket
+    * @throws   HTTP_Request2_MessageException     In case of timeout
+    */
+    protected function fread($length)
+    {
+        if ($this->deadline) {
+            stream_set_timeout($this->socket, max($this->deadline - time(), 1));
+        }
+        $data = fread($this->socket, $length);
+        $info = stream_get_meta_data($this->socket);
+        if ($info['timed_out'] || $this->deadline && time() > $this->deadline) {
+            $reason = $this->deadline
+                      ? 'after ' . $this->request->getConfig('timeout') . ' second(s)'
+                      : 'due to default_socket_timeout php.ini setting';
+            throw new HTTP_Request2_MessageException(
+                "Request timed out {$reason}", HTTP_Request2_Exception::TIMEOUT
+            );
+        }
+        return $data;
+    }
+
+   /**
+    * Reads a part of response body encoded with chunked Transfer-Encoding
+    *
+    * @param    int     buffer size to use for reading
+    * @return   string
+    * @throws   HTTP_Request2_MessageException
+    */
+    protected function readChunked($bufferSize)
+    {
+        // at start of the next chunk?
+        if (0 == $this->chunkLength) {
+            $line = $this->readLine($bufferSize);
+            if (!preg_match('/^([0-9a-f]+)/i', $line, $matches)) {
+                throw new HTTP_Request2_MessageException(
+                    "Cannot decode chunked response, invalid chunk length '{$line}'",
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            } else {
+                $this->chunkLength = hexdec($matches[1]);
+                // Chunk with zero length indicates the end
+                if (0 == $this->chunkLength) {
+                    $this->readLine($bufferSize);
+                    return '';
+                }
+            }
+        }
+        $data = $this->fread(min($this->chunkLength, $bufferSize));
+        $this->chunkLength -= strlen($data);
+        if (0 == $this->chunkLength) {
+            $this->readLine($bufferSize); // Trailing CRLF
+        }
+        return $data;
+    }
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/HTTP/Request2/CookieJar.php b/typo3/contrib/pear/HTTP/Request2/CookieJar.php
new file mode 100644 (file)
index 0000000..36ca06d
--- /dev/null
@@ -0,0 +1,499 @@
+<?php
+/**
+ * Stores cookies and passes them between HTTP requests
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: CookieJar.php 308629 2011-02-24 17:34:24Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/** Class representing a HTTP request message */
+require_once 'HTTP/Request2.php';
+
+/**
+ * Stores cookies and passes them between HTTP requests
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @version    Release: 2.0.0RC1
+ */
+class HTTP_Request2_CookieJar implements Serializable
+{
+   /**
+    * Array of stored cookies
+    *
+    * The array is indexed by domain, path and cookie name
+    *   .example.com
+    *     /
+    *       some_cookie => cookie data
+    *     /subdir
+    *       other_cookie => cookie data
+    *   .example.org
+    *     ...
+    *
+    * @var array
+    */
+    protected $cookies = array();
+
+   /**
+    * Whether session cookies should be serialized when serializing the jar
+    * @var bool
+    */
+    protected $serializeSession = false;
+
+   /**
+    * Whether Public Suffix List should be used for domain matching
+    * @var bool
+    */
+    protected $useList = true;
+
+   /**
+    * Array with Public Suffix List data
+    * @var  array
+    * @link http://publicsuffix.org/
+    */
+    protected static $psl = array();
+
+   /**
+    * Class constructor, sets various options
+    *
+    * @param bool Controls serializing session cookies, see {@link serializeSessionCookies()}
+    * @param bool Controls using Public Suffix List, see {@link usePublicSuffixList()}
+    */
+    public function __construct($serializeSessionCookies = false, $usePublicSuffixList = true)
+    {
+        $this->serializeSessionCookies($serializeSessionCookies);
+        $this->usePublicSuffixList($usePublicSuffixList);
+    }
+
+   /**
+    * Returns current time formatted in ISO-8601 at UTC timezone
+    *
+    * @return string
+    */
+    protected function now()
+    {
+        $dt = new DateTime();
+        $dt->setTimezone(new DateTimeZone('UTC'));
+        return $dt->format(DateTime::ISO8601);
+    }
+
+   /**
+    * Checks cookie array for correctness, possibly updating its 'domain', 'path' and 'expires' fields
+    *
+    * The checks are as follows:
+    *   - cookie array should contain 'name' and 'value' fields;
+    *   - name and value should not contain disallowed symbols;
+    *   - 'expires' should be either empty parseable by DateTime;
+    *   - 'domain' and 'path' should be either not empty or an URL where
+    *     cookie was set should be provided.
+    *   - if $setter is provided, then document at that URL should be allowed
+    *     to set a cookie for that 'domain'. If $setter is not provided,
+    *     then no domain checks will be made.
+    *
+    * 'expires' field will be converted to ISO8601 format from COOKIE format,
+    * 'domain' and 'path' will be set from setter URL if empty.
+    *
+    * @param    array    cookie data, as returned by {@link HTTP_Request2_Response::getCookies()}
+    * @param    Net_URL2 URL of the document that sent Set-Cookie header
+    * @return   array    Updated cookie array
+    * @throws   HTTP_Request2_LogicException
+    * @throws   HTTP_Request2_MessageException
+    */
+    protected function checkAndUpdateFields(array $cookie, Net_URL2 $setter = null)
+    {
+        if ($missing = array_diff(array('name', 'value'), array_keys($cookie))) {
+            throw new HTTP_Request2_LogicException(
+                "Cookie array should contain 'name' and 'value' fields",
+                HTTP_Request2_Exception::MISSING_VALUE
+            );
+        }
+        if (preg_match(HTTP_Request2::REGEXP_INVALID_COOKIE, $cookie['name'])) {
+            throw new HTTP_Request2_LogicException(
+                "Invalid cookie name: '{$cookie['name']}'",
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+        if (preg_match(HTTP_Request2::REGEXP_INVALID_COOKIE, $cookie['value'])) {
+            throw new HTTP_Request2_LogicException(
+                "Invalid cookie value: '{$cookie['value']}'",
+                HTTP_Request2_Exception::INVALID_ARGUMENT
+            );
+        }
+        $cookie += array('domain' => '', 'path' => '', 'expires' => null, 'secure' => false);
+
+        // Need ISO-8601 date @ UTC timezone
+        if (!empty($cookie['expires'])
+            && !preg_match('/^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\+0000$/', $cookie['expires'])
+        ) {
+            try {
+                $dt = new DateTime($cookie['expires']);
+                $dt->setTimezone(new DateTimeZone('UTC'));
+                $cookie['expires'] = $dt->format(DateTime::ISO8601);
+            } catch (Exception $e) {
+                throw new HTTP_Request2_LogicException($e->getMessage());
+            }
+        }
+
+        if (empty($cookie['domain']) || empty($cookie['path'])) {
+            if (!$setter) {
+                throw new HTTP_Request2_LogicException(
+                    'Cookie misses domain and/or path component, cookie setter URL needed',
+                    HTTP_Request2_Exception::MISSING_VALUE
+                );
+            }
+            if (empty($cookie['domain'])) {
+                if ($host = $setter->getHost()) {
+                    $cookie['domain'] = $host;
+                } else {
+                    throw new HTTP_Request2_LogicException(
+                        'Setter URL does not contain host part, can\'t set cookie domain',
+                        HTTP_Request2_Exception::MISSING_VALUE
+                    );
+                }
+            }
+            if (empty($cookie['path'])) {
+                $path = $setter->getPath();
+                $cookie['path'] = empty($path)? '/': substr($path, 0, strrpos($path, '/') + 1);
+            }
+        }
+
+        if ($setter && !$this->domainMatch($setter->getHost(), $cookie['domain'])) {
+            throw new HTTP_Request2_MessageException(
+                "Domain " . $setter->getHost() . " cannot set cookies for "
+                . $cookie['domain']
+            );
+        }
+
+        return $cookie;
+    }
+
+   /**
+    * Stores a cookie in the jar
+    *
+    * @param    array    cookie data, as returned by {@link HTTP_Request2_Response::getCookies()}
+    * @param    Net_URL2 URL of the document that sent Set-Cookie header
+    * @throws   HTTP_Request2_Exception
+    */
+    public function store(array $cookie, Net_URL2 $setter = null)
+    {
+        $cookie = $this->checkAndUpdateFields($cookie, $setter);
+
+        if (strlen($cookie['value'])
+            && (is_null($cookie['expires']) || $cookie['expires'] > $this->now())
+        ) {
+            if (!isset($this->cookies[$cookie['domain']])) {
+                $this->cookies[$cookie['domain']] = array();
+            }
+            if (!isset($this->cookies[$cookie['domain']][$cookie['path']])) {
+                $this->cookies[$cookie['domain']][$cookie['path']] = array();
+            }
+            $this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']] = $cookie;
+
+        } elseif (isset($this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']])) {
+            unset($this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']]);
+        }
+    }
+
+   /**
+    * Adds cookies set in HTTP response to the jar
+    *
+    * @param HTTP_Request2_Response response
+    * @param Net_URL2               original request URL, needed for setting
+    *                               default domain/path
+    */
+    public function addCookiesFromResponse(HTTP_Request2_Response $response, Net_URL2 $setter)
+    {
+        foreach ($response->getCookies() as $cookie) {
+            $this->store($cookie, $setter);
+        }
+    }
+
+   /**
+    * Returns all cookies matching a given request URL
+    *
+    * The following checks are made:
+    *   - cookie domain should match request host
+    *   - cookie path should be a prefix for request path
+    *   - 'secure' cookies will only be sent for HTTPS requests
+    *
+    * @param  Net_URL2
+    * @param  bool      Whether to return cookies as string for "Cookie: " header
+    * @return array
+    */
+    public function getMatching(Net_URL2 $url, $asString = false)
+    {
+        $host   = $url->getHost();
+        $path   = $url->getPath();
+        $secure = 0 == strcasecmp($url->getScheme(), 'https');
+
+        $matched = $ret = array();
+        foreach (array_keys($this->cookies) as $domain) {
+            if ($this->domainMatch($host, $domain)) {
+                foreach (array_keys($this->cookies[$domain]) as $cPath) {
+                    if (0 === strpos($path, $cPath)) {
+                        foreach ($this->cookies[$domain][$cPath] as $name => $cookie) {
+                            if (!$cookie['secure'] || $secure) {
+                                $matched[$name][strlen($cookie['path'])] = $cookie;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        foreach ($matched as $cookies) {
+            krsort($cookies);
+            $ret = array_merge($ret, $cookies);
+        }
+        if (!$asString) {
+            return $ret;
+        } else {
+            $str = '';
+            foreach ($ret as $c) {
+                $str .= (empty($str)? '': '; ') . $c['name'] . '=' . $c['value'];
+            }
+            return $str;
+        }
+    }
+
+   /**
+    * Returns all cookies stored in a jar
+    *
+    * @return array
+    */
+    public function getAll()
+    {
+        $cookies = array();
+        foreach (array_keys($this->cookies) as $domain) {
+            foreach (array_keys($this->cookies[$domain]) as $path) {
+                foreach ($this->cookies[$domain][$path] as $name => $cookie) {
+                    $cookies[] = $cookie;
+                }
+            }
+        }
+        return $cookies;
+    }
+
+   /**
+    * Sets whether session cookies should be serialized when serializing the jar
+    *
+    * @param    boolean
+    */
+    public function serializeSessionCookies($serialize)
+    {
+        $this->serializeSession = (bool)$serialize;
+    }
+
+   /**
+    * Sets whether Public Suffix List should be used for restricting cookie-setting
+    *
+    * Without PSL {@link domainMatch()} will only prevent setting cookies for
+    * top-level domains like '.com' or '.org'. However, it will not prevent
+    * setting a cookie for '.co.uk' even though only third-level registrations
+    * are possible in .uk domain.
+    *
+    * With the List it is possible to find the highest level at which a domain
+    * may be registered for a particular top-level domain and consequently
+    * prevent cookies set for '.co.uk' or '.msk.ru'. The same list is used by
+    * Firefox, Chrome and Opera browsers to restrict cookie setting.
+    *
+    * Note that PSL is licensed differently to HTTP_Request2 package (refer to
+    * the license information in public-suffix-list.php), so you can disable
+    * its use if this is an issue for you.
+    *
+    * @param    boolean
+    * @link     http://publicsuffix.org/learn/
+    */
+    public function usePublicSuffixList($useList)
+    {
+        $this->useList = (bool)$useList;
+    }
+
+   /**
+    * Returns string representation of object
+    *
+    * @return string
+    * @see    Serializable::serialize()
+    */
+    public function serialize()
+    {
+        $cookies = $this->getAll();
+        if (!$this->serializeSession) {
+            for ($i = count($cookies) - 1; $i >= 0; $i--) {
+                if (empty($cookies[$i]['expires'])) {
+                    unset($cookies[$i]);
+                }
+            }
+        }
+        return serialize(array(
+            'cookies'          => $cookies,
+            'serializeSession' => $this->serializeSession,
+            'useList'          => $this->useList
+        ));
+    }
+
+   /**
+    * Constructs the object from serialized string
+    *
+    * @param string  string representation
+    * @see   Serializable::unserialize()
+    */
+    public function unserialize($serialized)
+    {
+        $data = unserialize($serialized);
+        $now  = $this->now();
+        $this->serializeSessionCookies($data['serializeSession']);
+        $this->usePublicSuffixList($data['useList']);
+        foreach ($data['cookies'] as $cookie) {
+            if (!empty($cookie['expires']) && $cookie['expires'] <= $now) {
+                continue;
+            }
+            if (!isset($this->cookies[$cookie['domain']])) {
+                $this->cookies[$cookie['domain']] = array();
+            }
+            if (!isset($this->cookies[$cookie['domain']][$cookie['path']])) {
+                $this->cookies[$cookie['domain']][$cookie['path']] = array();
+            }
+            $this->cookies[$cookie['domain']][$cookie['path']][$cookie['name']] = $cookie;
+        }
+    }
+
+   /**
+    * Checks whether a cookie domain matches a request host.
+    *
+    * The method is used by {@link store()} to check for whether a document
+    * at given URL can set a cookie with a given domain attribute and by
+    * {@link getMatching()} to find cookies matching the request URL.
+    *
+    * @param    string  request host
+    * @param    string  cookie domain
+    * @return   bool    match success
+    */
+    public function domainMatch($requestHost, $cookieDomain)
+    {
+        if ($requestHost == $cookieDomain) {
+            return true;
+        }
+        // IP address, we require exact match
+        if (preg_match('/^(?:\d{1,3}\.){3}\d{1,3}$/', $requestHost)) {
+            return false;
+        }
+        if ('.' != $cookieDomain[0]) {
+            $cookieDomain = '.' . $cookieDomain;
+        }
+        // prevents setting cookies for '.com' and similar domains
+        if (!$this->useList && substr_count($cookieDomain, '.') < 2
+            || $this->useList && !self::getRegisteredDomain($cookieDomain)
+        ) {
+            return false;
+        }
+        return substr('.' . $requestHost, -strlen($cookieDomain)) == $cookieDomain;
+    }
+
+   /**
+    * Removes subdomains to get the registered domain (the first after top-level)
+    *
+    * The method will check Public Suffix List to find out where top-level
+    * domain ends and registered domain starts. It will remove domain parts
+    * to the left of registered one.
+    *
+    * @param  string        domain name
+    * @return string|bool   registered domain, will return false if $domain is
+    *                       either invalid or a TLD itself
+    */
+    public static function getRegisteredDomain($domain)
+    {
+        $domainParts = explode('.', ltrim($domain, '.'));
+
+        // load the list if needed
+        if (empty(self::$psl)) {
+            $path = '/usr/share/pear/data' . DIRECTORY_SEPARATOR . 'HTTP_Request2';
+            if (0 === strpos($path, '@' . 'data_dir@')) {
+                $path = realpath(dirname(__FILE__) . DIRECTORY_SEPARATOR . '..'
+                                 . DIRECTORY_SEPARATOR . 'data');
+            }
+            self::$psl = include_once $path . DIRECTORY_SEPARATOR . 'public-suffix-list.php';
+        }
+
+        if (!($result = self::checkDomainsList($domainParts, self::$psl))) {
+            // known TLD, invalid domain name
+            return false;
+        }
+
+        // unknown TLD
+        if (!strpos($result, '.')) {
+            // fallback to checking that domain "has at least two dots"
+            if (2 > ($count = count($domainParts))) {
+                return false;
+            }
+            return $domainParts[$count - 2] . '.' . $domainParts[$count - 1];
+        }
+        return $result;
+    }
+
+   /**
+    * Recursive helper method for {@link getRegisteredDomain()}
+    *
+    * @param  array         remaining domain parts
+    * @param  mixed         node in {@link HTTP_Request2_CookieJar::$psl} to check
+    * @return string|null   concatenated domain parts, null in case of error
+    */
+    protected static function checkDomainsList(array $domainParts, $listNode)
+    {
+        $sub    = array_pop($domainParts);
+        $result = null;
+
+        if (!is_array($listNode) || is_null($sub)
+            || array_key_exists('!' . $sub, $listNode)
+         ) {
+            return $sub;
+
+        } elseif (array_key_exists($sub, $listNode)) {
+            $result = self::checkDomainsList($domainParts, $listNode[$sub]);
+
+        } elseif (array_key_exists('*', $listNode)) {
+            $result = self::checkDomainsList($domainParts, $listNode['*']);
+
+        } else {
+            return $sub;
+        }
+
+        return (strlen($result) > 0) ? ($result . '.' . $sub) : null;
+    }
+}
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/HTTP/Request2/Exception.php b/typo3/contrib/pear/HTTP/Request2/Exception.php
new file mode 100644 (file)
index 0000000..0fb5659
--- /dev/null
@@ -0,0 +1,160 @@
+<?php
+/**
+ * Exception classes for HTTP_Request2 package
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: Exception.php 308629 2011-02-24 17:34:24Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Base class for exceptions in PEAR
+ */
+require_once 'PEAR/Exception.php';
+
+/**
+ * Base exception class for HTTP_Request2 package
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @version    Release: 2.0.0RC1
+ * @link       http://pear.php.net/pepr/pepr-proposal-show.php?id=132
+ */
+class HTTP_Request2_Exception extends PEAR_Exception
+{
+    /** An invalid argument was passed to a method */
+    const INVALID_ARGUMENT   = 1;
+    /** Some required value was not available */
+    const MISSING_VALUE      = 2;
+    /** Request cannot be processed due to errors in PHP configuration */
+    const MISCONFIGURATION   = 3;
+    /** Error reading the local file */
+    const READ_ERROR         = 4;
+
+    /** Server returned a response that does not conform to HTTP protocol */
+    const MALFORMED_RESPONSE = 10;
+    /** Failure decoding Content-Encoding or Transfer-Encoding of response */
+    const DECODE_ERROR       = 20;
+    /** Operation timed out */
+    const TIMEOUT            = 30;
+    /** Number of redirects exceeded 'max_redirects' configuration parameter */
+    const TOO_MANY_REDIRECTS = 40;
+    /** Redirect to a protocol other than http(s):// */
+    const NON_HTTP_REDIRECT  = 50;
+
+   /**
+    * Native error code
+    * @var int
+    */
+    private $_nativeCode;
+
+   /**
+    * Constructor, can set package error code and native error code
+    *
+    * @param string exception message
+    * @param int    package error code, one of class constants
+    * @param int    error code from underlying PHP extension
+    */
+    public function __construct($message = null, $code = null, $nativeCode = null)
+    {
+        parent::__construct($message, $code);
+        $this->_nativeCode = $nativeCode;
+    }
+
+   /**
+    * Returns error code produced by underlying PHP extension
+    *
+    * For Socket Adapter this may contain error number returned by
+    * stream_socket_client(), for Curl Adapter this will contain error number
+    * returned by curl_errno()
+    *
+    * @return integer
+    */
+    public function getNativeCode()
+    {
+        return $this->_nativeCode;
+    }
+}
+
+/**
+ * Exception thrown in case of missing features
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @version    Release: 2.0.0RC1
+ */
+class HTTP_Request2_NotImplementedException extends HTTP_Request2_Exception {}
+
+/**
+ * Exception that represents error in the program logic
+ *
+ * This exception usually implies a programmer's error, like passing invalid
+ * data to methods or trying to use PHP extensions that weren't installed or
+ * enabled. Usually exceptions of this kind will be thrown before request even
+ * starts.
+ *
+ * The exception will usually contain a package error code.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @version    Release: 2.0.0RC1
+ */
+class HTTP_Request2_LogicException extends HTTP_Request2_Exception {}
+
+/**
+ * Exception thrown when connection to a web or proxy server fails
+ *
+ * The exception will not contain a package error code, but will contain
+ * native error code, as returned by stream_socket_client() or curl_errno().
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @version    Release: 2.0.0RC1
+ */
+class HTTP_Request2_ConnectionException extends HTTP_Request2_Exception {}
+
+/**
+ * Exception thrown when sending or receiving HTTP message fails
+ *
+ * The exception may contain both package error code and native error code.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @version    Release: 2.0.0RC1
+ */
+class HTTP_Request2_MessageException extends HTTP_Request2_Exception {}
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/HTTP/Request2/MultipartBody.php b/typo3/contrib/pear/HTTP/Request2/MultipartBody.php
new file mode 100644 (file)
index 0000000..f9bebd6
--- /dev/null
@@ -0,0 +1,274 @@
+<?php
+/**
+ * Helper class for building multipart/form-data request body
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: MultipartBody.php 308322 2011-02-14 13:58:03Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Class for building multipart/form-data request body
+ *
+ * The class helps to reduce memory consumption by streaming large file uploads
+ * from disk, it also allows monitoring of upload progress (see request #7630)
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @version    Release: 2.0.0RC1
+ * @link       http://tools.ietf.org/html/rfc1867
+ */
+class HTTP_Request2_MultipartBody
+{
+   /**
+    * MIME boundary
+    * @var  string
+    */
+    private $_boundary;
+
+   /**
+    * Form parameters added via {@link HTTP_Request2::addPostParameter()}
+    * @var  array
+    */
+    private $_params = array();
+
+   /**
+    * File uploads added via {@link HTTP_Request2::addUpload()}
+    * @var  array
+    */
+    private $_uploads = array();
+
+   /**
+    * Header for parts with parameters
+    * @var  string
+    */
+    private $_headerParam = "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n";
+
+   /**
+    * Header for parts with uploads
+    * @var  string
+    */
+    private $_headerUpload = "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\nContent-Type: %s\r\n\r\n";
+
+   /**
+    * Current position in parameter and upload arrays
+    *
+    * First number is index of "current" part, second number is position within
+    * "current" part
+    *
+    * @var  array
+    */
+    private $_pos = array(0, 0);
+
+
+   /**
+    * Constructor. Sets the arrays with POST data.
+    *
+    * @param    array   values of form fields set via {@link HTTP_Request2::addPostParameter()}
+    * @param    array   file uploads set via {@link HTTP_Request2::addUpload()}
+    * @param    bool    whether to append brackets to array variable names
+    */
+    public function __construct(array $params, array $uploads, $useBrackets = true)
+    {
+        $this->_params = self::_flattenArray('', $params, $useBrackets);
+        foreach ($uploads as $fieldName => $f) {
+            if (!is_array($f['fp'])) {
+                $this->_uploads[] = $f + array('name' => $fieldName);
+            } else {
+                for ($i = 0; $i < count($f['fp']); $i++) {
+                    $upload = array(
+                        'name' => ($useBrackets? $fieldName . '[' . $i . ']': $fieldName)
+                    );
+                    foreach (array('fp', 'filename', 'size', 'type') as $key) {
+                        $upload[$key] = $f[$key][$i];
+                    }
+                    $this->_uploads[] = $upload;
+                }
+            }
+        }
+    }
+
+   /**
+    * Returns the length of the body to use in Content-Length header
+    *
+    * @return   integer
+    */
+    public function getLength()
+    {
+        $boundaryLength     = strlen($this->getBoundary());
+        $headerParamLength  = strlen($this->_headerParam) - 4 + $boundaryLength;
+        $headerUploadLength = strlen($this->_headerUpload) - 8 + $boundaryLength;
+        $length             = $boundaryLength + 6;
+        foreach ($this->_params as $p) {
+            $length += $headerParamLength + strlen($p[0]) + strlen($p[1]) + 2;
+        }
+        foreach ($this->_uploads as $u) {
+            $length += $headerUploadLength + strlen($u['name']) + strlen($u['type']) +
+                       strlen($u['filename']) + $u['size'] + 2;
+        }
+        return $length;
+    }
+
+   /**
+    * Returns the boundary to use in Content-Type header
+    *
+    * @return   string
+    */
+    public function getBoundary()
+    {
+        if (empty($this->_boundary)) {
+            $this->_boundary = '--' . md5('PEAR-HTTP_Request2-' . microtime());
+        }
+        return $this->_boundary;
+    }
+
+   /**
+    * Returns next chunk of request body
+    *
+    * @param    integer Amount of bytes to read
+    * @return   string  Up to $length bytes of data, empty string if at end
+    */
+    public function read($length)
+    {
+        $ret         = '';
+        $boundary    = $this->getBoundary();
+        $paramCount  = count($this->_params);
+        $uploadCount = count($this->_uploads);
+        while ($length > 0 && $this->_pos[0] <= $paramCount + $uploadCount) {
+            $oldLength = $length;
+            if ($this->_pos[0] < $paramCount) {
+                $param = sprintf($this->_headerParam, $boundary,
+                                 $this->_params[$this->_pos[0]][0]) .
+                         $this->_params[$this->_pos[0]][1] . "\r\n";
+                $ret    .= substr($param, $this->_pos[1], $length);
+                $length -= min(strlen($param) - $this->_pos[1], $length);
+
+            } elseif ($this->_pos[0] < $paramCount + $uploadCount) {
+                $pos    = $this->_pos[0] - $paramCount;
+                $header = sprintf($this->_headerUpload, $boundary,
+                                  $this->_uploads[$pos]['name'],
+                                  $this->_uploads[$pos]['filename'],
+                                  $this->_uploads[$pos]['type']);
+                if ($this->_pos[1] < strlen($header)) {
+                    $ret    .= substr($header, $this->_pos[1], $length);
+                    $length -= min(strlen($header) - $this->_pos[1], $length);
+                }
+                $filePos  = max(0, $this->_pos[1] - strlen($header));
+                if ($length > 0 && $filePos < $this->_uploads[$pos]['size']) {
+                    $ret     .= fread($this->_uploads[$pos]['fp'], $length);
+                    $length  -= min($length, $this->_uploads[$pos]['size'] - $filePos);
+                }
+                if ($length > 0) {
+                    $start   = $this->_pos[1] + ($oldLength - $length) -
+                               strlen($header) - $this->_uploads[$pos]['size'];
+                    $ret    .= substr("\r\n", $start, $length);
+                    $length -= min(2 - $start, $length);
+                }
+
+            } else {
+                $closing  = '--' . $boundary . "--\r\n";
+                $ret     .= substr($closing, $this->_pos[1], $length);
+                $length  -= min(strlen($closing) - $this->_pos[1], $length);
+            }
+            if ($length > 0) {
+                $this->_pos     = array($this->_pos[0] + 1, 0);
+            } else {
+                $this->_pos[1] += $oldLength;
+            }
+        }
+        return $ret;
+    }
+
+   /**
+    * Sets the current position to the start of the body
+    *
+    * This allows reusing the same body in another request
+    */
+    public function rewind()
+    {
+        $this->_pos = array(0, 0);
+        foreach ($this->_uploads as $u) {
+            rewind($u['fp']);
+        }
+    }
+
+   /**
+    * Returns the body as string
+    *
+    * Note that it reads all file uploads into memory so it is a good idea not
+    * to use this method with large file uploads and rely on read() instead.
+    *
+    * @return   string
+    */
+    public function __toString()
+    {
+        $this->rewind();
+        return $this->read($this->getLength());
+    }
+
+
+   /**
+    * Helper function to change the (probably multidimensional) associative array
+    * into the simple one.
+    *
+    * @param    string  name for item
+    * @param    mixed   item's values
+    * @param    bool    whether to append [] to array variables' names
+    * @return   array   array with the following items: array('item name', 'item value');
+    */
+    private static function _flattenArray($name, $values, $useBrackets)
+    {
+        if (!is_array($values)) {
+            return array(array($name, $values));
+        } else {
+            $ret = array();
+            foreach ($values as $k => $v) {
+                if (empty($name)) {
+                    $newName = $k;
+                } elseif ($useBrackets) {
+                    $newName = $name . '[' . $k . ']';
+                } else {
+                    $newName = $name;
+                }
+                $ret = array_merge($ret, self::_flattenArray($newName, $v, $useBrackets));
+            }
+            return $ret;
+        }
+    }
+}
+?>
diff --git a/typo3/contrib/pear/HTTP/Request2/Observer/Log.php b/typo3/contrib/pear/HTTP/Request2/Observer/Log.php
new file mode 100644 (file)
index 0000000..40fd2c6
--- /dev/null
@@ -0,0 +1,215 @@
+<?php
+/**
+ * An observer useful for debugging / testing.
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category HTTP
+ * @package  HTTP_Request2
+ * @author   David Jean Louis <izi@php.net>
+ * @author   Alexey Borzov <avb@php.net>
+ * @license  http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version  SVN: $Id: Log.php 308680 2011-02-25 17:40:17Z avb $
+ * @link     http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Exception class for HTTP_Request2 package
+ */
+require_once 'HTTP/Request2/Exception.php';
+
+/**
+ * A debug observer useful for debugging / testing.
+ *
+ * This observer logs to a log target data corresponding to the various request
+ * and response events, it logs by default to php://output but can be configured
+ * to log to a file or via the PEAR Log package.
+ *
+ * A simple example:
+ * <code>
+ * require_once 'HTTP/Request2.php';
+ * require_once 'HTTP/Request2/Observer/Log.php';
+ *
+ * $request  = new HTTP_Request2('http://www.example.com');
+ * $observer = new HTTP_Request2_Observer_Log();
+ * $request->attach($observer);
+ * $request->send();
+ * </code>
+ *
+ * A more complex example with PEAR Log:
+ * <code>
+ * require_once 'HTTP/Request2.php';
+ * require_once 'HTTP/Request2/Observer/Log.php';
+ * require_once 'Log.php';
+ *
+ * $request  = new HTTP_Request2('http://www.example.com');
+ * // we want to log with PEAR log
+ * $observer = new HTTP_Request2_Observer_Log(Log::factory('console'));
+ *
+ * // we only want to log received headers
+ * $observer->events = array('receivedHeaders');
+ *
+ * $request->attach($observer);
+ * $request->send();
+ * </code>
+ *
+ * @category HTTP
+ * @package  HTTP_Request2
+ * @author   David Jean Louis <izi@php.net>
+ * @author   Alexey Borzov <avb@php.net>
+ * @license  http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version  Release: 2.0.0RC1
+ * @link     http://pear.php.net/package/HTTP_Request2
+ */
+class HTTP_Request2_Observer_Log implements SplObserver
+{
+    // properties {{{
+
+    /**
+     * The log target, it can be a a resource or a PEAR Log instance.
+     *
+     * @var resource|Log $target
+     */
+    protected $target = null;
+
+    /**
+     * The events to log.
+     *
+     * @var array $events
+     */
+    public $events = array(
+        'connect',
+        'sentHeaders',
+        'sentBody',
+        'receivedHeaders',
+        'receivedBody',
+        'disconnect',
+    );
+
+    // }}}
+    // __construct() {{{
+
+    /**
+     * Constructor.
+     *
+     * @param mixed $target Can be a file path (default: php://output), a resource,
+     *                      or an instance of the PEAR Log class.
+     * @param array $events Array of events to listen to (default: all events)
+     *
+     * @return void
+     */
+    public function __construct($target = 'php://output', array $events = array())
+    {
+        if (!empty($events)) {
+            $this->events = $events;
+        }
+        if (is_resource($target) || $target instanceof Log) {
+            $this->target = $target;
+        } elseif (false === ($this->target = @fopen($target, 'ab'))) {
+            throw new HTTP_Request2_Exception("Unable to open '{$target}'");
+        }
+    }
+
+    // }}}
+    // update() {{{
+
+    /**
+     * Called when the request notifies us of an event.
+     *
+     * @param HTTP_Request2 $subject The HTTP_Request2 instance
+     *
+     * @return void
+     */
+    public function update(SplSubject $subject)
+    {
+        $event = $subject->getLastEvent();
+        if (!in_array($event['name'], $this->events)) {
+            return;
+        }
+
+        switch ($event['name']) {
+        case 'connect':
+            $this->log('* Connected to ' . $event['data']);
+            break;
+        case 'sentHeaders':
+            $headers = explode("\r\n", $event['data']);
+            array_pop($headers);
+            foreach ($headers as $header) {
+                $this->log('> ' . $header);
+            }
+            break;
+        case 'sentBody':
+            $this->log('> ' . $event['data'] . ' byte(s) sent');
+            break;
+        case 'receivedHeaders':
+            $this->log(sprintf('< HTTP/%s %s %s',
+                $event['data']->getVersion(),
+                $event['data']->getStatus(),
+                $event['data']->getReasonPhrase()));
+            $headers = $event['data']->getHeader();
+            foreach ($headers as $key => $val) {
+                $this->log('< ' . $key . ': ' . $val);
+            }
+            $this->log('< ');
+            break;
+        case 'receivedBody':
+            $this->log($event['data']->getBody());
+            break;
+        case 'disconnect':
+            $this->log('* Disconnected');
+            break;
+        }
+    }
+
+    // }}}
+    // log() {{{
+
+    /**
+     * Logs the given message to the configured target.
+     *
+     * @param string $message Message to display
+     *
+     * @return void
+     */
+    protected function log($message)
+    {
+        if ($this->target instanceof Log) {
+            $this->target->debug($message);
+        } elseif (is_resource($this->target)) {
+            fwrite($this->target, $message . "\r\n");
+        }
+    }
+
+    // }}}
+}
+
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/HTTP/Request2/Response.php b/typo3/contrib/pear/HTTP/Request2/Response.php
new file mode 100644 (file)
index 0000000..974aff2
--- /dev/null
@@ -0,0 +1,629 @@
+<?php
+/**
+ * Class representing a HTTP response
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2008-2011, Alexey Borzov <avb@php.net>
+ * 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.
+ *    * The names of the authors may not be used to endorse or promote products
+ *      derived from this software without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    SVN: $Id: Response.php 309921 2011-04-03 16:43:02Z avb $
+ * @link       http://pear.php.net/package/HTTP_Request2
+ */
+
+/**
+ * Exception class for HTTP_Request2 package
+ */
+require_once 'HTTP/Request2/Exception.php';
+
+/**
+ * Class representing a HTTP response
+ *
+ * The class is designed to be used in "streaming" scenario, building the
+ * response as it is being received:
+ * <code>
+ * $statusLine = read_status_line();
+ * $response = new HTTP_Request2_Response($statusLine);
+ * do {
+ *     $headerLine = read_header_line();
+ *     $response->parseHeaderLine($headerLine);
+ * } while ($headerLine != '');
+ *
+ * while ($chunk = read_body()) {
+ *     $response->appendBody($chunk);
+ * }
+ *
+ * var_dump($response->getHeader(), $response->getCookies(), $response->getBody());
+ * </code>
+ *
+ *
+ * @category   HTTP
+ * @package    HTTP_Request2
+ * @author     Alexey Borzov <avb@php.net>
+ * @version    Release: 2.0.0RC1
+ * @link       http://tools.ietf.org/html/rfc2616#section-6
+ */
+class HTTP_Request2_Response
+{
+   /**
+    * HTTP protocol version (e.g. 1.0, 1.1)
+    * @var  string
+    */
+    protected $version;
+
+   /**
+    * Status code
+    * @var  integer
+    * @link http://tools.ietf.org/html/rfc2616#section-6.1.1
+    */
+    protected $code;
+
+   /**
+    * Reason phrase
+    * @var  string
+    * @link http://tools.ietf.org/html/rfc2616#section-6.1.1
+    */
+    protected $reasonPhrase;
+
+   /**
+    * Effective URL (may be different from original request URL in case of redirects)
+    * @var  string
+    */
+    protected $effectiveUrl;
+
+   /**
+    * Associative array of response headers
+    * @var  array
+    */
+    protected $headers = array();
+
+   /**
+    * Cookies set in the response
+    * @var  array
+    */
+    protected $cookies = array();
+
+   /**
+    * Name of last header processed by parseHederLine()
+    *
+    * Used to handle the headers that span multiple lines
+    *
+    * @var  string
+    */
+    protected $lastHeader = null;
+
+   /**
+    * Response body
+    * @var  string
+    */
+    protected $body = '';
+
+   /**
+    * Whether the body is still encoded by Content-Encoding
+    *
+    * cURL provides the decoded body to the callback; if we are reading from
+    * socket the body is still gzipped / deflated
+    *
+    * @var  bool
+    */
+    protected $bodyEncoded;
+
+   /**
+    * Associative array of HTTP status code / reason phrase.
+    *
+    * @var  array
+    * @link http://tools.ietf.org/html/rfc2616#section-10
+    */
+    protected static $phrases = array(
+
+        // 1xx: Informational - Request received, continuing process
+        100 => 'Continue',
+        101 => 'Switching Protocols',
+
+        // 2xx: Success - The action was successfully received, understood and
+        // accepted
+        200 => 'OK',
+        201 => 'Created',
+        202 => 'Accepted',
+        203 => 'Non-Authoritative Information',
+        204 => 'No Content',
+        205 => 'Reset Content',
+        206 => 'Partial Content',
+
+        // 3xx: Redirection - Further action must be taken in order to complete
+        // the request
+        300 => 'Multiple Choices',
+        301 => 'Moved Permanently',
+        302 => 'Found',  // 1.1
+        303 => 'See Other',
+        304 => 'Not Modified',
+        305 => 'Use Proxy',
+        307 => 'Temporary Redirect',
+
+        // 4xx: Client Error - The request contains bad syntax or cannot be
+        // fulfilled
+        400 => 'Bad Request',
+        401 => 'Unauthorized',
+        402 => 'Payment Required',
+        403 => 'Forbidden',
+        404 => 'Not Found',
+        405 => 'Method Not Allowed',
+        406 => 'Not Acceptable',
+        407 => 'Proxy Authentication Required',
+        408 => 'Request Timeout',
+        409 => 'Conflict',
+        410 => 'Gone',
+        411 => 'Length Required',
+        412 => 'Precondition Failed',
+        413 => 'Request Entity Too Large',
+        414 => 'Request-URI Too Long',
+        415 => 'Unsupported Media Type',
+        416 => 'Requested Range Not Satisfiable',
+        417 => 'Expectation Failed',
+
+        // 5xx: Server Error - The server failed to fulfill an apparently
+        // valid request
+        500 => 'Internal Server Error',
+        501 => 'Not Implemented',
+        502 => 'Bad Gateway',
+        503 => 'Service Unavailable',
+        504 => 'Gateway Timeout',
+        505 => 'HTTP Version Not Supported',
+        509 => 'Bandwidth Limit Exceeded',
+
+    );
+
+   /**
+    * Constructor, parses the response status line
+    *
+    * @param    string Response status line (e.g. "HTTP/1.1 200 OK")
+    * @param    bool   Whether body is still encoded by Content-Encoding
+    * @param    string Effective URL of the response
+    * @throws   HTTP_Request2_MessageException if status line is invalid according to spec
+    */
+    public function __construct($statusLine, $bodyEncoded = true, $effectiveUrl = null)
+    {
+        if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) {
+            throw new HTTP_Request2_MessageException(
+                "Malformed response: {$statusLine}",
+                HTTP_Request2_Exception::MALFORMED_RESPONSE
+            );
+        }
+        $this->version = $m[1];
+        $this->code    = intval($m[2]);
+        if (!empty($m[3])) {
+            $this->reasonPhrase = trim($m[3]);
+        } elseif (!empty(self::$phrases[$this->code])) {
+            $this->reasonPhrase = self::$phrases[$this->code];
+        }
+        $this->bodyEncoded  = (bool)$bodyEncoded;
+        $this->effectiveUrl = (string)$effectiveUrl;
+    }
+
+   /**
+    * Parses the line from HTTP response filling $headers array
+    *
+    * The method should be called after reading the line from socket or receiving
+    * it into cURL callback. Passing an empty string here indicates the end of
+    * response headers and triggers additional processing, so be sure to pass an
+    * empty string in the end.
+    *
+    * @param    string  Line from HTTP response
+    */
+    public function parseHeaderLine($headerLine)
+    {
+        $headerLine = trim($headerLine, "\r\n");
+
+        // empty string signals the end of headers, process the received ones
+        if ('' == $headerLine) {
+            if (!empty($this->headers['set-cookie'])) {
+                $cookies = is_array($this->headers['set-cookie'])?
+                           $this->headers['set-cookie']:
+                           array($this->headers['set-cookie']);
+                foreach ($cookies as $cookieString) {
+                    $this->parseCookie($cookieString);
+                }
+                unset($this->headers['set-cookie']);
+            }
+            foreach (array_keys($this->headers) as $k) {
+                if (is_array($this->headers[$k])) {
+                    $this->headers[$k] = implode(', ', $this->headers[$k]);
+                }
+            }
+
+        // string of the form header-name: header value
+        } elseif (preg_match('!^([^\x00-\x1f\x7f-\xff()<>@,;:\\\\"/\[\]?={}\s]+):(.+)$!', $headerLine, $m)) {
+            $name  = strtolower($m[1]);
+            $value = trim($m[2]);
+            if (empty($this->headers[$name])) {
+                $this->headers[$name] = $value;
+            } else {
+                if (!is_array($this->headers[$name])) {
+                    $this->headers[$name] = array($this->headers[$name]);
+                }
+                $this->headers[$name][] = $value;
+            }
+            $this->lastHeader = $name;
+
+        // continuation of a previous header
+        } elseif (preg_match('!^\s+(.+)$!', $headerLine, $m) && $this->lastHeader) {
+            if (!is_array($this->headers[$this->lastHeader])) {
+                $this->headers[$this->lastHeader] .= ' ' . trim($m[1]);
+            } else {
+                $key = count($this->headers[$this->lastHeader]) - 1;
+                $this->headers[$this->lastHeader][$key] .= ' ' . trim($m[1]);
+            }
+        }
+    }
+
+   /**
+    * Parses a Set-Cookie header to fill $cookies array
+    *
+    * @param    string    value of Set-Cookie header
+    * @link     http://web.archive.org/web/20080331104521/http://cgi.netscape.com/newsref/std/cookie_spec.html
+    */
+    protected function parseCookie($cookieString)
+    {
+        $cookie = array(
+            'expires' => null,
+            'domain'  => null,
+            'path'    => null,
+            'secure'  => false
+        );
+
+        // Only a name=value pair
+        if (!strpos($cookieString, ';')) {
+            $pos = strpos($cookieString, '=');
+            $cookie['name']  = trim(substr($cookieString, 0, $pos));
+            $cookie['value'] = trim(substr($cookieString, $pos + 1));
+
+        // Some optional parameters are supplied
+        } else {
+            $elements = explode(';', $cookieString);
+            $pos = strpos($elements[0], '=');
+            $cookie['name']  = trim(substr($elements[0], 0, $pos));
+            $cookie['value'] = trim(substr($elements[0], $pos + 1));
+
+            for ($i = 1; $i < count($elements); $i++) {
+                if (false === strpos($elements[$i], '=')) {
+                    $elName  = trim($elements[$i]);
+                    $elValue = null;
+                } else {
+                    list ($elName, $elValue) = array_map('trim', explode('=', $elements[$i]));
+                }
+                $elName = strtolower($elName);
+                if ('secure' == $elName) {
+                    $cookie['secure'] = true;
+                } elseif ('expires' == $elName) {
+                    $cookie['expires'] = str_replace('"', '', $elValue);
+                } elseif ('path' == $elName || 'domain' == $elName) {
+                    $cookie[$elName] = urldecode($elValue);
+                } else {
+                    $cookie[$elName] = $elValue;
+                }
+            }
+        }
+        $this->cookies[] = $cookie;
+    }
+
+   /**
+    * Appends a string to the response body
+    * @param    string
+    */
+    public function appendBody($bodyChunk)
+    {
+        $this->body .= $bodyChunk;
+    }
+
+   /**
+    * Returns the effective URL of the response
+    *
+    * This may be different from the request URL if redirects were followed.
+    *
+    * @return string
+    * @link   http://pear.php.net/bugs/bug.php?id=18412
+    */
+    public function getEffectiveUrl()
+    {
+        return $this->effectiveUrl;
+    }
+
+   /**
+    * Returns the status code
+    * @return   integer
+    */
+    public function getStatus()
+    {
+        return $this->code;
+    }
+
+   /**
+    * Returns the reason phrase
+    * @return   string
+    */
+    public function getReasonPhrase()
+    {
+        return $this->reasonPhrase;
+    }
+
+   /**
+    * Whether response is a redirect that can be automatically handled by HTTP_Request2
+    * @return   bool
+    */
+    public function isRedirect()
+    {
+        return in_array($this->code, array(300, 301, 302, 303, 307))
+               && isset($this->headers['location']);
+    }
+
+   /**
+    * Returns either the named header or all response headers
+    *
+    * @param    string          Name of header to return
+    * @return   string|array    Value of $headerName header (null if header is
+    *                           not present), array of all response headers if
+    *                           $headerName is null
+    */
+    public function getHeader($headerName = null)
+    {
+        if (null === $headerName) {
+            return $this->headers;
+        } else {
+            $headerName = strtolower($headerName);
+            return isset($this->headers[$headerName])? $this->headers[$headerName]: null;
+        }
+    }
+
+   /**
+    * Returns cookies set in response
+    *
+    * @return   array
+    */
+    public function getCookies()
+    {
+        return $this->cookies;
+    }
+
+   /**
+    * Returns the body of the response
+    *
+    * @return   string
+    * @throws   HTTP_Request2_Exception if body cannot be decoded
+    */
+    public function getBody()
+    {
+        if (0 == strlen($this->body) || !$this->bodyEncoded ||
+            !in_array(strtolower($this->getHeader('content-encoding')), array('gzip', 'deflate'))
+        ) {
+            return $this->body;
+
+        } else {
+            if (extension_loaded('mbstring') && (2 & ini_get('mbstring.func_overload'))) {
+                $oldEncoding = mb_internal_encoding();
+                mb_internal_encoding('iso-8859-1');
+            }
+
+            try {
+                switch (strtolower($this->getHeader('content-encoding'))) {
+                    case 'gzip':
+                        $decoded = self::decodeGzip($this->body);
+                        break;
+                    case 'deflate':
+                        $decoded = self::decodeDeflate($this->body);
+                }
+            } catch (Exception $e) {
+            }
+
+            if (!empty($oldEncoding)) {
+                mb_internal_encoding($oldEncoding);
+            }
+            if (!empty($e)) {
+                throw $e;
+            }
+            return $decoded;
+        }
+    }
+
+   /**
+    * Get the HTTP version of the response
+    *
+    * @return   string
+    */
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+   /**
+    * Decodes the message-body encoded by gzip
+    *
+    * The real decoding work is done by gzinflate() built-in function, this
+    * method only parses the header and checks data for compliance with
+    * RFC 1952
+    *
+    * @param    string  gzip-encoded data
+    * @return   string  decoded data
+    * @throws   HTTP_Request2_LogicException
+    * @throws   HTTP_Request2_MessageException
+    * @link     http://tools.ietf.org/html/rfc1952
+    */
+    public static function decodeGzip($data)
+    {
+        $length = strlen($data);
+        // If it doesn't look like gzip-encoded data, don't bother
+        if (18 > $length || strcmp(substr($data, 0, 2), "\x1f\x8b")) {
+            return $data;
+        }
+        if (!function_exists('gzinflate')) {
+            throw new HTTP_Request2_LogicException(
+                'Unable to decode body: gzip extension not available',
+                HTTP_Request2_Exception::MISCONFIGURATION
+            );
+        }
+        $method = ord(substr($data, 2, 1));
+        if (8 != $method) {
+            throw new HTTP_Request2_MessageException(
+                'Error parsing gzip header: unknown compression method',
+                HTTP_Request2_Exception::DECODE_ERROR
+            );
+        }
+        $flags = ord(substr($data, 3, 1));
+        if ($flags & 224) {
+            throw new HTTP_Request2_MessageException(
+                'Error parsing gzip header: reserved bits are set',
+                HTTP_Request2_Exception::DECODE_ERROR
+            );
+        }
+
+        // header is 10 bytes minimum. may be longer, though.
+        $headerLength = 10;
+        // extra fields, need to skip 'em
+        if ($flags & 4) {
+            if ($length - $headerLength - 2 < 8) {
+                throw new HTTP_Request2_MessageException(
+                    'Error parsing gzip header: data too short',
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            }
+            $extraLength = unpack('v', substr($data, 10, 2));
+            if ($length - $headerLength - 2 - $extraLength[1] < 8) {
+                throw new HTTP_Request2_MessageException(
+                    'Error parsing gzip header: data too short',
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            }
+            $headerLength += $extraLength[1] + 2;
+        }
+        // file name, need to skip that
+        if ($flags & 8) {
+            if ($length - $headerLength - 1 < 8) {
+                throw new HTTP_Request2_MessageException(
+                    'Error parsing gzip header: data too short',
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            }
+            $filenameLength = strpos(substr($data, $headerLength), chr(0));
+            if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) {
+                throw new HTTP_Request2_MessageException(
+                    'Error parsing gzip header: data too short',
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            }
+            $headerLength += $filenameLength + 1;
+        }
+        // comment, need to skip that also
+        if ($flags & 16) {
+            if ($length - $headerLength - 1 < 8) {
+                throw new HTTP_Request2_MessageException(
+                    'Error parsing gzip header: data too short',
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            }
+            $commentLength = strpos(substr($data, $headerLength), chr(0));
+            if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) {
+                throw new HTTP_Request2_MessageException(
+                    'Error parsing gzip header: data too short',
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            }
+            $headerLength += $commentLength + 1;
+        }
+        // have a CRC for header. let's check
+        if ($flags & 2) {
+            if ($length - $headerLength - 2 < 8) {
+                throw new HTTP_Request2_MessageException(
+                    'Error parsing gzip header: data too short',
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            }
+            $crcReal   = 0xffff & crc32(substr($data, 0, $headerLength));
+            $crcStored = unpack('v', substr($data, $headerLength, 2));
+            if ($crcReal != $crcStored[1]) {
+                throw new HTTP_Request2_MessageException(
+                    'Header CRC check failed',
+                    HTTP_Request2_Exception::DECODE_ERROR
+                );
+            }
+            $headerLength += 2;
+        }
+        // unpacked data CRC and size at the end of encoded data
+        $tmp = unpack('V2', substr($data, -8));
+        $dataCrc  = $tmp[1];
+        $dataSize = $tmp[2];
+
+        // finally, call the gzinflate() function
+        // don't pass $dataSize to gzinflate, see bugs #13135, #14370
+        $unpacked = gzinflate(substr($data, $headerLength, -8));
+        if (false === $unpacked) {
+            throw new HTTP_Request2_MessageException(
+                'gzinflate() call failed',
+                HTTP_Request2_Exception::DECODE_ERROR
+            );
+        } elseif ($dataSize != strlen($unpacked)) {
+            throw new HTTP_Request2_MessageException(
+                'Data size check failed',
+                HTTP_Request2_Exception::DECODE_ERROR
+            );
+        } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) {
+            throw new HTTP_Request2_Exception(
+                'Data CRC check failed',
+                HTTP_Request2_Exception::DECODE_ERROR
+            );
+        }
+        return $unpacked;
+    }
+
+   /**
+    * Decodes the message-body encoded by deflate
+    *
+    * @param    string  deflate-encoded data
+    * @return   string  decoded data
+    * @throws   HTTP_Request2_LogicException
+    */
+    public static function decodeDeflate($data)
+    {
+        if (!function_exists('gzuncompress')) {
+            throw new HTTP_Request2_LogicException(
+                'Unable to decode body: gzip extension not available',
+                HTTP_Request2_Exception::MISCONFIGURATION
+            );
+        }
+        // RFC 2616 defines 'deflate' encoding as zlib format from RFC 1950,
+        // while many applications send raw deflate stream from RFC 1951.
+        // We should check for presence of zlib header and use gzuncompress() or
+        // gzinflate() as needed. See bug #15305
+        $header = unpack('n', substr($data, 0, 2));
+        return (0 == $header[1] % 31)? gzuncompress($data): gzinflate($data);
+    }
+}
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/Net/URL2.php b/typo3/contrib/pear/Net/URL2.php
new file mode 100644 (file)
index 0000000..5e1eaf6
--- /dev/null
@@ -0,0 +1,895 @@
+<?php
+/**
+ * Net_URL2, a class representing a URL as per RFC 3986.
+ *
+ * PHP version 5
+ *
+ * LICENSE:
+ *
+ * Copyright (c) 2007-2009, Peytz & Co. A/S
+ * 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.
+ *   * Neither the name of the Net_URL2 nor the names of its contributors may
+ *     be used to endorse or promote products derived from this software
+ *     without specific prior written permission.
+ *
+ * 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.
+ *
+ * @category  Networking
+ * @package   Net_URL2
+ * @author    Christian Schmidt <schmidt@php.net>
+ * @copyright 2007-2009 Peytz & Co. A/S
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version   CVS: $Id: URL2.php 290036 2009-10-28 19:52:49Z schmidt $
+ * @link      http://www.rfc-editor.org/rfc/rfc3986.txt
+ */
+
+/**
+ * Represents a URL as per RFC 3986.
+ *
+ * @category  Networking
+ * @package   Net_URL2
+ * @author    Christian Schmidt <schmidt@php.net>
+ * @copyright 2007-2009 Peytz & Co. A/S
+ * @license   http://www.opensource.org/licenses/bsd-license.php New BSD License
+ * @version   Release: @package_version@
+ * @link      http://pear.php.net/package/Net_URL2
+ */
+class Net_URL2
+{
+    /**
+     * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
+     * is true.
+     */
+    const OPTION_STRICT = 'strict';
+
+    /**
+     * Represent arrays in query using PHP's [] notation. Default is true.
+     */
+    const OPTION_USE_BRACKETS = 'use_brackets';
+
+    /**
+     * URL-encode query variable keys. Default is true.
+     */
+    const OPTION_ENCODE_KEYS = 'encode_keys';
+
+    /**
+     * Query variable separators when parsing the query string. Every character
+     * is considered a separator. Default is "&".
+     */
+    const OPTION_SEPARATOR_INPUT = 'input_separator';
+
+    /**
+     * Query variable separator used when generating the query string. Default
+     * is "&".
+     */
+    const OPTION_SEPARATOR_OUTPUT = 'output_separator';
+
+    /**
+     * Default options corresponds to how PHP handles $_GET.
+     */
+    private $_options = array(
+        self::OPTION_STRICT           => true,
+        self::OPTION_USE_BRACKETS     => true,
+        self::OPTION_ENCODE_KEYS      => true,
+        self::OPTION_SEPARATOR_INPUT  => '&',
+        self::OPTION_SEPARATOR_OUTPUT => '&',
+        );
+
+    /**
+     * @var  string|bool
+     */
+    private $_scheme = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $_userinfo = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $_host = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $_port = false;
+
+    /**
+     * @var  string
+     */
+    private $_path = '';
+
+    /**
+     * @var  string|bool
+     */
+    private $_query = false;
+
+    /**
+     * @var  string|bool
+     */
+    private $_fragment = false;
+
+    /**
+     * Constructor.
+     *
+     * @param string $url     an absolute or relative URL
+     * @param array  $options an array of OPTION_xxx constants
+     */
+    public function __construct($url, array $options = array())
+    {
+        foreach ($options as $optionName => $value) {
+            if (array_key_exists($optionName, $this->_options)) {
+                $this->_options[$optionName] = $value;
+            }
+        }
+
+        // The regular expression is copied verbatim from RFC 3986, appendix B.
+        // The expression does not validate the URL but matches any string.
+        preg_match('!^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?!',
+                   $url,
+                   $matches);
+
+        // "path" is always present (possibly as an empty string); the rest
+        // are optional.
+        $this->_scheme = !empty($matches[1]) ? $matches[2] : false;
+        $this->setAuthority(!empty($matches[3]) ? $matches[4] : false);
+        $this->_path = $matches[5];
+        $this->_query = !empty($matches[6]) ? $matches[7] : false;
+        $this->_fragment = !empty($matches[8]) ? $matches[9] : false;
+    }
+
+    /**
+     * Magic Setter.
+     *
+     * This method will magically set the value of a private variable ($var)
+     * with the value passed as the args
+     *
+     * @param  string $var      The private variable to set.
+     * @param  mixed  $arg      An argument of any type.
+     * @return void
+     */
+    public function __set($var, $arg)
+    {
+        $method = 'set' . $var;
+        if (method_exists($this, $method)) {
+            $this->$method($arg);
+        }
+    }
+
+    /**
+     * Magic Getter.
+     *
+     * This is the magic get method to retrieve the private variable
+     * that was set by either __set() or it's setter...
+     *
+     * @param  string $var         The property name to retrieve.
+     * @return mixed  $this->$var  Either a boolean false if the
+     *                             property is not set or the value
+     *                             of the private property.
+     */
+    public function __get($var)
+    {
+        $method = 'get' . $var;
+        if (method_exists($this, $method)) {
+            return $this->$method();
+        }
+
+        return false;
+    }
+
+    /**
+     * Returns the scheme, e.g. "http" or "urn", or false if there is no
+     * scheme specified, i.e. if this is a relative URL.
+     *
+     * @return  string|bool
+     */
+    public function getScheme()
+    {
+        return $this->_scheme;
+    }
+
+    /**
+     * Sets the scheme, e.g. "http" or "urn". Specify false if there is no
+     * scheme specified, i.e. if this is a relative URL.
+     *
+     * @param string|bool $scheme e.g. "http" or "urn", or false if there is no
+     *                            scheme specified, i.e. if this is a relative
+     *                            URL
+     *
+     * @return void
+     * @see    getScheme()
+     */
+    public function setScheme($scheme)
+    {
+        $this->_scheme = $scheme;
+    }
+
+    /**
+     * Returns the user part of the userinfo part (the part preceding the first
+     *  ":"), or false if there is no userinfo part.
+     *
+     * @return  string|bool
+     */
+    public function getUser()
+    {
+        return $this->_userinfo !== false
+            ? preg_replace('@:.*$@', '', $this->_userinfo)
+            : false;
+    }
+
+    /**
+     * Returns the password part of the userinfo part (the part after the first
+     *  ":"), or false if there is no userinfo part (i.e. the URL does not
+     * contain "@" in front of the hostname) or the userinfo part does not
+     * contain ":".
+     *
+     * @return  string|bool
+     */
+    public function getPassword()
+    {
+        return $this->_userinfo !== false
+            ? substr(strstr($this->_userinfo, ':'), 1)
+            : false;
+    }
+
+    /**
+     * Returns the userinfo part, or false if there is none, i.e. if the
+     * authority part does not contain "@".
+     *
+     * @return  string|bool
+     */
+    public function getUserinfo()
+    {
+        return $this->_userinfo;
+    }
+
+    /**
+     * Sets the userinfo part. If two arguments are passed, they are combined
+     * in the userinfo part as username ":" password.
+     *
+     * @param string|bool $userinfo userinfo or username
+     * @param string|bool $password optional password, or false
+     *
+     * @return void
+     */
+    public function setUserinfo($userinfo, $password = false)
+    {
+        $this->_userinfo = $userinfo;
+        if ($password !== false) {
+            $this->_userinfo .= ':' . $password;
+        }
+    }
+
+    /**
+     * Returns the host part, or false if there is no authority part, e.g.
+     * relative URLs.
+     *
+     * @return  string|bool a hostname, an IP address, or false
+     */
+    public function getHost()
+    {
+        return $this->_host;
+    }
+
+    /**
+     * Sets the host part. Specify false if there is no authority part, e.g.
+     * relative URLs.
+     *
+     * @param string|bool $host a hostname, an IP address, or false
+     *
+     * @return void
+     */
+    public function setHost($host)
+    {
+        $this->_host = $host;
+    }
+
+    /**
+     * Returns the port number, or false if there is no port number specified,
+     * i.e. if the default port is to be used.
+     *
+     * @return  string|bool
+     */
+    public function getPort()
+    {
+        return $this->_port;
+    }
+
+    /**
+     * Sets the port number. Specify false if there is no port number specified,
+     * i.e. if the default port is to be used.
+     *
+     * @param string|bool $port a port number, or false
+     *
+     * @return void
+     */
+    public function setPort($port)
+    {
+        $this->_port = $port;
+    }
+
+    /**
+     * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or
+     * false if there is no authority.
+     *
+     * @return string|bool
+     */
+    public function getAuthority()
+    {
+        if (!$this->_host) {
+            return false;
+        }
+
+        $authority = '';
+
+        if ($this->_userinfo !== false) {
+            $authority .= $this->_userinfo . '@';
+        }
+
+        $authority .= $this->_host;
+
+        if ($this->_port !== false) {
+            $authority .= ':' . $this->_port;
+        }
+
+        return $authority;
+    }
+
+    /**
+     * Sets the authority part, i.e. [ userinfo "@" ] host [ ":" port ]. Specify
+     * false if there is no authority.
+     *
+     * @param string|false $authority a hostname or an IP addresse, possibly
+     *                                with userinfo prefixed and port number
+     *                                appended, e.g. "foo:bar@example.org:81".
+     *
+     * @return void
+     */
+    public function setAuthority($authority)
+    {
+        $this->_userinfo = false;
+        $this->_host     = false;
+        $this->_port     = false;
+        if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
+            if ($reg[1]) {
+                $this->_userinfo = $reg[2];
+            }
+
+            $this->_host = $reg[3];
+            if (isset($reg[5])) {
+                $this->_port = $reg[5];
+            }
+        }
+    }
+
+    /**
+     * Returns the path part (possibly an empty string).
+     *
+     * @return string
+     */
+    public function getPath()
+    {
+        return $this->_path;
+    }
+
+    /**
+     * Sets the path part (possibly an empty string).
+     *
+     * @param string $path a path
+     *
+     * @return void
+     */
+    public function setPath($path)
+    {
+        $this->_path = $path;
+    }
+
+    /**
+     * Returns the query string (excluding the leading "?"), or false if "?"
+     * is not present in the URL.
+     *
+     * @return  string|bool
+     * @see     self::getQueryVariables()
+     */
+    public function getQuery()
+    {
+        return $this->_query;
+    }
+
+    /**
+     * Sets the query string (excluding the leading "?"). Specify false if "?"
+     * is not present in the URL.
+     *
+     * @param string|bool $query a query string, e.g. "foo=1&bar=2"
+     *
+     * @return void
+     * @see   self::setQueryVariables()
+     */
+    public function setQuery($query)
+    {
+        $this->_query = $query;
+    }
+
+    /**
+     * Returns the fragment name, or false if "#" is not present in the URL.
+     *
+     * @return  string|bool
+     */
+    public function getFragment()
+    {
+        return $this->_fragment;
+    }
+
+    /**
+     * Sets the fragment name. Specify false if "#" is not present in the URL.
+     *
+     * @param string|bool $fragment a fragment excluding the leading "#", or
+     *                              false
+     *
+     * @return void
+     */
+    public function setFragment($fragment)
+    {
+        $this->_fragment = $fragment;
+    }
+
+    /**
+     * Returns the query string like an array as the variables would appear in
+     * $_GET in a PHP script. If the URL does not contain a "?", an empty array
+     * is returned.
+     *
+     * @return  array
+     */
+    public function getQueryVariables()
+    {
+        $pattern = '/[' .
+                   preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') .
+                   ']/';
+        $parts   = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY);
+        $return  = array();
+
+        foreach ($parts as $part) {
+            if (strpos($part, '=') !== false) {
+                list($key, $value) = explode('=', $part, 2);
+            } else {
+                $key   = $part;
+                $value = null;
+            }
+
+            if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
+                $key = rawurldecode($key);
+            }
+            $value = rawurldecode($value);
+
+            if ($this->getOption(self::OPTION_USE_BRACKETS) &&
+                preg_match('#^(.*)\[([0-9a-z_-]*)\]#i', $key, $matches)) {
+
+                $key = $matches[1];
+                $idx = $matches[2];
+
+                // Ensure is an array
+                if (empty($return[$key]) || !is_array($return[$key])) {
+                    $return[$key] = array();
+                }
+
+                // Add data
+                if ($idx === '') {
+                    $return[$key][] = $value;
+                } else {
+                    $return[$key][$idx] = $value;
+                }
+            } elseif (!$this->getOption(self::OPTION_USE_BRACKETS)
+                      && !empty($return[$key])
+            ) {
+                $return[$key]   = (array) $return[$key];
+                $return[$key][] = $value;
+            } else {
+                $return[$key] = $value;
+            }
+        }
+
+        return $return;
+    }
+
+    /**
+     * Sets the query string to the specified variable in the query string.
+     *
+     * @param array $array (name => value) array
+     *
+     * @return void
+     */
+    public function setQueryVariables(array $array)
+    {
+        if (!$array) {
+            $this->_query = false;
+        } else {
+            foreach ($array as $name => $value) {
+                if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
+                    $name = self::urlencode($name);
+                }
+
+                if (is_array($value)) {
+                    foreach ($value as $k => $v) {
+                        $parts[] = $this->getOption(self::OPTION_USE_BRACKETS)
+                            ? sprintf('%s[%s]=%s', $name, $k, $v)
+                            : ($name . '=' . $v);
+                    }
+                } elseif (!is_null($value)) {
+                    $parts[] = $name . '=' . self::urlencode($value);
+                } else {
+                    $parts[] = $name;
+                }
+            }
+            $this->_query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT),
+                                    $parts);
+        }
+    }
+
+    /**
+     * Sets the specified variable in the query string.
+     *
+     * @param string $name  variable name
+     * @param mixed  $value variable value
+     *
+     * @return  array
+     */
+    public function setQueryVariable($name, $value)
+    {
+        $array = $this->getQueryVariables();
+        $array[$name] = $value;
+        $this->setQueryVariables($array);
+    }
+
+    /**
+     * Removes the specifed variable from the query string.
+     *
+     * @param string $name a query string variable, e.g. "foo" in "?foo=1"
+     *
+     * @return void
+     */
+    public function unsetQueryVariable($name)
+    {
+        $array = $this->getQueryVariables();
+        unset($array[$name]);
+        $this->setQueryVariables($array);
+    }
+
+    /**
+     * Returns a string representation of this URL.
+     *
+     * @return  string
+     */
+    public function getURL()
+    {
+        // See RFC 3986, section 5.3
+        $url = "";
+
+        if ($this->_scheme !== false) {
+            $url .= $this->_scheme . ':';
+        }
+
+        $authority = $this->getAuthority();
+        if ($authority !== false) {
+            $url .= '//' . $authority;
+        }
+        $url .= $this->_path;
+
+        if ($this->_query !== false) {
+            $url .= '?' . $this->_query;
+        }
+
+        if ($this->_fragment !== false) {
+            $url .= '#' . $this->_fragment;
+        }
+
+        return $url;
+    }
+
+    /**
+     * Returns a string representation of this URL.
+     *
+     * @return  string
+     * @see toString()
+     */
+    public function __toString()
+    {
+        return $this->getURL();
+    }
+
+    /**
+     * Returns a normalized string representation of this URL. This is useful
+     * for comparison of URLs.
+     *
+     * @return  string
+     */
+    public function getNormalizedURL()
+    {
+        $url = clone $this;
+        $url->normalize();
+        return $url->getUrl();
+    }
+
+    /**
+     * Returns a normalized Net_URL2 instance.
+     *
+     * @return  Net_URL2
+     */
+    public function normalize()
+    {
+        // See RFC 3886, section 6
+
+        // Schemes are case-insensitive
+        if ($this->_scheme) {
+            $this->_scheme = strtolower($this->_scheme);
+        }
+
+        // Hostnames are case-insensitive
+        if ($this->_host) {
+            $this->_host = strtolower($this->_host);
+        }
+
+        // Remove default port number for known schemes (RFC 3986, section 6.2.3)
+        if ($this->_port &&
+            $this->_scheme &&
+            $this->_port == getservbyname($this->_scheme, 'tcp')) {
+
+            $this->_port = false;
+        }
+
+        // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1)
+        foreach (array('_userinfo', '_host', '_path') as $part) {
+            if ($this->$part) {
+                $this->$part = preg_replace('/%[0-9a-f]{2}/ie',
+                                            'strtoupper("\0")',
+                                            $this->$part);
+            }
+        }
+
+        // Path segment normalization (RFC 3986, section 6.2.2.3)
+        $this->_path = self::removeDotSegments($this->_path);
+
+        // Scheme based normalization (RFC 3986, section 6.2.3)
+        if ($this->_host && !$this->_path) {
+            $this->_path = '/';
+        }
+    }
+
+    /**
+     * Returns whether this instance represents an absolute URL.
+     *
+     * @return  bool
+     */
+    public function isAbsolute()
+    {
+        return (bool) $this->_scheme;
+    }
+
+    /**
+     * Returns an Net_URL2 instance representing an absolute URL relative to
+     * this URL.
+     *
+     * @param Net_URL2|string $reference relative URL
+     *
+     * @return Net_URL2
+     */
+    public function resolve($reference)
+    {
+        if (!$reference instanceof Net_URL2) {
+            $reference = new self($reference);
+        }
+        if (!$this->isAbsolute()) {
+            throw new Exception('Base-URL must be absolute');
+        }
+
+        // A non-strict parser may ignore a scheme in the reference if it is
+        // identical to the base URI's scheme.
+        if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
+            $reference->_scheme = false;
+        }
+
+        $target = new self('');
+        if ($reference->_scheme !== false) {
+            $target->_scheme = $reference->_scheme;
+            $target->setAuthority($reference->getAuthority());
+            $target->_path  = self::removeDotSegments($reference->_path);
+            $target->_query = $reference->_query;
+        } else {
+            $authority = $reference->getAuthority();
+            if ($authority !== false) {
+                $target->setAuthority($authority);
+                $target->_path  = self::removeDotSegments($reference->_path);
+                $target->_query = $reference->_query;
+            } else {
+                if ($reference->_path == '') {
+                    $target->_path = $this->_path;
+                    if ($reference->_query !== false) {
+                        $target->_query = $reference->_query;
+                    } else {
+                        $target->_query = $this->_query;
+                    }
+                } else {
+                    if (substr($reference->_path, 0, 1) == '/') {
+                        $target->_path = self::removeDotSegments($reference->_path);
+                    } else {
+                        // Merge paths (RFC 3986, section 5.2.3)
+                        if ($this->_host !== false && $this->_path == '') {
+                            $target->_path = '/' . $this->_path;
+                        } else {
+                            $i = strrpos($this->_path, '/');
+                            if ($i !== false) {
+                                $target->_path = substr($this->_path, 0, $i + 1);
+                            }
+                            $target->_path .= $reference->_path;
+                        }
+                        $target->_path = self::removeDotSegments($target->_path);
+                    }
+                    $target->_query = $reference->_query;
+                }
+                $target->setAuthority($this->getAuthority());
+            }
+            $target->_scheme = $this->_scheme;
+        }
+
+        $target->_fragment = $reference->_fragment;
+
+        return $target;
+    }
+
+    /**
+     * Removes dots as described in RFC 3986, section 5.2.4, e.g.
+     * "/foo/../bar/baz" => "/bar/baz"
+     *
+     * @param string $path a path
+     *
+     * @return string a path
+     */
+    public static function removeDotSegments($path)
+    {
+        $output = '';
+
+        // Make sure not to be trapped in an infinite loop due to a bug in this
+        // method
+        $j = 0;
+        while ($path && $j++ < 100) {
+            if (substr($path, 0, 2) == './') {
+                // Step 2.A
+                $path = substr($path, 2);
+            } elseif (substr($path, 0, 3) == '../') {
+                // Step 2.A
+                $path = substr($path, 3);
+            } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
+                // Step 2.B
+                $path = '/' . substr($path, 3);
+            } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
+                // Step 2.C
+                $path   = '/' . substr($path, 4);
+                $i      = strrpos($output, '/');
+                $output = $i === false ? '' : substr($output, 0, $i);
+            } elseif ($path == '.' || $path == '..') {
+                // Step 2.D
+                $path = '';
+            } else {
+                // Step 2.E
+                $i = strpos($path, '/');
+                if ($i === 0) {
+                    $i = strpos($path, '/', 1);
+                }
+                if ($i === false) {
+                    $i = strlen($path);
+                }
+                $output .= substr($path, 0, $i);
+                $path = substr($path, $i);
+            }
+        }
+
+        return $output;
+    }
+
+    /**
+     * Percent-encodes all non-alphanumeric characters except these: _ . - ~
+     * Similar to PHP's rawurlencode(), except that it also encodes ~ in PHP
+     * 5.2.x and earlier.
+     *
+     * @param  $raw the string to encode
+     * @return string
+     */
+    public static function urlencode($string)
+    {
+               $encoded = rawurlencode($string);
+               // This is only necessary in PHP < 5.3.
+               $encoded = str_replace('%7E', '~', $encoded);
+               return $encoded;
+    }
+
+    /**
+     * Returns a Net_URL2 instance representing the canonical URL of the
+     * currently executing PHP script.
+     *
+     * @return  string
+     */
+    public static function getCanonical()
+    {
+        if (!isset($_SERVER['REQUEST_METHOD'])) {
+            // ALERT - no current URL
+            throw new Exception('Script was not called through a webserver');
+        }
+
+        // Begin with a relative URL
+        $url = new self($_SERVER['PHP_SELF']);
+        $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+        $url->_host   = $_SERVER['SERVER_NAME'];
+        $port = $_SERVER['SERVER_PORT'];
+        if ($url->_scheme == 'http' && $port != 80 ||
+            $url->_scheme == 'https' && $port != 443) {
+
+            $url->_port = $port;
+        }
+        return $url;
+    }
+
+    /**
+     * Returns the URL used to retrieve the current request.
+     *
+     * @return  string
+     */
+    public static function getRequestedURL()
+    {
+        return self::getRequested()->getUrl();
+    }
+
+    /**
+     * Returns a Net_URL2 instance representing the URL used to retrieve the
+     * current request.
+     *
+     * @return  Net_URL2
+     */
+    public static function getRequested()
+    {
+        if (!isset($_SERVER['REQUEST_METHOD'])) {
+            // ALERT - no current URL
+            throw new Exception('Script was not called through a webserver');
+        }
+
+        // Begin with a relative URL
+        $url = new self($_SERVER['REQUEST_URI']);
+        $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
+        // Set host and possibly port
+        $url->setAuthority($_SERVER['HTTP_HOST']);
+        return $url;
+    }
+
+    /**
+     * Returns the value of the specified option.
+     *
+     * @param string $optionName The name of the option to retrieve
+     *
+     * @return  mixed
+     */
+    function getOption($optionName)
+    {
+        return isset($this->_options[$optionName])
+            ? $this->_options[$optionName] : false;
+    }
+}
+?>
\ No newline at end of file
diff --git a/typo3/contrib/pear/PEAR/Exception.php b/typo3/contrib/pear/PEAR/Exception.php
new file mode 100644 (file)
index 0000000..ef1cfe2
--- /dev/null
@@ -0,0 +1,390 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */
+/**
+ * PEAR_Exception
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Tomas V. V. Cox <cox@idecnet.com>
+ * @author     Hans Lellelid <hans@velum.net>
+ * @author     Bertrand Mansion <bmansion@mamasam.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Exception.php 313023 2011-07-06 19:17:11Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 1.3.3
+ */
+
+
+/**
+ * Base PEAR_Exception Class
+ *
+ * 1) Features:
+ *
+ * - Nestable exceptions (throw new PEAR_Exception($msg, $prev_exception))
+ * - Definable triggers, shot when exceptions occur
+ * - Pretty and informative error messages
+ * - Added more context info available (like class, method or cause)
+ * - cause can be a PEAR_Exception or an array of mixed
+ *   PEAR_Exceptions/PEAR_ErrorStack warnings
+ * - callbacks for specific exception classes and their children
+ *
+ * 2) Ideas:
+ *
+ * - Maybe a way to define a 'template' for the output
+ *
+ * 3) Inherited properties from PHP Exception Class:
+ *
+ * protected $message
+ * protected $code
+ * protected $line
+ * protected $file
+ * private   $trace
+ *
+ * 4) Inherited methods from PHP Exception Class:
+ *
+ * __clone
+ * __construct
+ * getMessage
+ * getCode
+ * getFile
+ * getLine
+ * getTraceSafe
+ * getTraceSafeAsString
+ * __toString
+ *
+ * 5) Usage example
+ *
+ * <code>
+ *  require_once 'PEAR/Exception.php';
+ *
+ *  class Test {
+ *     function foo() {
+ *         throw new PEAR_Exception('Error Message', ERROR_CODE);
+ *     }
+ *  }
+ *
+ *  function myLogger($pear_exception) {
+ *     echo $pear_exception->getMessage();
+ *  }
+ *  // each time a exception is thrown the 'myLogger' will be called
+ *  // (its use is completely optional)
+ *  PEAR_Exception::addObserver('myLogger');
+ *  $test = new Test;
+ *  try {
+ *     $test->foo();
+ *  } catch (PEAR_Exception $e) {
+ *     print $e;
+ *  }
+ * </code>
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Tomas V.V.Cox <cox@idecnet.com>
+ * @author     Hans Lellelid <hans@velum.net>
+ * @author     Bertrand Mansion <bmansion@mamasam.com>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 1.3.3
+ *
+ */
+class PEAR_Exception extends Exception
+{
+    const OBSERVER_PRINT = -2;
+    const OBSERVER_TRIGGER = -4;
+    const OBSERVER_DIE = -8;
+    protected $cause;
+    private static $_observers = array();
+    private static $_uniqueid = 0;
+    private $_trace;
+
+    /**
+     * Supported signatures:
+     *  - PEAR_Exception(string $message);
+     *  - PEAR_Exception(string $message, int $code);
+     *  - PEAR_Exception(string $message, Exception $cause);
+     *  - PEAR_Exception(string $message, Exception $cause, int $code);
+     *  - PEAR_Exception(string $message, PEAR_Error $cause);
+     *  - PEAR_Exception(string $message, PEAR_Error $cause, int $code);
+     *  - PEAR_Exception(string $message, array $causes);
+     *  - PEAR_Exception(string $message, array $causes, int $code);
+     * @param string exception message
+     * @param int|Exception|PEAR_Error|array|null exception cause
+     * @param int|null exception code or null
+     */
+    public function __construct($message, $p2 = null, $p3 = null)
+    {
+        if (is_int($p2)) {
+            $code = $p2;
+            $this->cause = null;
+        } elseif (is_object($p2) || is_array($p2)) {
+            // using is_object allows both Exception and PEAR_Error
+            if (is_object($p2) && !($p2 instanceof Exception)) {
+                if (!class_exists('PEAR_Error') || !($p2 instanceof PEAR_Error)) {
+                    throw new PEAR_Exception('exception cause must be Exception, ' .
+                        'array, or PEAR_Error');
+                }
+            }
+            $code = $p3;
+            if (is_array($p2) && isset($p2['message'])) {
+                // fix potential problem of passing in a single warning
+                $p2 = array($p2);
+            }
+            $this->cause = $p2;
+        } else {
+            $code = null;
+            $this->cause = null;
+        }
+        parent::__construct($message, $code);
+        $this->signal();
+    }
+
+    /**
+     * @param mixed $callback  - A valid php callback, see php func is_callable()
+     *                         - A PEAR_Exception::OBSERVER_* constant
+     *                         - An array(const PEAR_Exception::OBSERVER_*,
+     *                           mixed $options)
+     * @param string $label    The name of the observer. Use this if you want
+     *                         to remove it later with removeObserver()
+     */
+    public static function addObserver($callback, $label = 'default')
+    {
+        self::$_observers[$label] = $callback;
+    }
+
+    public static function removeObserver($label = 'default')
+    {
+        unset(self::$_observers[$label]);
+    }
+
+    /**
+     * @return int unique identifier for an observer
+     */
+    public static function getUniqueId()
+    {
+        return self::$_uniqueid++;
+    }
+
+    private function signal()
+    {
+        foreach (self::$_observers as $func) {
+            if (is_callable($func)) {
+                call_user_func($func, $this);
+                continue;
+            }
+            settype($func, 'array');
+            switch ($func[0]) {
+                case self::OBSERVER_PRINT :
+                    $f = (isset($func[1])) ? $func[1] : '%s';
+                    printf($f, $this->getMessage());
+                    break;
+                case self::OBSERVER_TRIGGER :
+                    $f = (isset($func[1])) ? $func[1] : E_USER_NOTICE;
+                    trigger_error($this->getMessage(), $f);
+                    break;
+                case self::OBSERVER_DIE :
+                    $f = (isset($func[1])) ? $func[1] : '%s';
+                    die(printf($f, $this->getMessage()));
+                    break;
+                default:
+                    trigger_error('invalid observer type', E_USER_WARNING);
+            }
+        }
+    }
+
+    /**
+     * Return specific error information that can be used for more detailed
+     * error messages or translation.
+     *
+     * This method may be overridden in child exception classes in order
+     * to add functionality not present in PEAR_Exception and is a placeholder
+     * to define API
+     *
+     * The returned array must be an associative array of parameter => value like so:
+     * <pre>
+     * array('name' => $name, 'context' => array(...))
+     * </pre>
+     * @return array
+     */
+    public function getErrorData()
+    {
+        return array();
+    }
+
+    /**
+     * Returns the exception that caused this exception to be thrown
+     * @access public
+     * @return Exception|array The context of the exception
+     */
+    public function getCause()
+    {
+        return $this->cause;
+    }
+
+    /**
+     * Function must be public to call on caused exceptions
+     * @param array
+     */
+    public function getCauseMessage(&$causes)
+    {
+        $trace = $this->getTraceSafe();
+        $cause = array('class'   => get_class($this),
+                       'message' => $this->message,
+                       'file' => 'unknown',
+                       'line' => 'unknown');
+        if (isset($trace[0])) {
+            if (isset($trace[0]['file'])) {
+                $cause['file'] = $trace[0]['file'];
+                $cause['line'] = $trace[0]['line'];
+            }
+        }
+        $causes[] = $cause;
+        if ($this->cause instanceof PEAR_Exception) {
+            $this->cause->getCauseMessage($causes);
+        } elseif ($this->cause instanceof Exception) {
+            $causes[] = array('class'   => get_class($this->cause),
+                              'message' => $this->cause->getMessage(),
+                              'file' => $this->cause->getFile(),
+                              'line' => $this->cause->getLine());
+        } elseif (class_exists('PEAR_Error') && $this->cause instanceof PEAR_Error) {
+            $causes[] = array('class' => get_class($this->cause),
+                              'message' => $this->cause->getMessage(),
+                              'file' => 'unknown',
+                              'line' => 'unknown');
+        } elseif (is_array($this->cause)) {
+            foreach ($this->cause as $cause) {
+                if ($cause instanceof PEAR_Exception) {
+                    $cause->getCauseMessage($causes);
+                } elseif ($cause instanceof Exception) {
+                    $causes[] = array('class'   => get_class($cause),
+                                   'message' => $cause->getMessage(),
+                                   'file' => $cause->getFile(),
+                                   'line' => $cause->getLine());
+                } elseif (class_exists('PEAR_Error') && $cause instanceof PEAR_Error) {
+                    $causes[] = array('class' => get_class($cause),
+                                      'message' => $cause->getMessage(),
+                                      'file' => 'unknown',
+                                      'line' => 'unknown');
+                } elseif (is_array($cause) && isset($cause['message'])) {
+                    // PEAR_ErrorStack warning
+                    $causes[] = array(
+                        'class' => $cause['package'],
+                        'message' => $cause['message'],
+                        'file' => isset($cause['context']['file']) ?
+                                            $cause['context']['file'] :
+                                            'unknown',
+                        'line' => isset($cause['context']['line']) ?
+                                            $cause['context']['line'] :
+                                            'unknown',
+                    );
+                }
+            }
+        }
+    }
+
+    public function getTraceSafe()
+    {
+        if (!isset($this->_trace)) {
+            $this->_trace = $this->getTrace();
+            if (empty($this->_trace)) {
+                $backtrace = debug_backtrace();
+                $this->_trace = array($backtrace[count($backtrace)-1]);
+            }
+        }
+        return $this->_trace;
+    }
+
+    public function getErrorClass()
+    {
+        $trace = $this->getTraceSafe();
+        return $trace[0]['class'];
+    }
+
+    public function getErrorMethod()
+    {
+        $trace = $this->getTraceSafe();
+        return $trace[0]['function'];
+    }
+
+    public function __toString()
+    {
+        if (isset($_SERVER['REQUEST_URI'])) {
+            return $this->toHtml();
+        }
+        return $this->toText();
+    }
+
+    public function toHtml()
+    {
+        $trace = $this->getTraceSafe();
+        $causes = array();
+        $this->getCauseMessage($causes);
+        $html =  '<table style="border: 1px" cellspacing="0">' . "\n";
+        foreach ($causes as $i => $cause) {
+            $html .= '<tr><td colspan="3" style="background: #ff9999">'
+               . str_repeat('-', $i) . ' <b>' . $cause['class'] . '</b>: '
+               . htmlspecialchars($cause['message']) . ' in <b>' . $cause['file'] . '</b> '
+               . 'on line <b>' . $cause['line'] . '</b>'
+               . "</td></tr>\n";
+        }
+        $html .= '<tr><td colspan="3" style="background-color: #aaaaaa; text-align: center; font-weight: bold;">Exception trace</td></tr>' . "\n"
+               . '<tr><td style="text-align: center; background: #cccccc; width:20px; font-weight: bold;">#</td>'
+               . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Function</td>'
+               . '<td style="text-align: center; background: #cccccc; font-weight: bold;">Location</td></tr>' . "\n";
+
+        foreach ($trace as $k => $v) {
+            $html .= '<tr><td style="text-align: center;">' . $k . '</td>'
+                   . '<td>';
+            if (!empty($v['class'])) {
+                $html .= $v['class'] . $v['type'];
+            }
+            $html .= $v['function'];
+            $args = array();
+            if (!empty($v['args'])) {
+                foreach ($v['args'] as $arg) {
+                    if (is_null($arg)) $args[] = 'null';
+                    elseif (is_array($arg)) $args[] = 'Array';
+                    elseif (is_object($arg)) $args[] = 'Object('.get_class($arg).')';
+                    elseif (is_bool($arg)) $args[] = $arg ? 'true' : 'false';
+                    elseif (is_int($arg) || is_double($arg)) $args[] = $arg;
+                    else {
+                        $arg = (string)$arg;
+                        $str = htmlspecialchars(substr($arg, 0, 16));
+                        if (strlen($arg) > 16) $str .= '&hellip;';
+                        $args[] = "'" . $str . "'";
+                    }
+                }
+            }
+            $html .= '(' . implode(', ',$args) . ')'
+                   . '</td>'
+                   . '<td>' . (isset($v['file']) ? $v['file'] : 'unknown')
+                   . ':' . (isset($v['line']) ? $v['line'] : 'unknown')
+                   . '</td></tr>' . "\n";
+        }
+        $html .= '<tr><td style="text-align: center;">' . ($k+1) . '</td>'
+               . '<td>{main}</td>'
+               . '<td>&nbsp;</td></tr>' . "\n"
+               . '</table>';
+        return $html;
+    }
+
+    public function toText()
+    {
+        $causes = array();
+        $this->getCauseMessage($causes);
+        $causeMsg = '';
+        foreach ($causes as $i => $cause) {
+            $causeMsg .= str_repeat(' ', $i) . $cause['class'] . ': '
+                   . $cause['message'] . ' in ' . $cause['file']
+                   . ' on line ' . $cause['line'] . "\n";
+        }
+        return $causeMsg . $this->getTraceAsString();
+    }
+}
+?>
\ No newline at end of file
index 4f5ae72..d216af8 100644 (file)
@@ -184,6 +184,9 @@ die();
        }
 }
 
+       // fix include path for pear packages
+set_include_path(get_include_path() . PATH_SEPARATOR . PATH_typo3 . 'contrib/pear/');
+
 // *********************
 // Unset variable(s) in global scope (fixes #13959)
 // *********************