From fa37967858c3c29000797e510e5f98aca8ab558f Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 28 Oct 2009 15:29:20 -0400 Subject: Rebuilt HTTPClient class as an extension of PEAR HTTP_Request2 package, adding redirect handling and convenience functions. Caching support will be added in future work after unit tests have been added. * extlib: add PEAR HTTP_Request2 0.4.1 alpha * extlib: update PEAR Net_URL2 to 0.3.0 beta for HTTP_Request2 compatibility * moved direct usage of CURL and file_get_contents to HTTPClient class, excluding external-sourced libraries Note some plugins haven't been tested yet. --- lib/ping.php | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'lib/ping.php') diff --git a/lib/ping.php b/lib/ping.php index 175bf8440..2797c1b2d 100644 --- a/lib/ping.php +++ b/lib/ping.php @@ -44,20 +44,18 @@ function ping_broadcast_notice($notice) { array('nickname' => $profile->nickname)), $tags)); - $context = stream_context_create(array('http' => array('method' => "POST", - 'header' => - "Content-Type: text/xml\r\n". - "User-Agent: StatusNet/".STATUSNET_VERSION."\r\n", - 'content' => $req))); - $file = file_get_contents($notify_url, false, $context); + $request = new HTTPClient($notify_url, HTTP_Request2::METHOD_POST); + $request->setHeader('Content-Type', 'text/xml'); + $request->setBody($req); + $httpResponse = $request->send(); - if ($file === false || mb_strlen($file) == 0) { + if (!$httpResponse || mb_strlen($httpResponse->getBody()) == 0) { common_log(LOG_WARNING, "XML-RPC empty results for ping ($notify_url, $notice->id) "); continue; } - $response = xmlrpc_decode($file); + $response = xmlrpc_decode($httpResponse->getBody()); if (is_array($response) && xmlrpc_is_fault($response)) { common_log(LOG_WARNING, -- cgit v1.2.3-54-g00ecf From b22fc5b74aecd505d4e2df01258171fc65d312cf Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 2 Nov 2009 06:56:31 -0800 Subject: Revert "Rebuilt HTTPClient class as an extension of PEAR HTTP_Request2 package, adding redirect handling and convenience functions." Going to restructure a little more before finalizing this... This reverts commit fa37967858c3c29000797e510e5f98aca8ab558f. --- classes/File_redirection.php | 68 +- extlib/HTTP/Request2.php | 844 ------------------ extlib/HTTP/Request2/Adapter.php | 152 ---- extlib/HTTP/Request2/Adapter/Curl.php | 383 -------- extlib/HTTP/Request2/Adapter/Mock.php | 171 ---- extlib/HTTP/Request2/Adapter/Socket.php | 971 --------------------- extlib/HTTP/Request2/Exception.php | 62 -- extlib/HTTP/Request2/MultipartBody.php | 274 ------ extlib/HTTP/Request2/Observer/Log.php | 215 ----- extlib/HTTP/Request2/Response.php | 549 ------------ extlib/Net/URL2.php | 471 ++++------ install.php | 7 - lib/Shorturl_api.php | 23 +- lib/curlclient.php | 179 ++++ lib/default.php | 2 + lib/httpclient.php | 180 +--- lib/oauthclient.php | 65 +- lib/ping.php | 14 +- lib/snapshot.php | 21 +- plugins/BlogspamNetPlugin.php | 17 +- plugins/LinkbackPlugin.php | 36 +- plugins/SimpleUrl/SimpleUrlPlugin.php | 11 +- .../TwitterBridge/daemons/synctwitterfriends.php | 4 +- .../TwitterBridge/daemons/twitterstatusfetcher.php | 45 +- plugins/TwitterBridge/twitter.php | 2 +- plugins/TwitterBridge/twitterauthorization.php | 2 +- plugins/TwitterBridge/twitterbasicauthclient.php | 66 +- plugins/WikiHashtagsPlugin.php | 12 +- scripts/enjitqueuehandler.php | 64 +- 29 files changed, 669 insertions(+), 4241 deletions(-) delete mode 100644 extlib/HTTP/Request2.php delete mode 100644 extlib/HTTP/Request2/Adapter.php delete mode 100644 extlib/HTTP/Request2/Adapter/Curl.php delete mode 100644 extlib/HTTP/Request2/Adapter/Mock.php delete mode 100644 extlib/HTTP/Request2/Adapter/Socket.php delete mode 100644 extlib/HTTP/Request2/Exception.php delete mode 100644 extlib/HTTP/Request2/MultipartBody.php delete mode 100644 extlib/HTTP/Request2/Observer/Log.php delete mode 100644 extlib/HTTP/Request2/Response.php create mode 100644 lib/curlclient.php (limited to 'lib/ping.php') diff --git a/classes/File_redirection.php b/classes/File_redirection.php index b7945699a..79052bf7d 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -47,15 +47,18 @@ class File_redirection extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - static function _commonHttp($url, $redirs) { - $request = new HTTPClient($url); - $request->setConfig(array( - 'connect_timeout' => 10, // # seconds to wait - 'max_redirs' => $redirs, // # max number of http redirections to follow - 'follow_redirects' => true, // Follow redirects - 'store_body' => false, // We won't need body content here. - )); - return $request; + function _commonCurl($url, $redirs) { + $curlh = curl_init(); + curl_setopt($curlh, CURLOPT_URL, $url); + curl_setopt($curlh, CURLOPT_AUTOREFERER, true); // # setup referer header when folowing redirects + curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 10); // # seconds to wait + curl_setopt($curlh, CURLOPT_MAXREDIRS, $redirs); // # max number of http redirections to follow + curl_setopt($curlh, CURLOPT_USERAGENT, USER_AGENT); + curl_setopt($curlh, CURLOPT_FOLLOWLOCATION, true); // Follow redirects + curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curlh, CURLOPT_FILETIME, true); + curl_setopt($curlh, CURLOPT_HEADER, true); // Include header in output + return $curlh; } function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) { @@ -79,39 +82,32 @@ class File_redirection extends Memcached_DataObject if(strpos($short_url,'://') === false){ return $short_url; } - try { - $request = self::_commonHttp($short_url, $redirs); - // Don't include body in output - $request->setMethod(HTTP_Request2::METHOD_HEAD); - $response = $request->send(); - - if (405 == $response->getCode()) { - // Server doesn't support HEAD method? Can this really happen? - // We'll try again as a GET and ignore the response data. - $request = self::_commonHttp($short_url, $redirs); - $response = $request->send(); - } - } catch (Exception $e) { - // Invalid URL or failure to reach server - return $short_url; + $curlh = File_redirection::_commonCurl($short_url, $redirs); + // Don't include body in output + curl_setopt($curlh, CURLOPT_NOBODY, true); + curl_exec($curlh); + $info = curl_getinfo($curlh); + curl_close($curlh); + + if (405 == $info['http_code']) { + $curlh = File_redirection::_commonCurl($short_url, $redirs); + curl_exec($curlh); + $info = curl_getinfo($curlh); + curl_close($curlh); } - if ($response->getRedirectCount() && File::isProtected($response->getUrl())) { - // Bump back up the redirect chain until we find a non-protected URL - return self::_redirectWhere_imp($short_url, $response->getRedirectCount() - 1, true); + if (!empty($info['redirect_count']) && File::isProtected($info['url'])) { + return File_redirection::_redirectWhere_imp($short_url, $info['redirect_count'] - 1, true); } - $ret = array('code' => $response->getCode() - , 'redirects' => $response->getRedirectCount() - , 'url' => $response->getUrl()); + $ret = array('code' => $info['http_code'] + , 'redirects' => $info['redirect_count'] + , 'url' => $info['url']); - $type = $response->getHeader('Content-Type'); - if ($type) $ret['type'] = $type; + if (!empty($info['content_type'])) $ret['type'] = $info['content_type']; if ($protected) $ret['protected'] = true; - $size = $request->getHeader('Content-Length'); // @fixme bytes? - if ($size) $ret['size'] = $size; - $time = $request->getHeader('Last-Modified'); - if ($time) $ret['time'] = strtotime($time); + if (!empty($info['download_content_length'])) $ret['size'] = $info['download_content_length']; + if (isset($info['filetime']) && ($info['filetime'] > 0)) $ret['time'] = $info['filetime']; return $ret; } diff --git a/extlib/HTTP/Request2.php b/extlib/HTTP/Request2.php deleted file mode 100644 index e06bb86bc..000000000 --- a/extlib/HTTP/Request2.php +++ /dev/null @@ -1,844 +0,0 @@ - - * 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 - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Request2.php 278226 2009-04-03 21:32:48Z 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 - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 0.4.1 - * @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://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 - ); - - /** - * 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; - - - /** - * 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()) - { - if (!empty($url)) { - $this->setUrl($url); - } - if (!empty($method)) { - $this->setMethod($method); - } - $this->setConfig($config); - $this->setHeader('user-agent', 'HTTP_Request2/0.4.1 ' . - '(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_Exception - */ - public function setUrl($url) - { - if (is_string($url)) { - $url = new Net_URL2($url); - } - if (!$url instanceof Net_URL2) { - throw new HTTP_Request2_Exception('Parameter is not a valid HTTP URL'); - } - // 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_Exception 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_Exception("Invalid request method '{$method}'"); - } - $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: - *
    - *
  • 'adapter' - adapter to use (string)
  • - *
  • 'connect_timeout' - Connection timeout in seconds (integer)
  • - *
  • 'timeout' - Total number of seconds a request can take. - * Use 0 for no limit, should be greater than - * 'connect_timeout' if set (integer)
  • - *
  • 'use_brackets' - Whether to append [] to array variable names (bool)
  • - *
  • 'protocol_version' - HTTP Version to use, '1.0' or '1.1' (string)
  • - *
  • 'buffer_size' - Buffer size to use for reading and writing (int)
  • - *
  • '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)
  • - *
  • 'proxy_host' - Proxy server host (string)
  • - *
  • 'proxy_port' - Proxy server port (integer)
  • - *
  • 'proxy_user' - Proxy auth username (string)
  • - *
  • 'proxy_password' - Proxy auth password (string)
  • - *
  • 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)
  • - *
  • 'ssl_verify_peer' - Whether to verify peer's SSL certificate (bool)
  • - *
  • 'ssl_verify_host' - Whether to check that Common Name in SSL - * certificate matches host name (bool)
  • - *
  • 'ssl_cafile' - Cerificate Authority file to verify the peer - * with (use with 'ssl_verify_peer') (string)
  • - *
  • 'ssl_capath' - Directory holding multiple Certificate - * Authority files (string)
  • - *
  • 'ssl_local_cert' - Name of a file containing local cerificate (string)
  • - *
  • 'ssl_passphrase' - Passphrase with which local certificate - * was encoded (string)
  • - *
  • 'digest_compat_ie' - Whether to imitate behaviour of MSIE 5 and 6 - * in using URL without query string in digest - * authentication (boolean)
  • - *
- * - * @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_Exception 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_Exception( - "Unknown configuration parameter '{$nameOrConfig}'" - ); - } - $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_Exception 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_Exception( - "Unknown configuration parameter '{$name}'" - ); - } - 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. - * - * - * $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 - * - * - * @param string|array header name, header string ('Header: value') - * or an array of headers - * @param string|null header value, header will be removed if null - * @return HTTP_Request2 - * @throws HTTP_Request2_Exception - */ - public function setHeader($name, $value = null) - { - if (is_array($name)) { - foreach ($name as $k => $v) { - if (is_string($k)) { - $this->setHeader($k, $v); - } else { - $this->setHeader($v); - } - } - } 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_Exception("Invalid header name '{$name}'"); - } - // Header names are case insensitive anyway - $name = strtolower($name); - if (null === $value) { - unset($this->headers[$name]); - } 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; - } - - /** - * Appends a cookie to "Cookie:" header - * - * @param string cookie name - * @param string cookie value - * @return HTTP_Request2 - * @throws HTTP_Request2_Exception - */ - public function addCookie($name, $value) - { - $cookie = $name . '=' . $value; - if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) { - throw new HTTP_Request2_Exception("Invalid cookie: '{$cookie}'"); - } - $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; '; - $this->setHeader('cookie', $cookies . $cookie); - - return $this; - } - - /** - * Sets the request body - * - * @param string Either a string with the body or filename containing body - * @param bool Whether first parameter is a filename - * @return HTTP_Request2 - * @throws HTTP_Request2_Exception - */ - public function setBody($body, $isFilename = false) - { - if (!$isFilename) { - $this->body = (string)$body; - } else { - if (!($fp = @fopen($body, 'rb'))) { - throw new HTTP_Request2_Exception("Cannot open file {$body}"); - } - $this->body = $fp; - if (empty($this->headers['content-type'])) { - $this->setHeader('content-type', self::detectMimeType($body)); - } - } - - 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 ('application/x-www-form-urlencoded' == $this->headers['content-type']) { - $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 ('multipart/form-data' == $this->headers['content-type']) { - 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. - * - * @param string name of file-upload field - * @param mixed full name of local file - * @param string filename to send in the request - * @param string content-type of file being uploaded - * @return HTTP_Request2 - * @throws HTTP_Request2_Exception - */ - public function addUpload($fieldName, $filename, $sendFilename = null, - $contentType = null) - { - if (!is_array($filename)) { - if (!($fp = @fopen($filename, 'rb'))) { - throw new HTTP_Request2_Exception("Cannot open file {$filename}"); - } - $this->uploads[$fieldName] = array( - 'fp' => $fp, - 'filename' => empty($sendFilename)? basename($filename): $sendFilename, - 'size' => filesize($filename), - 'type' => empty($contentType)? self::detectMimeType($filename): $contentType - ); - } else { - $fps = $names = $sizes = $types = array(); - foreach ($filename as $f) { - if (!is_array($f)) { - $f = array($f); - } - if (!($fp = @fopen($f[0], 'rb'))) { - throw new HTTP_Request2_Exception("Cannot open file {$f[0]}"); - } - $fps[] = $fp; - $names[] = empty($f[1])? basename($f[0]): $f[1]; - $sizes[] = filesize($f[0]); - $types[] = empty($f[2])? self::detectMimeType($f[0]): $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: - *
    - *
  • 'connect' - after connection to remote server, - * data is the destination (string)
  • - *
  • 'disconnect' - after disconnection from server
  • - *
  • 'sentHeaders' - after sending the request headers, - * data is the headers sent (string)
  • - *
  • 'sentBodyPart' - after sending a part of the request body, - * data is the length of that part (int)
  • - *
  • 'receivedHeaders' - after receiving the response headers, - * data is HTTP_Request2_Response object
  • - *
  • 'receivedBodyPart' - after receiving a part of the response - * body, data is that part (string)
  • - *
  • 'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still - * encoded by Content-Encoding
  • - *
  • 'receivedBody' - after receiving the complete response - * body, data is HTTP_Request2_Response object
  • - *
- * 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 - * - * $request->setAdapter('curl'); - * - * will work. - * - * @param string|HTTP_Request2_Adapter - * @return HTTP_Request2 - * @throws HTTP_Request2_Exception - */ - 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_Exception("Class {$adapter} not found"); - } - } - $adapter = new $adapter; - } - if (!$adapter instanceof HTTP_Request2_Adapter) { - throw new HTTP_Request2_Exception('Parameter is not a HTTP request adapter'); - } - $this->adapter = $adapter; - - return $this; - } - - /** - * 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) { - throw new HTTP_Request2_Exception('No URL given'); - } elseif (!$this->url->isAbsolute()) { - throw new HTTP_Request2_Exception('Absolute URL required'); - } elseif (!in_array(strtolower($this->url->getScheme()), array('https', 'http'))) { - throw new HTTP_Request2_Exception('Not a HTTP URL'); - } - if (empty($this->adapter)) { - $this->setAdapter($this->getConfig('adapter')); - } - // magic_quotes_runtime may break file uploads and chunked response - // processing; see bug #4543 - if ($magicQuotes = ini_get('magic_quotes_runtime')) { - ini_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) { - ini_set('magic_quotes_runtime', true); - } - if (!empty($oldEncoding)) { - mb_internal_encoding($oldEncoding); - } - // rethrow the exception - if (!empty($e)) { - throw $e; - } - return $response; - } - - /** - * 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/extlib/HTTP/Request2/Adapter.php b/extlib/HTTP/Request2/Adapter.php deleted file mode 100644 index 39b092b34..000000000 --- a/extlib/HTTP/Request2/Adapter.php +++ /dev/null @@ -1,152 +0,0 @@ - - * 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 - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Adapter.php 274684 2009-01-26 23:07:27Z 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 - * @version Release: 0.4.1 - */ -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 - ) { - unset($headers['content-type']); - // 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']); - } - } else { - if (empty($headers['content-type'])) { - $headers['content-type'] = 'application/x-www-form-urlencoded'; - } - $headers['content-length'] = $this->contentLength; - } - } -} -?> diff --git a/extlib/HTTP/Request2/Adapter/Curl.php b/extlib/HTTP/Request2/Adapter/Curl.php deleted file mode 100644 index 4d4de0dcc..000000000 --- a/extlib/HTTP/Request2/Adapter/Curl.php +++ /dev/null @@ -1,383 +0,0 @@ - - * 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 - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Curl.php 278226 2009-04-03 21:32:48Z 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 - * @version Release: 0.4.1 - */ -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 - ); - - /** - * 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; - - /** - * 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_Exception('cURL extension not available'); - } - - $this->request = $request; - $this->response = null; - $this->position = 0; - $this->eventSentHeaders = false; - $this->eventReceivedHeaders = false; - - try { - if (false === curl_exec($ch = $this->createCurlHandle())) { - $errorMessage = 'Error sending request: #' . curl_errno($ch) . - ' ' . curl_error($ch); - } - } catch (Exception $e) { - } - $this->lastInfo = curl_getinfo($ch); - curl_close($ch); - - if (!empty($e)) { - throw $e; - } elseif (!empty($errorMessage)) { - throw new HTTP_Request2_Exception($errorMessage); - } - - if (0 < $this->lastInfo['size_download']) { - $this->request->setLastEvent('receivedBody', $this->response); - } - return $this->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_Exception - */ - protected function createCurlHandle() - { - $ch = curl_init(); - - curl_setopt_array($ch, array( - // setup callbacks - CURLOPT_READFUNCTION => array($this, 'callbackReadBody'), - CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'), - CURLOPT_WRITEFUNCTION => array($this, 'callbackWriteBody'), - // disallow redirects - CURLOPT_FOLLOWLOCATION => false, - // 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() - )); - - // 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; - 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_Exception('Proxy port not provided'); - } - 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 - if (0 == strcasecmp($this->request->getUrl()->getScheme(), 'https')) { - 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'] = ''; - } - - // 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); - - // 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; - } - - /** - * 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) - ); - } - $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); - } 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); - } - $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_Exception("Malformed response: {$string}"); - } - if ($this->request->getConfig('store_body')) { - $this->response->appendBody($string); - } - $this->request->setLastEvent('receivedBodyPart', $string); - return strlen($string); - } -} -?> diff --git a/extlib/HTTP/Request2/Adapter/Mock.php b/extlib/HTTP/Request2/Adapter/Mock.php deleted file mode 100644 index 89688003b..000000000 --- a/extlib/HTTP/Request2/Adapter/Mock.php +++ /dev/null @@ -1,171 +0,0 @@ - - * 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 - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Mock.php 274406 2009-01-23 18:01:57Z 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() - * - * $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(); - * - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 0.4.1 - */ -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 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, - * a HTTP_Request2_Response or Exception object - * @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/extlib/HTTP/Request2/Adapter/Socket.php b/extlib/HTTP/Request2/Adapter/Socket.php deleted file mode 100644 index ff44d4959..000000000 --- a/extlib/HTTP/Request2/Adapter/Socket.php +++ /dev/null @@ -1,971 +0,0 @@ - - * 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 - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Socket.php 279760 2009-05-03 10:46:42Z 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 - * @version Release: 0.4.1 - */ -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; - - /** - * Global timeout, exception will be raised if request continues past this time - * @var integer - */ - protected $timeout = null; - - /** - * Remaining length of the current chunk, when reading chunked response - * @var integer - * @see readChunked() - */ - protected $chunkLength = 0; - - /** - * 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; - $keepAlive = $this->connect(); - $headers = $this->prepareHeaders(); - - // Use global request timeout if given, see feature requests #5735, #8964 - if ($timeout = $request->getConfig('timeout')) { - $this->timeout = time() + $timeout; - } else { - $this->timeout = null; - } - - try { - if (false === @fwrite($this->socket, $headers, strlen($headers))) { - throw new HTTP_Request2_Exception('Error writing request'); - } - // provide request headers to the observer, see request #7633 - $this->request->setLastEvent('sentHeaders', $headers); - $this->writeBody(); - - if ($this->timeout && time() > $this->timeout) { - throw new HTTP_Request2_Exception( - 'Request timed out after ' . - $request->getConfig('timeout') . ' second(s)' - ); - } - - $response = $this->readResponse(); - - 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(); - throw $e; - } - - return $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_Exception('Proxy port not provided'); - } - $proxy = true; - } else { - $host = $reqHost; - $port = $reqPort; - $proxy = false; - } - - if ($tunnel && !$proxy) { - throw new HTTP_Request2_Exception( - "Trying to perform CONNECT request without proxy" - ); - } - if ($secure && !in_array('ssl', stream_get_transports())) { - throw new HTTP_Request2_Exception( - 'Need OpenSSL support for https:// requests' - ); - } - - // 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_Exception( - "Error setting SSL context option '{$name}'" - ); - } - } - $this->socket = @stream_socket_client( - $remote, $errno, $errstr, - $this->request->getConfig('connect_timeout'), - STREAM_CLIENT_CONNECT, $context - ); - if (!$this->socket) { - throw new HTTP_Request2_Exception( - "Unable to connect to {$remote}. Error #{$errno}: {$errstr}" - ); - } - $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_Exception( - '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_Exception( - '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'); - $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'); - } - } - - /** - * 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 - * here are defined as quoted-string 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_Exception 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_Exception( - "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_Exception( - "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_Exception - */ - 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_Exception( - "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_Exception - */ - 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_Exception( - "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'; - } - - $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_Exception - */ - 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_Exception('Error writing request'); - } - // Provide the length of written string to the observer, request #7630 - $this->request->setLastEvent('sentBodyPart', strlen($str)); - $position += strlen($str); - } - } - - /** - * 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); - 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_Exception In case of timeout - */ - protected function readLine($bufferSize) - { - $line = ''; - while (!feof($this->socket)) { - if ($this->timeout) { - stream_set_timeout($this->socket, max($this->timeout - time(), 1)); - } - $line .= @fgets($this->socket, $bufferSize); - $info = stream_get_meta_data($this->socket); - if ($info['timed_out'] || $this->timeout && time() > $this->timeout) { - throw new HTTP_Request2_Exception( - 'Request timed out after ' . - $this->request->getConfig('timeout') . ' second(s)' - ); - } - 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_Exception In case of timeout - */ - protected function fread($length) - { - if ($this->timeout) { - stream_set_timeout($this->socket, max($this->timeout - time(), 1)); - } - $data = fread($this->socket, $length); - $info = stream_get_meta_data($this->socket); - if ($info['timed_out'] || $this->timeout && time() > $this->timeout) { - throw new HTTP_Request2_Exception( - 'Request timed out after ' . - $this->request->getConfig('timeout') . ' second(s)' - ); - } - 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_Exception - */ - 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_Exception( - "Cannot decode chunked response, invalid chunk length '{$line}'" - ); - } 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/extlib/HTTP/Request2/Exception.php b/extlib/HTTP/Request2/Exception.php deleted file mode 100644 index bfef7d6c2..000000000 --- a/extlib/HTTP/Request2/Exception.php +++ /dev/null @@ -1,62 +0,0 @@ - - * 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 - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Exception.php 273003 2009-01-07 19:28:22Z avb $ - * @link http://pear.php.net/package/HTTP_Request2 - */ - -/** - * Base class for exceptions in PEAR - */ -require_once 'PEAR/Exception.php'; - -/** - * Exception class for HTTP_Request2 package - * - * Such a class is required by the Exception RFC: - * http://pear.php.net/pepr/pepr-proposal-show.php?id=132 - * - * @category HTTP - * @package HTTP_Request2 - * @version Release: 0.4.1 - */ -class HTTP_Request2_Exception extends PEAR_Exception -{ -} -?> \ No newline at end of file diff --git a/extlib/HTTP/Request2/MultipartBody.php b/extlib/HTTP/Request2/MultipartBody.php deleted file mode 100644 index d8afd8344..000000000 --- a/extlib/HTTP/Request2/MultipartBody.php +++ /dev/null @@ -1,274 +0,0 @@ - - * 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 - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: MultipartBody.php 287306 2009-08-14 15:22:52Z 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 - * @version Release: 0.4.1 - * @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/extlib/HTTP/Request2/Observer/Log.php b/extlib/HTTP/Request2/Observer/Log.php deleted file mode 100644 index b1a055278..000000000 --- a/extlib/HTTP/Request2/Observer/Log.php +++ /dev/null @@ -1,215 +0,0 @@ - - * 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 - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Log.php 272593 2009-01-02 16:27:14Z 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: - * - * 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(); - * - * - * A more complex example with PEAR Log: - * - * 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(); - * - * - * @category HTTP - * @package HTTP_Request2 - * @author David Jean Louis - * @author Alexey Borzov - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version Release: 0.4.1 - * @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', - 'sentBodyPart', - '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, 'w'))) { - throw new HTTP_Request2_Exception("Unable to open '{$target}'"); - } - } - - // }}} - // update() {{{ - - /** - * Called when the request notify 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 'sentBodyPart': - $this->log('> ' . $event['data']); - 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() {{{ - - /** - * Log 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/extlib/HTTP/Request2/Response.php b/extlib/HTTP/Request2/Response.php deleted file mode 100644 index c7c1021fb..000000000 --- a/extlib/HTTP/Request2/Response.php +++ /dev/null @@ -1,549 +0,0 @@ - - * 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 - * @license http://opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: Response.php 287948 2009-09-01 17:12:18Z 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: - * - * $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()); - * - * - * - * @category HTTP - * @package HTTP_Request2 - * @author Alexey Borzov - * @version Release: 0.4.1 - * @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; - - /** - * 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 - * @throws HTTP_Request2_Exception if status line is invalid according to spec - */ - public function __construct($statusLine, $bodyEncoded = true) - { - if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) { - throw new HTTP_Request2_Exception("Malformed response: {$statusLine}"); - } - $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; - } - - /** - * 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; - - // string - } 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://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 status code - * @return integer - */ - public function getStatus() - { - return $this->code; - } - - /** - * Returns the reason phrase - * @return string - */ - public function getReasonPhrase() - { - return $this->reasonPhrase; - } - - /** - * 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 (!$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_Exception - * @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_Exception('Unable to decode body: gzip extension not available'); - } - $method = ord(substr($data, 2, 1)); - if (8 != $method) { - throw new HTTP_Request2_Exception('Error parsing gzip header: unknown compression method'); - } - $flags = ord(substr($data, 3, 1)); - if ($flags & 224) { - throw new HTTP_Request2_Exception('Error parsing gzip header: reserved bits are set'); - } - - // 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_Exception('Error parsing gzip header: data too short'); - } - $extraLength = unpack('v', substr($data, 10, 2)); - if ($length - $headerLength - 2 - $extraLength[1] < 8) { - throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); - } - $headerLength += $extraLength[1] + 2; - } - // file name, need to skip that - if ($flags & 8) { - if ($length - $headerLength - 1 < 8) { - throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); - } - $filenameLength = strpos(substr($data, $headerLength), chr(0)); - if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) { - throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); - } - $headerLength += $filenameLength + 1; - } - // comment, need to skip that also - if ($flags & 16) { - if ($length - $headerLength - 1 < 8) { - throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); - } - $commentLength = strpos(substr($data, $headerLength), chr(0)); - if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) { - throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); - } - $headerLength += $commentLength + 1; - } - // have a CRC for header. let's check - if ($flags & 2) { - if ($length - $headerLength - 2 < 8) { - throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); - } - $crcReal = 0xffff & crc32(substr($data, 0, $headerLength)); - $crcStored = unpack('v', substr($data, $headerLength, 2)); - if ($crcReal != $crcStored[1]) { - throw new HTTP_Request2_Exception('Header CRC check failed'); - } - $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_Exception('gzinflate() call failed'); - } elseif ($dataSize != strlen($unpacked)) { - throw new HTTP_Request2_Exception('Data size check failed'); - } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) { - throw new HTTP_Request2_Exception('Data CRC check failed'); - } - return $unpacked; - } - - /** - * Decodes the message-body encoded by deflate - * - * @param string deflate-encoded data - * @return string decoded data - * @throws HTTP_Request2_Exception - */ - public static function decodeDeflate($data) - { - if (!function_exists('gzuncompress')) { - throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available'); - } - // 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/extlib/Net/URL2.php b/extlib/Net/URL2.php index f7fbcd9ce..7a654aed8 100644 --- a/extlib/Net/URL2.php +++ b/extlib/Net/URL2.php @@ -1,58 +1,44 @@ | +// +-----------------------------------------------------------------------+ +// +// $Id: URL2.php,v 1.10 2008/04/26 21:57:08 schmidt Exp $ +// +// Net_URL2 Class (PHP5 Only) + +// This code is released under the BSD License - http://www.opensource.org/licenses/bsd-license.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 PHP_LexerGenerator 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 - * @copyright 2007-2008 Peytz & Co. A/S - * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @version CVS: $Id: URL2.php 286661 2009-08-02 12:50:54Z 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 - * @copyright 2007-2008 Peytz & Co. ApS - * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @version Release: @package_version@ - * @link http://pear.php.net/package/Net_URL2 + * @license BSD License */ class Net_URL2 { @@ -60,24 +46,24 @@ class Net_URL2 * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default * is true. */ - const OPTION_STRICT = 'strict'; + const OPTION_STRICT = 'strict'; /** * Represent arrays in query using PHP's [] notation. Default is true. */ - const OPTION_USE_BRACKETS = 'use_brackets'; + const OPTION_USE_BRACKETS = 'use_brackets'; /** * URL-encode query variable keys. Default is true. */ - const OPTION_ENCODE_KEYS = 'encode_keys'; + const OPTION_ENCODE_KEYS = 'encode_keys'; /** * Query variable separators when parsing the query string. Every character * is considered a separator. Default is specified by the * arg_separator.input php.ini setting (this defaults to "&"). */ - const OPTION_SEPARATOR_INPUT = 'input_separator'; + const OPTION_SEPARATOR_INPUT = 'input_separator'; /** * Query variable separator used when generating the query string. Default @@ -89,7 +75,7 @@ class Net_URL2 /** * Default options corresponds to how PHP handles $_GET. */ - private $_options = array( + private $options = array( self::OPTION_STRICT => true, self::OPTION_USE_BRACKETS => true, self::OPTION_ENCODE_KEYS => true, @@ -100,43 +86,41 @@ class Net_URL2 /** * @var string|bool */ - private $_scheme = false; + private $scheme = false; /** * @var string|bool */ - private $_userinfo = false; + private $userinfo = false; /** * @var string|bool */ - private $_host = false; + private $host = false; /** * @var int|bool */ - private $_port = false; + private $port = false; /** * @var string */ - private $_path = ''; + private $path = ''; /** * @var string|bool */ - private $_query = false; + private $query = false; /** * @var string|bool */ - private $_fragment = false; + private $fragment = false; /** - * Constructor. - * * @param string $url an absolute or relative URL - * @param array $options an array of OPTION_xxx constants + * @param array $options */ public function __construct($url, $options = null) { @@ -146,12 +130,12 @@ class Net_URL2 ini_get('arg_separator.output')); if (is_array($options)) { foreach ($options as $optionName => $value) { - $this->setOption($optionName, $value); + $this->setOption($optionName); } } if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) { - $this->_scheme = $reg[1]; + $this->scheme = $reg[1]; $url = substr($url, strlen($reg[0])); } @@ -161,58 +145,19 @@ class Net_URL2 } $i = strcspn($url, '?#'); - $this->_path = substr($url, 0, $i); + $this->path = substr($url, 0, $i); $url = substr($url, $i); if (preg_match('@^\?([^#]*)@', $url, $reg)) { - $this->_query = $reg[1]; + $this->query = $reg[1]; $url = substr($url, strlen($reg[0])); } if ($url) { - $this->_fragment = substr($url, 1); + $this->fragment = substr($url, 1); } } - /** - * 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. @@ -221,23 +166,18 @@ class Net_URL2 */ public function getScheme() { - return $this->_scheme; + 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 + * @param string|bool $scheme * * @return void * @see getScheme() */ public function setScheme($scheme) { - $this->_scheme = $scheme; + $this->scheme = $scheme; } /** @@ -248,9 +188,7 @@ class Net_URL2 */ public function getUser() { - return $this->_userinfo !== false - ? preg_replace('@:.*$@', '', $this->_userinfo) - : false; + return $this->userinfo !== false ? preg_replace('@:.*$@', '', $this->userinfo) : false; } /** @@ -263,9 +201,7 @@ class Net_URL2 */ public function getPassword() { - return $this->_userinfo !== false - ? substr(strstr($this->_userinfo, ':'), 1) - : false; + return $this->userinfo !== false ? substr(strstr($this->userinfo, ':'), 1) : false; } /** @@ -276,7 +212,7 @@ class Net_URL2 */ public function getUserinfo() { - return $this->_userinfo; + return $this->userinfo; } /** @@ -284,15 +220,15 @@ class Net_URL2 * in the userinfo part as username ":" password. * * @param string|bool $userinfo userinfo or username - * @param string|bool $password optional password, or false + * @param string|bool $password * * @return void */ public function setUserinfo($userinfo, $password = false) { - $this->_userinfo = $userinfo; + $this->userinfo = $userinfo; if ($password !== false) { - $this->_userinfo .= ':' . $password; + $this->userinfo .= ':' . $password; } } @@ -300,24 +236,21 @@ class Net_URL2 * 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 + * @return string|bool */ public function getHost() { - return $this->_host; + 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 + * @param string|bool $host * * @return void */ public function setHost($host) { - $this->_host = $host; + $this->host = $host; } /** @@ -328,72 +261,65 @@ class Net_URL2 */ public function getPort() { - return $this->_port; + 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 int|bool $port a port number, or false + * @param int|bool $port * * @return void */ public function setPort($port) { - $this->_port = intval($port); + $this->port = intval($port); } /** * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or - * false if there is no authority. + * false if there is no authority none. * * @return string|bool */ public function getAuthority() { - if (!$this->_host) { + if (!$this->host) { return false; } $authority = ''; - if ($this->_userinfo !== false) { - $authority .= $this->_userinfo . '@'; + if ($this->userinfo !== false) { + $authority .= $this->userinfo . '@'; } - $authority .= $this->_host; + $authority .= $this->host; - if ($this->_port !== false) { - $authority .= ':' . $this->_port; + 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". + * @param string|false $authority * * @return void */ public function setAuthority($authority) { - $this->_userinfo = false; - $this->_host = false; - $this->_port = false; - if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) { + $this->user = false; + $this->pass = false; + $this->host = false; + $this->port = false; + if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) { if ($reg[1]) { - $this->_userinfo = $reg[2]; + $this->userinfo = $reg[2]; } - $this->_host = $reg[3]; + $this->host = $reg[3]; if (isset($reg[5])) { - $this->_port = intval($reg[5]); + $this->port = intval($reg[5]); } } } @@ -405,74 +331,65 @@ class Net_URL2 */ public function getPath() { - return $this->_path; + return $this->path; } /** - * Sets the path part (possibly an empty string). - * - * @param string $path a path + * @param string $path * * @return void */ public function setPath($path) { - $this->_path = $path; + $this->path = $path; } /** * Returns the query string (excluding the leading "?"), or false if "?" - * is not present in the URL. + * isn't present in the URL. * * @return string|bool * @see self::getQueryVariables() */ public function getQuery() { - return $this->_query; + 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" + * @param string|bool $query * * @return void * @see self::setQueryVariables() */ public function setQuery($query) { - $this->_query = $query; + $this->query = $query; } /** - * Returns the fragment name, or false if "#" is not present in the URL. + * Returns the fragment name, or false if "#" isn't present in the URL. * * @return string|bool */ public function getFragment() { - return $this->_fragment; + 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 + * @param string|bool $fragment * * @return void */ public function setFragment($fragment) { - $this->_fragment = $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. + * $_GET in a PHP script. * * @return array */ @@ -481,7 +398,7 @@ class Net_URL2 $pattern = '/[' . preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') . ']/'; - $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY); + $parts = preg_split($pattern, $this->query, -1, PREG_SPLIT_NO_EMPTY); $return = array(); foreach ($parts as $part) { @@ -528,8 +445,6 @@ class Net_URL2 } /** - * Sets the query string to the specified variable in the query string. - * * @param array $array (name => value) array * * @return void @@ -537,11 +452,11 @@ class Net_URL2 public function setQueryVariables(array $array) { if (!$array) { - $this->_query = false; + $this->query = false; } else { foreach ($array as $name => $value) { if ($this->getOption(self::OPTION_ENCODE_KEYS)) { - $name = self::urlencode($name); + $name = rawurlencode($name); } if (is_array($value)) { @@ -551,21 +466,19 @@ class Net_URL2 : ($name . '=' . $v); } } elseif (!is_null($value)) { - $parts[] = $name . '=' . self::urlencode($value); + $parts[] = $name . '=' . $value; } else { $parts[] = $name; } } - $this->_query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT), - $parts); + $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 + * @param string $name + * @param mixed $value * * @return array */ @@ -577,9 +490,7 @@ class Net_URL2 } /** - * Removes the specifed variable from the query string. - * - * @param string $name a query string variable, e.g. "foo" in "?foo=1" + * @param string $name * * @return void */ @@ -600,38 +511,27 @@ class Net_URL2 // See RFC 3986, section 5.3 $url = ""; - if ($this->_scheme !== false) { - $url .= $this->_scheme . ':'; + if ($this->scheme !== false) { + $url .= $this->scheme . ':'; } $authority = $this->getAuthority(); if ($authority !== false) { $url .= '//' . $authority; } - $url .= $this->_path; + $url .= $this->path; - if ($this->_query !== false) { - $url .= '?' . $this->_query; + if ($this->query !== false) { + $url .= '?' . $this->query; } - if ($this->_fragment !== false) { - $url .= '#' . $this->_fragment; + 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. @@ -655,38 +555,36 @@ class Net_URL2 // See RFC 3886, section 6 // Schemes are case-insensitive - if ($this->_scheme) { - $this->_scheme = strtolower($this->_scheme); + if ($this->scheme) { + $this->scheme = strtolower($this->scheme); } // Hostnames are case-insensitive - if ($this->_host) { - $this->_host = strtolower($this->_host); + 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')) { + if ($this->port && + $this->scheme && + $this->port == getservbyname($this->scheme, 'tcp')) { - $this->_port = false; + $this->port = false; } // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) - foreach (array('_userinfo', '_host', '_path') as $part) { + foreach (array('userinfo', 'host', 'path') as $part) { if ($this->$part) { - $this->$part = preg_replace('/%[0-9a-f]{2}/ie', - 'strtoupper("\0")', - $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); + $this->path = self::removeDotSegments($this->path); // Scheme based normalization (RFC 3986, section 6.2.3) - if ($this->_host && !$this->_path) { - $this->_path = '/'; + if ($this->host && !$this->path) { + $this->path = '/'; } } @@ -697,7 +595,7 @@ class Net_URL2 */ public function isAbsolute() { - return (bool) $this->_scheme; + return (bool) $this->scheme; } /** @@ -710,7 +608,7 @@ class Net_URL2 */ public function resolve($reference) { - if (!$reference instanceof Net_URL2) { + if (is_string($reference)) { $reference = new self($reference); } if (!$this->isAbsolute()) { @@ -719,54 +617,54 @@ class Net_URL2 // 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; + if (!$this->getOption(self::OPTION_STRICT) && $reference->scheme == $this->scheme) { + $reference->scheme = false; } $target = new self(''); - if ($reference->_scheme !== false) { - $target->_scheme = $reference->_scheme; + if ($reference->scheme !== false) { + $target->scheme = $reference->scheme; $target->setAuthority($reference->getAuthority()); - $target->_path = self::removeDotSegments($reference->_path); - $target->_query = $reference->_query; + $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; + $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; + if ($reference->path == '') { + $target->path = $this->path; + if ($reference->query !== false) { + $target->query = $reference->query; } else { - $target->_query = $this->_query; + $target->query = $this->query; } } else { - if (substr($reference->_path, 0, 1) == '/') { - $target->_path = self::removeDotSegments($reference->_path); + 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; + if ($this->host !== false && $this->path == '') { + $target->path = '/' . $this->path; } else { - $i = strrpos($this->_path, '/'); + $i = strrpos($this->path, '/'); if ($i !== false) { - $target->_path = substr($this->_path, 0, $i + 1); + $target->path = substr($this->path, 0, $i + 1); } - $target->_path .= $reference->_path; + $target->path .= $reference->path; } - $target->_path = self::removeDotSegments($target->_path); + $target->path = self::removeDotSegments($target->path); } - $target->_query = $reference->_query; + $target->query = $reference->query; } $target->setAuthority($this->getAuthority()); } - $target->_scheme = $this->_scheme; + $target->scheme = $this->scheme; } - $target->_fragment = $reference->_fragment; + $target->fragment = $reference->fragment; return $target; } @@ -779,7 +677,7 @@ class Net_URL2 * * @return string a path */ - public static function removeDotSegments($path) + private static function removeDotSegments($path) { $output = ''; @@ -787,25 +685,28 @@ class Net_URL2 // method $j = 0; while ($path && $j++ < 100) { + // Step A if (substr($path, 0, 2) == './') { - // Step 2.A $path = substr($path, 2); } elseif (substr($path, 0, 3) == '../') { - // Step 2.A $path = substr($path, 3); + + // Step B } elseif (substr($path, 0, 3) == '/./' || $path == '/.') { - // Step 2.B $path = '/' . substr($path, 3); + + // Step C } elseif (substr($path, 0, 4) == '/../' || $path == '/..') { - // Step 2.C - $path = '/' . substr($path, 4); - $i = strrpos($output, '/'); + $path = '/' . substr($path, 4); + $i = strrpos($output, '/'); $output = $i === false ? '' : substr($output, 0, $i); + + // Step D } elseif ($path == '.' || $path == '..') { - // Step 2.D $path = ''; + + // Step E } else { - // Step 2.E $i = strpos($path, '/'); if ($i === 0) { $i = strpos($path, '/', 1); @@ -821,22 +722,6 @@ class Net_URL2 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. @@ -852,13 +737,13 @@ class Net_URL2 // Begin with a relative URL $url = new self($_SERVER['PHP_SELF']); - $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; - $url->_host = $_SERVER['SERVER_NAME']; + $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + $url->host = $_SERVER['SERVER_NAME']; $port = intval($_SERVER['SERVER_PORT']); - if ($url->_scheme == 'http' && $port != 80 || - $url->_scheme == 'https' && $port != 443) { + if ($url->scheme == 'http' && $port != 80 || + $url->scheme == 'https' && $port != 443) { - $url->_port = $port; + $url->port = $port; } return $url; } @@ -888,7 +773,7 @@ class Net_URL2 // Begin with a relative URL $url = new self($_SERVER['REQUEST_URI']); - $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; // Set host and possibly port $url->setAuthority($_SERVER['HTTP_HOST']); return $url; @@ -907,10 +792,10 @@ class Net_URL2 */ function setOption($optionName, $value) { - if (!array_key_exists($optionName, $this->_options)) { + if (!array_key_exists($optionName, $this->options)) { return false; } - $this->_options[$optionName] = $value; + $this->options[$optionName] = $value; } /** @@ -922,7 +807,7 @@ class Net_URL2 */ function getOption($optionName) { - return isset($this->_options[$optionName]) - ? $this->_options[$optionName] : false; + return isset($this->options[$optionName]) + ? $this->options[$optionName] : false; } } diff --git a/install.php b/install.php index d34e92dab..6bfc4c2e2 100644 --- a/install.php +++ b/install.php @@ -93,13 +93,6 @@ $external_libraries=array( 'include'=>'HTTP/Request.php', 'check_class'=>'HTTP_Request' ), - array( - 'name'=>'HTTP_Request2', - 'pear'=>'HTTP_Request2', - 'url'=>'http://pear.php.net/package/HTTP_Request2', - 'include'=>'HTTP/Request2.php', - 'check_class'=>'HTTP_Request2' - ), array( 'name'=>'Mail', 'pear'=>'Mail', diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php index ef0d8dda4..18ae7719b 100644 --- a/lib/Shorturl_api.php +++ b/lib/Shorturl_api.php @@ -41,17 +41,22 @@ abstract class ShortUrlApi return strlen($url) >= common_config('site', 'shorturllength'); } - protected function http_post($data) - { - $request = new HTTPClient($this->service_url); - return $request->post($data); + protected function http_post($data) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $this->service_url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + $response = curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + if (($code < 200) || ($code >= 400)) return false; + return $response; } - protected function http_get($url) - { - $service = $this->service_url . urlencode($url); - $request = new HTTPClient($service); - return $request->get(); + protected function http_get($url) { + $encoded_url = urlencode($url); + return file_get_contents("{$this->service_url}$encoded_url"); } protected function tidy($response) { diff --git a/lib/curlclient.php b/lib/curlclient.php new file mode 100644 index 000000000..c307c2984 --- /dev/null +++ b/lib/curlclient.php @@ -0,0 +1,179 @@ +. + * + * @category HTTP + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +define(CURLCLIENT_VERSION, "0.1"); + +/** + * Wrapper for Curl + * + * Makes Curl HTTP client calls within our HTTPClient framework + * + * @category HTTP + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class CurlClient extends HTTPClient +{ + function __construct() + { + } + + function head($url, $headers=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + curl_setopt_array($ch, + array(CURLOPT_NOBODY => true)); + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function get($url, $headers=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function post($url, $headers=null, $body=null) + { + $ch = curl_init($url); + + $this->setup($ch); + + curl_setopt($ch, CURLOPT_POST, true); + + if (!is_null($body)) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $body); + } + + if (!is_null($headers)) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + } + + $result = curl_exec($ch); + + curl_close($ch); + + return $this->parseResults($result); + } + + function setup($ch) + { + curl_setopt_array($ch, + array(CURLOPT_USERAGENT => $this->userAgent(), + CURLOPT_HEADER => true, + CURLOPT_RETURNTRANSFER => true)); + } + + function userAgent() + { + $version = curl_version(); + return parent::userAgent() . " CurlClient/".CURLCLIENT_VERSION . " cURL/" . $version['version']; + } + + function parseResults($results) + { + $resp = new HTTPResponse(); + + $lines = explode("\r\n", $results); + + if (preg_match("#^HTTP/1.[01] (\d\d\d) .+$#", $lines[0], $match)) { + $resp->code = $match[1]; + } else { + throw Exception("Bad format: initial line is not HTTP status line"); + } + + $lastk = null; + + for ($i = 1; $i < count($lines); $i++) { + $l =& $lines[$i]; + if (mb_strlen($l) == 0) { + $resp->body = implode("\r\n", array_slice($lines, $i + 1)); + break; + } + if (preg_match("#^(\S+):\s+(.*)$#", $l, $match)) { + $k = $match[1]; + $v = $match[2]; + + if (array_key_exists($k, $resp->headers)) { + if (is_array($resp->headers[$k])) { + $resp->headers[$k][] = $v; + } else { + $resp->headers[$k] = array($resp->headers[$k], $v); + } + } else { + $resp->headers[$k] = $v; + } + $lastk = $k; + } else if (preg_match("#^\s+(.*)$#", $l, $match)) { + // continuation line + if (is_null($lastk)) { + throw Exception("Bad format: initial whitespace in headers"); + } + $h =& $resp->headers[$lastk]; + if (is_array($h)) { + $n = count($h); + $h[$n-1] .= $match[1]; + } else { + $h .= $match[1]; + } + } + } + + return $resp; + } +} diff --git a/lib/default.php b/lib/default.php index f6cc4b725..7ec8558b0 100644 --- a/lib/default.php +++ b/lib/default.php @@ -228,6 +228,8 @@ $default = array('contentlimit' => null), 'message' => array('contentlimit' => null), + 'http' => + array('client' => 'curl'), // XXX: should this be the default? 'location' => array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth ); diff --git a/lib/httpclient.php b/lib/httpclient.php index ee894e983..f16e31e10 100644 --- a/lib/httpclient.php +++ b/lib/httpclient.php @@ -31,9 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once 'HTTP/Request2.php'; -require_once 'HTTP/Request2/Response.php'; - /** * Useful structure for HTTP responses * @@ -41,42 +38,18 @@ require_once 'HTTP/Request2/Response.php'; * ways of doing them. This class hides the specifics of what underlying * library (curl or PHP-HTTP or whatever) that's used. * - * This extends the HTTP_Request2_Response class with methods to get info - * about any followed redirects. - * * @category HTTP - * @package StatusNet - * @author Evan Prodromou - * @author Brion Vibber - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ + * @package StatusNet + * @author Evan Prodromou + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ */ -class HTTPResponse extends HTTP_Request2_Response -{ - function __construct(HTTP_Request2_Response $response, $url, $redirects=0) - { - foreach (get_object_vars($response) as $key => $val) { - $this->$key = $val; - } - $this->url = strval($url); - $this->redirectCount = intval($redirects); - } - - /** - * Get the count of redirects that have been followed, if any. - * @return int - */ - function getRedirectCount() { - return $this->redirectCount; - } - /** - * Gets the final target URL, after any redirects have been followed. - * @return string URL - */ - function getUrl() { - return $this->url; - } +class HTTPResponse +{ + public $code = null; + public $headers = array(); + public $body = null; } /** @@ -86,133 +59,64 @@ class HTTPResponse extends HTTP_Request2_Response * ways of doing them. This class hides the specifics of what underlying * library (curl or PHP-HTTP or whatever) that's used. * - * This extends the PEAR HTTP_Request2 package: - * - sends StatusNet-specific User-Agent header - * - 'follow_redirects' config option, defaulting off - * - 'max_redirs' config option, defaulting to 10 - * - extended response class adds getRedirectCount() and getUrl() methods - * - get() and post() convenience functions return body content directly - * * @category HTTP * @package StatusNet * @author Evan Prodromou - * @author Brion Vibber * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class HTTPClient extends HTTP_Request2 +class HTTPClient { + static $_client = null; - function __construct($url=null, $method=self::METHOD_GET, $config=array()) + static function start() { - $this->config['max_redirs'] = 10; - $this->config['follow_redirects'] = false; - parent::__construct($url, $method, $config); - $this->setHeader('User-Agent', $this->userAgent()); + if (!is_null(self::$_client)) { + return self::$_client; + } + + $type = common_config('http', 'client'); + + switch ($type) { + case 'curl': + self::$_client = new CurlClient(); + break; + default: + throw new Exception("Unknown HTTP client type '$type'"); + break; + } + + return self::$_client; } - /** - * Convenience function to run a get request and return the response body. - * Use when you don't need to get into details of the response. - * - * @return mixed string on success, false on failure - */ - function get() + function head($url, $headers) { - $this->setMethod(self::METHOD_GET); - return $this->doRequest(); + throw new Exception("HEAD method unimplemented"); } - /** - * Convenience function to post form data and return the response body. - * Use when you don't need to get into details of the response. - * - * @param array associative array of form data to submit - * @return mixed string on success, false on failure - */ - public function post($data=array()) + function get($url, $headers) { - $this->setMethod(self::METHOD_POST); - if ($data) { - $this->addPostParameter($data); - } - return $this->doRequest(); + throw new Exception("GET method unimplemented"); } - /** - * @return mixed string on success, false on failure - */ - protected function doRequest() + function post($url, $headers, $body) { - try { - $response = $this->send(); - $code = $response->getStatus(); - if (($code < 200) || ($code >= 400)) { - return false; - } - return $response->getBody(); - } catch (HTTP_Request2_Exception $e) { - $this->log(LOG_ERR, $e->getMessage()); - return false; - } + throw new Exception("POST method unimplemented"); } - - protected function log($level, $detail) { - $method = $this->getMethod(); - $url = $this->getUrl(); - common_log($level, __CLASS__ . ": HTTP $method $url - $detail"); + + function put($url, $headers, $body) + { + throw new Exception("PUT method unimplemented"); } - /** - * Pulls up StatusNet's customized user-agent string, so services - * we hit can track down the responsible software. - */ - function userAgent() + function delete($url, $headers) { - return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")"; + throw new Exception("DELETE method unimplemented"); } - function send() + function userAgent() { - $maxRedirs = intval($this->config['max_redirs']); - if (empty($this->config['follow_redirects'])) { - $maxRedirs = 0; - } - $redirs = 0; - do { - try { - $response = parent::send(); - } catch (HTTP_Request2_Exception $e) { - $this->log(LOG_ERR, $e->getMessage()); - throw $e; - } - $code = $response->getStatus(); - if ($code >= 200 && $code < 300) { - $reason = $response->getReasonPhrase(); - $this->log(LOG_INFO, "$code $reason"); - } elseif ($code >= 300 && $code < 400) { - $url = $this->getUrl(); - $target = $response->getHeader('Location'); - - if (++$redirs >= $maxRedirs) { - common_log(LOG_ERR, __CLASS__ . ": Too many redirects: skipping $code redirect from $url to $target"); - break; - } - try { - $this->setUrl($target); - $this->setHeader('Referer', $url); - common_log(LOG_INFO, __CLASS__ . ": Following $code redirect from $url to $target"); - continue; - } catch (HTTP_Request2_Exception $e) { - common_log(LOG_ERR, __CLASS__ . ": Invalid $code redirect from $url to $target"); - } - } else { - $reason = $response->getReasonPhrase(); - $this->log(LOG_ERR, "$code $reason"); - } - break; - } while ($maxRedirs); - return new HTTPResponse($response, $this->getUrl(), $redirs); + return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")"; } } diff --git a/lib/oauthclient.php b/lib/oauthclient.php index 1a86e2460..f1827726e 100644 --- a/lib/oauthclient.php +++ b/lib/oauthclient.php @@ -43,7 +43,7 @@ require_once 'OAuth.php'; * @link http://status.net/ * */ -class OAuthClientException extends Exception +class OAuthClientCurlException extends Exception { } @@ -97,14 +97,9 @@ class OAuthClient function getRequestToken($url) { $response = $this->oAuthGet($url); - $arr = array(); - parse_str($response, $arr); - if (isset($arr['oauth_token']) && isset($arr['oauth_token_secret'])) { - $token = new OAuthToken($arr['oauth_token'], @$arr['oauth_token_secret']); - return $token; - } else { - throw new OAuthClientException(); - } + parse_str($response); + $token = new OAuthToken($oauth_token, $oauth_token_secret); + return $token; } /** @@ -182,7 +177,7 @@ class OAuthClient } /** - * Make a HTTP request. + * Make a HTTP request using cURL. * * @param string $url Where to make the * @param array $params post parameters @@ -191,32 +186,40 @@ class OAuthClient */ function httpRequest($url, $params = null) { - $request = new HTTPClient($url); - $request->setConfig(array( - 'connect_timeout' => 120, - 'timeout' => 120, - 'follow_redirects' => true, - 'ssl_verify_peer' => false, - )); - - // Twitter is strict about accepting invalid "Expect" headers - $request->setHeader('Expect', ''); + $options = array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FAILONERROR => true, + CURLOPT_HEADER => false, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_USERAGENT => 'StatusNet', + CURLOPT_CONNECTTIMEOUT => 120, + CURLOPT_TIMEOUT => 120, + CURLOPT_HTTPAUTH => CURLAUTH_ANY, + CURLOPT_SSL_VERIFYPEER => false, + + // Twitter is strict about accepting invalid "Expect" headers + + CURLOPT_HTTPHEADER => array('Expect:') + ); if (isset($params)) { - $request->setMethod(HTTP_Request2::METHOD_POST); - $request->setBody($params); + $options[CURLOPT_POST] = true; + $options[CURLOPT_POSTFIELDS] = $params; } - try { - $response = $request->send(); - $code = $response->getStatus(); - if ($code < 200 || $code >= 400) { - throw new OAuthClientException($response->getBody(), $code); - } - return $response->getBody(); - } catch (Exception $e) { - throw new OAuthClientException($e->getMessage(), $e->getCode()); + $ch = curl_init($url); + curl_setopt_array($ch, $options); + $response = curl_exec($ch); + + if ($response === false) { + $msg = curl_error($ch); + $code = curl_errno($ch); + throw new OAuthClientCurlException($msg, $code); } + + curl_close($ch); + + return $response; } } diff --git a/lib/ping.php b/lib/ping.php index 2797c1b2d..175bf8440 100644 --- a/lib/ping.php +++ b/lib/ping.php @@ -44,18 +44,20 @@ function ping_broadcast_notice($notice) { array('nickname' => $profile->nickname)), $tags)); - $request = new HTTPClient($notify_url, HTTP_Request2::METHOD_POST); - $request->setHeader('Content-Type', 'text/xml'); - $request->setBody($req); - $httpResponse = $request->send(); + $context = stream_context_create(array('http' => array('method' => "POST", + 'header' => + "Content-Type: text/xml\r\n". + "User-Agent: StatusNet/".STATUSNET_VERSION."\r\n", + 'content' => $req))); + $file = file_get_contents($notify_url, false, $context); - if (!$httpResponse || mb_strlen($httpResponse->getBody()) == 0) { + if ($file === false || mb_strlen($file) == 0) { common_log(LOG_WARNING, "XML-RPC empty results for ping ($notify_url, $notice->id) "); continue; } - $response = xmlrpc_decode($httpResponse->getBody()); + $response = xmlrpc_decode($file); if (is_array($response) && xmlrpc_is_fault($response)) { common_log(LOG_WARNING, diff --git a/lib/snapshot.php b/lib/snapshot.php index 6829e8a75..ede846e5b 100644 --- a/lib/snapshot.php +++ b/lib/snapshot.php @@ -172,11 +172,26 @@ class Snapshot { // XXX: Use OICU2 and OAuth to make authorized requests + $postdata = http_build_query($this->stats); + + $opts = + array('http' => + array( + 'method' => 'POST', + 'header' => 'Content-type: '. + 'application/x-www-form-urlencoded', + 'content' => $postdata, + 'user_agent' => 'StatusNet/'.STATUSNET_VERSION + ) + ); + + $context = stream_context_create($opts); + $reporturl = common_config('snapshot', 'reporturl'); - $request = new HTTPClient($reporturl, HTTP_Request2::METHOD_POST); - $request->addPostParameter($this->stats); - $request->send(); + $result = @file_get_contents($reporturl, false, $context); + + return $result; } /** diff --git a/plugins/BlogspamNetPlugin.php b/plugins/BlogspamNetPlugin.php index 3bdc73556..c14569746 100644 --- a/plugins/BlogspamNetPlugin.php +++ b/plugins/BlogspamNetPlugin.php @@ -22,7 +22,6 @@ * @category Plugin * @package StatusNet * @author Evan Prodromou - * @author Brion Vibber * @copyright 2009 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -70,14 +69,14 @@ class BlogspamNetPlugin extends Plugin { $args = $this->testArgs($notice); common_debug("Blogspamnet args = " . print_r($args, TRUE)); - $requestBody = xmlrpc_encode_request('testComment', array($args)); - - $request = new HTTPClient($this->baseUrl, HTTP_Request2::METHOD_POST); - $request->addHeader('Content-Type: text/xml'); - $request->setBody($requestBody); - $httpResponse = $request->send(); - - $response = xmlrpc_decode($httpResponse->getBody()); + $request = xmlrpc_encode_request('testComment', array($args)); + $context = stream_context_create(array('http' => array('method' => "POST", + 'header' => + "Content-Type: text/xml\r\n". + "User-Agent: " . $this->userAgent(), + 'content' => $request))); + $file = file_get_contents($this->baseUrl, false, $context); + $response = xmlrpc_decode($file); if (xmlrpc_is_fault($response)) { throw new ServerException("$response[faultString] ($response[faultCode])", 500); } else { diff --git a/plugins/LinkbackPlugin.php b/plugins/LinkbackPlugin.php index 0513687e9..60f7a60c7 100644 --- a/plugins/LinkbackPlugin.php +++ b/plugins/LinkbackPlugin.php @@ -129,25 +129,27 @@ class LinkbackPlugin extends Plugin } } - $request = new HTTPClient($endpoint, 'POST'); - $request->setHeader('User-Agent', $this->userAgent()); - $request->setHeader('Content-Type', 'text/xml'); - $request->setBody(xmlrpc_encode_request('pingback.ping', $args)); - try { - $response = $request->send(); - } catch (HTTP_Request2_Exception $e) { + $request = xmlrpc_encode_request('pingback.ping', $args); + $context = stream_context_create(array('http' => array('method' => "POST", + 'header' => + "Content-Type: text/xml\r\n". + "User-Agent: " . $this->userAgent(), + 'content' => $request))); + $file = file_get_contents($endpoint, false, $context); + if (!$file) { common_log(LOG_WARNING, - "Pingback request failed for '$url' ($endpoint)"); - } - $response = xmlrpc_decode($response->getBody()); - if (xmlrpc_is_fault($response)) { - common_log(LOG_WARNING, - "Pingback error for '$url' ($endpoint): ". - "$response[faultString] ($response[faultCode])"); + "Pingback request failed for '$url' ($endpoint)"); } else { - common_log(LOG_INFO, - "Pingback success for '$url' ($endpoint): ". - "'$response'"); + $response = xmlrpc_decode($file); + if (xmlrpc_is_fault($response)) { + common_log(LOG_WARNING, + "Pingback error for '$url' ($endpoint): ". + "$response[faultString] ($response[faultCode])"); + } else { + common_log(LOG_INFO, + "Pingback success for '$url' ($endpoint): ". + "'$response'"); + } } } diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php index d59d63e47..82d772048 100644 --- a/plugins/SimpleUrl/SimpleUrlPlugin.php +++ b/plugins/SimpleUrl/SimpleUrlPlugin.php @@ -65,6 +65,15 @@ class SimpleUrlPlugin extends Plugin class SimpleUrl extends ShortUrlApi { protected function shorten_imp($url) { - return $this->http_get($url); + $curlh = curl_init(); + curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait + curl_setopt($curlh, CURLOPT_USERAGENT, 'StatusNet'); + curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true); + + curl_setopt($curlh, CURLOPT_URL, $this->service_url.urlencode($url)); + $short_url = curl_exec($curlh); + + curl_close($curlh); + return $short_url; } } diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php index 671e3c7af..ed2bf48a2 100755 --- a/plugins/TwitterBridge/daemons/synctwitterfriends.php +++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php @@ -152,8 +152,8 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon $friends_ids = $client->friendsIds(); } catch (Exception $e) { common_log(LOG_WARNING, $this->name() . - ' - error getting friend ids: ' . - $e->getMessage()); + ' - cURL error getting friend ids ' . + $e->getCode() . ' - ' . $e->getMessage()); return $friends; } diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index 6c91b2860..81bbbc7c5 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -109,16 +109,12 @@ class TwitterStatusFetcher extends ParallelizingDaemon $flink->find(); $flinks = array(); - common_log(LOG_INFO, "hello"); while ($flink->fetch()) { if (($flink->noticesync & FOREIGN_NOTICE_RECV) == FOREIGN_NOTICE_RECV) { $flinks[] = clone($flink); - common_log(LOG_INFO, "sync: foreign id $flink->foreign_id"); - } else { - common_log(LOG_INFO, "nothing to sync"); } } @@ -519,34 +515,31 @@ class TwitterStatusFetcher extends ParallelizingDaemon return $id; } - /** - * Fetch a remote avatar image and save to local storage. - * - * @param string $url avatar source URL - * @param string $filename bare local filename for download - * @return bool true on success, false on failure - */ function fetchAvatar($url, $filename) { - common_debug($this->name() . " - Fetching Twitter avatar: $url"); + $avatarfile = Avatar::path($filename); - $request = new HTTPClient($url, 'GET', array( - 'follow_redirects' => true, - )); - $data = $request->get(); - if ($data) { - $avatarfile = Avatar::path($filename); - $ok = file_put_contents($avatarfile, $data); - if (!$ok) { - common_log(LOG_WARNING, $this->name() . - " - Couldn't open file $filename"); - return false; - } - } else { + $out = fopen($avatarfile, 'wb'); + if (!$out) { + common_log(LOG_WARNING, $this->name() . + " - Couldn't open file $filename"); return false; } - return true; + common_debug($this->name() . " - Fetching Twitter avatar: $url"); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_FILE, $out); + curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); + $result = curl_exec($ch); + curl_close($ch); + + fclose($out); + + return $result; } } diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index 3c6803e49..1a5248a9b 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -215,7 +215,7 @@ function broadcast_basicauth($notice, $flink) try { $status = $client->statusesUpdate($statustxt); - } catch (HTTP_Request2_Exception $e) { + } catch (BasicAuthCurlException $e) { return process_error($e, $flink); } diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index f1daefab1..2a93ff13e 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -125,7 +125,7 @@ class TwitterauthorizationAction extends Action $auth_link = $client->getAuthorizeLink($req_tok); - } catch (OAuthClientException $e) { + } catch (TwitterOAuthClientException $e) { $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s', $e->getCode(), $e->getMessage()); $this->serverError(_('Couldn\'t link your Twitter account.')); diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php index e4cae7373..1040d72fb 100644 --- a/plugins/TwitterBridge/twitterbasicauthclient.php +++ b/plugins/TwitterBridge/twitterbasicauthclient.php @@ -31,6 +31,26 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +/** + * Exception wrapper for cURL errors + * + * @category Integration + * @package StatusNet + * @author Adrian Lang + * @author Brenda Wallace + * @author Craig Andrews + * @author Dan Moore + * @author Evan Prodromou + * @author mEDI + * @author Sarven Capadisli + * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + */ +class BasicAuthCurlException extends Exception +{ +} + /** * Class for talking to the Twitter API with HTTP Basic Auth. * @@ -178,27 +198,45 @@ class TwitterBasicAuthClient */ function httpRequest($url, $params = null, $auth = true) { - $request = new HTTPClient($url, 'GET', array( - 'follow_redirects' => true, - 'connect_timeout' => 120, - 'timeout' => 120, - 'ssl_verifypeer' => false, - )); - - // Twitter is strict about accepting invalid "Expect" headers - $request->setHeader('Expect', ''); + $options = array( + CURLOPT_RETURNTRANSFER => true, + CURLOPT_FAILONERROR => true, + CURLOPT_HEADER => false, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_USERAGENT => 'StatusNet', + CURLOPT_CONNECTTIMEOUT => 120, + CURLOPT_TIMEOUT => 120, + CURLOPT_HTTPAUTH => CURLAUTH_ANY, + CURLOPT_SSL_VERIFYPEER => false, + + // Twitter is strict about accepting invalid "Expect" headers + + CURLOPT_HTTPHEADER => array('Expect:') + ); if (isset($params)) { - $request->setMethod('POST'); - $request->addPostParameter($params); + $options[CURLOPT_POST] = true; + $options[CURLOPT_POSTFIELDS] = $params; } if ($auth) { - $request->setAuth($this->screen_name, $this->password); + $options[CURLOPT_USERPWD] = $this->screen_name . + ':' . $this->password; } - $response = $request->send(); - return $response->getBody(); + $ch = curl_init($url); + curl_setopt_array($ch, $options); + $response = curl_exec($ch); + + if ($response === false) { + $msg = curl_error($ch); + $code = curl_errno($ch); + throw new BasicAuthCurlException($msg, $code); + } + + curl_close($ch); + + return $response; } } diff --git a/plugins/WikiHashtagsPlugin.php b/plugins/WikiHashtagsPlugin.php index a9e675f5c..0c5649aa4 100644 --- a/plugins/WikiHashtagsPlugin.php +++ b/plugins/WikiHashtagsPlugin.php @@ -68,8 +68,10 @@ class WikiHashtagsPlugin extends Plugin $editurl = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=edit', urlencode($tag)); - $request = new HTTPClient($url); - $html = $request->get(); + $context = stream_context_create(array('http' => array('method' => "GET", + 'header' => + "User-Agent: " . $this->userAgent()))); + $html = @file_get_contents($url, false, $context); $action->elementStart('div', array('id' => 'wikihashtags', 'class' => 'section')); @@ -98,4 +100,10 @@ class WikiHashtagsPlugin extends Plugin return true; } + + function userAgent() + { + return 'WikiHashtagsPlugin/'.WIKIHASHTAGSPLUGIN_VERSION . + ' StatusNet/' . STATUSNET_VERSION; + } } diff --git a/scripts/enjitqueuehandler.php b/scripts/enjitqueuehandler.php index 214cc02b4..08f733b07 100755 --- a/scripts/enjitqueuehandler.php +++ b/scripts/enjitqueuehandler.php @@ -46,8 +46,8 @@ class EnjitQueueHandler extends QueueHandler function start() { - $this->log(LOG_INFO, "Starting EnjitQueueHandler"); - $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); + $this->log(LOG_INFO, "Starting EnjitQueueHandler"); + $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); return true; } @@ -56,16 +56,16 @@ class EnjitQueueHandler extends QueueHandler $profile = Profile::staticGet($notice->profile_id); - $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname); + $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname); - if ( ! $notice->is_local ) { - $this->log(LOG_INFO, "Skipping remote notice"); - return "skipped"; - } + if ( ! $notice->is_local ) { + $this->log(LOG_INFO, "Skipping remote notice"); + return "skipped"; + } - # - # Build an Atom message from the notice - # + # + # Build an Atom message from the notice + # $noticeurl = common_local_url('shownotice', array('notice' => $notice->id)); $msg = $profile->nickname . ': ' . $notice->content; @@ -86,20 +86,36 @@ class EnjitQueueHandler extends QueueHandler $atom .= "".common_date_w3dtf($notice->modified)."\n"; $atom .= "\n"; - $url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey'); - $data = array( - 'msg' => $atom, - ); - - # - # POST the message to $config['enjit']['apiurl'] - # - $request = new HTTPClient($url, HTTP_Request2::METHOD_POST); - $request->addPostFields($data); - $response = $request->send(); - - // @fixme handle_notice() is supposed to return true/false. Somethin' funky? - return $response->getStatus(); + $url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey'); + $data = "msg=$atom"; + + # + # POST the message to $config['enjit']['apiurl'] + # + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + + curl_setopt($ch, CURLOPT_HEADER, 1); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_POST, 1) ; + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + + # SSL and Debugging options + # + # curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); + # curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + # curl_setopt($ch, CURLOPT_VERBOSE, 1); + + $result = curl_exec($ch); + + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE ); + + $this->log(LOG_INFO, "Response Code: $code"); + + curl_close($ch); + + return $code; } } -- cgit v1.2.3-54-g00ecf From 5581143bee602dbd5417f532f2b483e58d0a4269 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 28 Oct 2009 15:29:20 -0400 Subject: Rebuilt HTTPClient class as an extension of PEAR HTTP_Request2 package, adding redirect handling and convenience functions. Caching support will be added in future work after unit tests have been added. * extlib: add PEAR HTTP_Request2 0.4.1 alpha * extlib: update PEAR Net_URL2 to 0.3.0 beta for HTTP_Request2 compatibility * moved direct usage of CURL and file_get_contents to HTTPClient class, excluding external-sourced libraries * adapted GeonamesPlugin for new HTTPResponse interface Note some plugins haven't been fully tested yet. --- classes/File_redirection.php | 68 +- extlib/HTTP/Request2.php | 844 ++++++++++++++++++ extlib/HTTP/Request2/Adapter.php | 152 ++++ extlib/HTTP/Request2/Adapter/Curl.php | 383 ++++++++ extlib/HTTP/Request2/Adapter/Mock.php | 171 ++++ extlib/HTTP/Request2/Adapter/Socket.php | 971 +++++++++++++++++++++ extlib/HTTP/Request2/Exception.php | 62 ++ extlib/HTTP/Request2/MultipartBody.php | 274 ++++++ extlib/HTTP/Request2/Observer/Log.php | 215 +++++ extlib/HTTP/Request2/Response.php | 549 ++++++++++++ extlib/Net/URL2.php | 471 ++++++---- install.php | 7 + lib/Shorturl_api.php | 24 +- lib/curlclient.php | 179 ---- lib/default.php | 2 - lib/httpclient.php | 213 ++++- lib/oauthclient.php | 65 +- lib/ping.php | 12 +- lib/snapshot.php | 21 +- plugins/BlogspamNetPlugin.php | 15 +- plugins/GeonamesPlugin.php | 16 +- plugins/LilUrl/LilUrlPlugin.php | 5 +- plugins/LinkbackPlugin.php | 21 +- plugins/SimpleUrl/SimpleUrlPlugin.php | 11 +- .../TwitterBridge/daemons/synctwitterfriends.php | 4 +- .../TwitterBridge/daemons/twitterstatusfetcher.php | 43 +- plugins/TwitterBridge/twitter.php | 2 +- plugins/TwitterBridge/twitterauthorization.php | 2 +- plugins/TwitterBridge/twitterbasicauthclient.php | 68 +- plugins/WikiHashtagsPlugin.php | 15 +- scripts/enjitqueuehandler.php | 58 +- 31 files changed, 4275 insertions(+), 668 deletions(-) create mode 100644 extlib/HTTP/Request2.php create mode 100644 extlib/HTTP/Request2/Adapter.php create mode 100644 extlib/HTTP/Request2/Adapter/Curl.php create mode 100644 extlib/HTTP/Request2/Adapter/Mock.php create mode 100644 extlib/HTTP/Request2/Adapter/Socket.php create mode 100644 extlib/HTTP/Request2/Exception.php create mode 100644 extlib/HTTP/Request2/MultipartBody.php create mode 100644 extlib/HTTP/Request2/Observer/Log.php create mode 100644 extlib/HTTP/Request2/Response.php delete mode 100644 lib/curlclient.php (limited to 'lib/ping.php') diff --git a/classes/File_redirection.php b/classes/File_redirection.php index 79052bf7d..08a6e8d8b 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -47,18 +47,15 @@ class File_redirection extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function _commonCurl($url, $redirs) { - $curlh = curl_init(); - curl_setopt($curlh, CURLOPT_URL, $url); - curl_setopt($curlh, CURLOPT_AUTOREFERER, true); // # setup referer header when folowing redirects - curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 10); // # seconds to wait - curl_setopt($curlh, CURLOPT_MAXREDIRS, $redirs); // # max number of http redirections to follow - curl_setopt($curlh, CURLOPT_USERAGENT, USER_AGENT); - curl_setopt($curlh, CURLOPT_FOLLOWLOCATION, true); // Follow redirects - curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true); - curl_setopt($curlh, CURLOPT_FILETIME, true); - curl_setopt($curlh, CURLOPT_HEADER, true); // Include header in output - return $curlh; + static function _commonHttp($url, $redirs) { + $request = new HTTPClient($url); + $request->setConfig(array( + 'connect_timeout' => 10, // # seconds to wait + 'max_redirs' => $redirs, // # max number of http redirections to follow + 'follow_redirects' => true, // Follow redirects + 'store_body' => false, // We won't need body content here. + )); + return $request; } function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) { @@ -82,32 +79,39 @@ class File_redirection extends Memcached_DataObject if(strpos($short_url,'://') === false){ return $short_url; } - $curlh = File_redirection::_commonCurl($short_url, $redirs); - // Don't include body in output - curl_setopt($curlh, CURLOPT_NOBODY, true); - curl_exec($curlh); - $info = curl_getinfo($curlh); - curl_close($curlh); - - if (405 == $info['http_code']) { - $curlh = File_redirection::_commonCurl($short_url, $redirs); - curl_exec($curlh); - $info = curl_getinfo($curlh); - curl_close($curlh); + try { + $request = self::_commonHttp($short_url, $redirs); + // Don't include body in output + $request->setMethod(HTTP_Request2::METHOD_HEAD); + $response = $request->send(); + + if (405 == $response->getStatus()) { + // Server doesn't support HEAD method? Can this really happen? + // We'll try again as a GET and ignore the response data. + $request = self::_commonHttp($short_url, $redirs); + $response = $request->send(); + } + } catch (Exception $e) { + // Invalid URL or failure to reach server + return $short_url; } - if (!empty($info['redirect_count']) && File::isProtected($info['url'])) { - return File_redirection::_redirectWhere_imp($short_url, $info['redirect_count'] - 1, true); + if ($response->getRedirectCount() && File::isProtected($response->getUrl())) { + // Bump back up the redirect chain until we find a non-protected URL + return self::_redirectWhere_imp($short_url, $response->getRedirectCount() - 1, true); } - $ret = array('code' => $info['http_code'] - , 'redirects' => $info['redirect_count'] - , 'url' => $info['url']); + $ret = array('code' => $response->getStatus() + , 'redirects' => $response->getRedirectCount() + , 'url' => $response->getUrl()); - if (!empty($info['content_type'])) $ret['type'] = $info['content_type']; + $type = $response->getHeader('Content-Type'); + if ($type) $ret['type'] = $type; if ($protected) $ret['protected'] = true; - if (!empty($info['download_content_length'])) $ret['size'] = $info['download_content_length']; - if (isset($info['filetime']) && ($info['filetime'] > 0)) $ret['time'] = $info['filetime']; + $size = $response->getHeader('Content-Length'); // @fixme bytes? + if ($size) $ret['size'] = $size; + $time = $response->getHeader('Last-Modified'); + if ($time) $ret['time'] = strtotime($time); return $ret; } diff --git a/extlib/HTTP/Request2.php b/extlib/HTTP/Request2.php new file mode 100644 index 000000000..e06bb86bc --- /dev/null +++ b/extlib/HTTP/Request2.php @@ -0,0 +1,844 @@ + + * 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 + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Request2.php 278226 2009-04-03 21:32:48Z 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 + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov + * @version Release: 0.4.1 + * @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://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 + ); + + /** + * 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; + + + /** + * 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()) + { + if (!empty($url)) { + $this->setUrl($url); + } + if (!empty($method)) { + $this->setMethod($method); + } + $this->setConfig($config); + $this->setHeader('user-agent', 'HTTP_Request2/0.4.1 ' . + '(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_Exception + */ + public function setUrl($url) + { + if (is_string($url)) { + $url = new Net_URL2($url); + } + if (!$url instanceof Net_URL2) { + throw new HTTP_Request2_Exception('Parameter is not a valid HTTP URL'); + } + // 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_Exception 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_Exception("Invalid request method '{$method}'"); + } + $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: + *
    + *
  • 'adapter' - adapter to use (string)
  • + *
  • 'connect_timeout' - Connection timeout in seconds (integer)
  • + *
  • 'timeout' - Total number of seconds a request can take. + * Use 0 for no limit, should be greater than + * 'connect_timeout' if set (integer)
  • + *
  • 'use_brackets' - Whether to append [] to array variable names (bool)
  • + *
  • 'protocol_version' - HTTP Version to use, '1.0' or '1.1' (string)
  • + *
  • 'buffer_size' - Buffer size to use for reading and writing (int)
  • + *
  • '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)
  • + *
  • 'proxy_host' - Proxy server host (string)
  • + *
  • 'proxy_port' - Proxy server port (integer)
  • + *
  • 'proxy_user' - Proxy auth username (string)
  • + *
  • 'proxy_password' - Proxy auth password (string)
  • + *
  • 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)
  • + *
  • 'ssl_verify_peer' - Whether to verify peer's SSL certificate (bool)
  • + *
  • 'ssl_verify_host' - Whether to check that Common Name in SSL + * certificate matches host name (bool)
  • + *
  • 'ssl_cafile' - Cerificate Authority file to verify the peer + * with (use with 'ssl_verify_peer') (string)
  • + *
  • 'ssl_capath' - Directory holding multiple Certificate + * Authority files (string)
  • + *
  • 'ssl_local_cert' - Name of a file containing local cerificate (string)
  • + *
  • 'ssl_passphrase' - Passphrase with which local certificate + * was encoded (string)
  • + *
  • 'digest_compat_ie' - Whether to imitate behaviour of MSIE 5 and 6 + * in using URL without query string in digest + * authentication (boolean)
  • + *
+ * + * @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_Exception 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_Exception( + "Unknown configuration parameter '{$nameOrConfig}'" + ); + } + $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_Exception 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_Exception( + "Unknown configuration parameter '{$name}'" + ); + } + 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. + * + * + * $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 + * + * + * @param string|array header name, header string ('Header: value') + * or an array of headers + * @param string|null header value, header will be removed if null + * @return HTTP_Request2 + * @throws HTTP_Request2_Exception + */ + public function setHeader($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $k => $v) { + if (is_string($k)) { + $this->setHeader($k, $v); + } else { + $this->setHeader($v); + } + } + } 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_Exception("Invalid header name '{$name}'"); + } + // Header names are case insensitive anyway + $name = strtolower($name); + if (null === $value) { + unset($this->headers[$name]); + } 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; + } + + /** + * Appends a cookie to "Cookie:" header + * + * @param string cookie name + * @param string cookie value + * @return HTTP_Request2 + * @throws HTTP_Request2_Exception + */ + public function addCookie($name, $value) + { + $cookie = $name . '=' . $value; + if (preg_match(self::REGEXP_INVALID_COOKIE, $cookie)) { + throw new HTTP_Request2_Exception("Invalid cookie: '{$cookie}'"); + } + $cookies = empty($this->headers['cookie'])? '': $this->headers['cookie'] . '; '; + $this->setHeader('cookie', $cookies . $cookie); + + return $this; + } + + /** + * Sets the request body + * + * @param string Either a string with the body or filename containing body + * @param bool Whether first parameter is a filename + * @return HTTP_Request2 + * @throws HTTP_Request2_Exception + */ + public function setBody($body, $isFilename = false) + { + if (!$isFilename) { + $this->body = (string)$body; + } else { + if (!($fp = @fopen($body, 'rb'))) { + throw new HTTP_Request2_Exception("Cannot open file {$body}"); + } + $this->body = $fp; + if (empty($this->headers['content-type'])) { + $this->setHeader('content-type', self::detectMimeType($body)); + } + } + + 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 ('application/x-www-form-urlencoded' == $this->headers['content-type']) { + $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 ('multipart/form-data' == $this->headers['content-type']) { + 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. + * + * @param string name of file-upload field + * @param mixed full name of local file + * @param string filename to send in the request + * @param string content-type of file being uploaded + * @return HTTP_Request2 + * @throws HTTP_Request2_Exception + */ + public function addUpload($fieldName, $filename, $sendFilename = null, + $contentType = null) + { + if (!is_array($filename)) { + if (!($fp = @fopen($filename, 'rb'))) { + throw new HTTP_Request2_Exception("Cannot open file {$filename}"); + } + $this->uploads[$fieldName] = array( + 'fp' => $fp, + 'filename' => empty($sendFilename)? basename($filename): $sendFilename, + 'size' => filesize($filename), + 'type' => empty($contentType)? self::detectMimeType($filename): $contentType + ); + } else { + $fps = $names = $sizes = $types = array(); + foreach ($filename as $f) { + if (!is_array($f)) { + $f = array($f); + } + if (!($fp = @fopen($f[0], 'rb'))) { + throw new HTTP_Request2_Exception("Cannot open file {$f[0]}"); + } + $fps[] = $fp; + $names[] = empty($f[1])? basename($f[0]): $f[1]; + $sizes[] = filesize($f[0]); + $types[] = empty($f[2])? self::detectMimeType($f[0]): $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: + *
    + *
  • 'connect' - after connection to remote server, + * data is the destination (string)
  • + *
  • 'disconnect' - after disconnection from server
  • + *
  • 'sentHeaders' - after sending the request headers, + * data is the headers sent (string)
  • + *
  • 'sentBodyPart' - after sending a part of the request body, + * data is the length of that part (int)
  • + *
  • 'receivedHeaders' - after receiving the response headers, + * data is HTTP_Request2_Response object
  • + *
  • 'receivedBodyPart' - after receiving a part of the response + * body, data is that part (string)
  • + *
  • 'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still + * encoded by Content-Encoding
  • + *
  • 'receivedBody' - after receiving the complete response + * body, data is HTTP_Request2_Response object
  • + *
+ * 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 + * + * $request->setAdapter('curl'); + * + * will work. + * + * @param string|HTTP_Request2_Adapter + * @return HTTP_Request2 + * @throws HTTP_Request2_Exception + */ + 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_Exception("Class {$adapter} not found"); + } + } + $adapter = new $adapter; + } + if (!$adapter instanceof HTTP_Request2_Adapter) { + throw new HTTP_Request2_Exception('Parameter is not a HTTP request adapter'); + } + $this->adapter = $adapter; + + return $this; + } + + /** + * 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) { + throw new HTTP_Request2_Exception('No URL given'); + } elseif (!$this->url->isAbsolute()) { + throw new HTTP_Request2_Exception('Absolute URL required'); + } elseif (!in_array(strtolower($this->url->getScheme()), array('https', 'http'))) { + throw new HTTP_Request2_Exception('Not a HTTP URL'); + } + if (empty($this->adapter)) { + $this->setAdapter($this->getConfig('adapter')); + } + // magic_quotes_runtime may break file uploads and chunked response + // processing; see bug #4543 + if ($magicQuotes = ini_get('magic_quotes_runtime')) { + ini_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) { + ini_set('magic_quotes_runtime', true); + } + if (!empty($oldEncoding)) { + mb_internal_encoding($oldEncoding); + } + // rethrow the exception + if (!empty($e)) { + throw $e; + } + return $response; + } + + /** + * 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/extlib/HTTP/Request2/Adapter.php b/extlib/HTTP/Request2/Adapter.php new file mode 100644 index 000000000..39b092b34 --- /dev/null +++ b/extlib/HTTP/Request2/Adapter.php @@ -0,0 +1,152 @@ + + * 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 + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Adapter.php 274684 2009-01-26 23:07:27Z 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 + * @version Release: 0.4.1 + */ +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 + ) { + unset($headers['content-type']); + // 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']); + } + } else { + if (empty($headers['content-type'])) { + $headers['content-type'] = 'application/x-www-form-urlencoded'; + } + $headers['content-length'] = $this->contentLength; + } + } +} +?> diff --git a/extlib/HTTP/Request2/Adapter/Curl.php b/extlib/HTTP/Request2/Adapter/Curl.php new file mode 100644 index 000000000..4d4de0dcc --- /dev/null +++ b/extlib/HTTP/Request2/Adapter/Curl.php @@ -0,0 +1,383 @@ + + * 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 + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Curl.php 278226 2009-04-03 21:32:48Z 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 + * @version Release: 0.4.1 + */ +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 + ); + + /** + * 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; + + /** + * 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_Exception('cURL extension not available'); + } + + $this->request = $request; + $this->response = null; + $this->position = 0; + $this->eventSentHeaders = false; + $this->eventReceivedHeaders = false; + + try { + if (false === curl_exec($ch = $this->createCurlHandle())) { + $errorMessage = 'Error sending request: #' . curl_errno($ch) . + ' ' . curl_error($ch); + } + } catch (Exception $e) { + } + $this->lastInfo = curl_getinfo($ch); + curl_close($ch); + + if (!empty($e)) { + throw $e; + } elseif (!empty($errorMessage)) { + throw new HTTP_Request2_Exception($errorMessage); + } + + if (0 < $this->lastInfo['size_download']) { + $this->request->setLastEvent('receivedBody', $this->response); + } + return $this->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_Exception + */ + protected function createCurlHandle() + { + $ch = curl_init(); + + curl_setopt_array($ch, array( + // setup callbacks + CURLOPT_READFUNCTION => array($this, 'callbackReadBody'), + CURLOPT_HEADERFUNCTION => array($this, 'callbackWriteHeader'), + CURLOPT_WRITEFUNCTION => array($this, 'callbackWriteBody'), + // disallow redirects + CURLOPT_FOLLOWLOCATION => false, + // 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() + )); + + // 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; + 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_Exception('Proxy port not provided'); + } + 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 + if (0 == strcasecmp($this->request->getUrl()->getScheme(), 'https')) { + 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'] = ''; + } + + // 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); + + // 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; + } + + /** + * 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) + ); + } + $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); + } 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); + } + $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_Exception("Malformed response: {$string}"); + } + if ($this->request->getConfig('store_body')) { + $this->response->appendBody($string); + } + $this->request->setLastEvent('receivedBodyPart', $string); + return strlen($string); + } +} +?> diff --git a/extlib/HTTP/Request2/Adapter/Mock.php b/extlib/HTTP/Request2/Adapter/Mock.php new file mode 100644 index 000000000..89688003b --- /dev/null +++ b/extlib/HTTP/Request2/Adapter/Mock.php @@ -0,0 +1,171 @@ + + * 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 + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Mock.php 274406 2009-01-23 18:01:57Z 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() + * + * $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(); + * + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov + * @version Release: 0.4.1 + */ +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 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, + * a HTTP_Request2_Response or Exception object + * @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/extlib/HTTP/Request2/Adapter/Socket.php b/extlib/HTTP/Request2/Adapter/Socket.php new file mode 100644 index 000000000..ff44d4959 --- /dev/null +++ b/extlib/HTTP/Request2/Adapter/Socket.php @@ -0,0 +1,971 @@ + + * 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 + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Socket.php 279760 2009-05-03 10:46:42Z 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 + * @version Release: 0.4.1 + */ +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; + + /** + * Global timeout, exception will be raised if request continues past this time + * @var integer + */ + protected $timeout = null; + + /** + * Remaining length of the current chunk, when reading chunked response + * @var integer + * @see readChunked() + */ + protected $chunkLength = 0; + + /** + * 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; + $keepAlive = $this->connect(); + $headers = $this->prepareHeaders(); + + // Use global request timeout if given, see feature requests #5735, #8964 + if ($timeout = $request->getConfig('timeout')) { + $this->timeout = time() + $timeout; + } else { + $this->timeout = null; + } + + try { + if (false === @fwrite($this->socket, $headers, strlen($headers))) { + throw new HTTP_Request2_Exception('Error writing request'); + } + // provide request headers to the observer, see request #7633 + $this->request->setLastEvent('sentHeaders', $headers); + $this->writeBody(); + + if ($this->timeout && time() > $this->timeout) { + throw new HTTP_Request2_Exception( + 'Request timed out after ' . + $request->getConfig('timeout') . ' second(s)' + ); + } + + $response = $this->readResponse(); + + 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(); + throw $e; + } + + return $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_Exception('Proxy port not provided'); + } + $proxy = true; + } else { + $host = $reqHost; + $port = $reqPort; + $proxy = false; + } + + if ($tunnel && !$proxy) { + throw new HTTP_Request2_Exception( + "Trying to perform CONNECT request without proxy" + ); + } + if ($secure && !in_array('ssl', stream_get_transports())) { + throw new HTTP_Request2_Exception( + 'Need OpenSSL support for https:// requests' + ); + } + + // 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_Exception( + "Error setting SSL context option '{$name}'" + ); + } + } + $this->socket = @stream_socket_client( + $remote, $errno, $errstr, + $this->request->getConfig('connect_timeout'), + STREAM_CLIENT_CONNECT, $context + ); + if (!$this->socket) { + throw new HTTP_Request2_Exception( + "Unable to connect to {$remote}. Error #{$errno}: {$errstr}" + ); + } + $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_Exception( + '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_Exception( + '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'); + $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'); + } + } + + /** + * 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 + * here are defined as quoted-string 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_Exception 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_Exception( + "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_Exception( + "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_Exception + */ + 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_Exception( + "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_Exception + */ + 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_Exception( + "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'; + } + + $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_Exception + */ + 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_Exception('Error writing request'); + } + // Provide the length of written string to the observer, request #7630 + $this->request->setLastEvent('sentBodyPart', strlen($str)); + $position += strlen($str); + } + } + + /** + * 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); + 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_Exception In case of timeout + */ + protected function readLine($bufferSize) + { + $line = ''; + while (!feof($this->socket)) { + if ($this->timeout) { + stream_set_timeout($this->socket, max($this->timeout - time(), 1)); + } + $line .= @fgets($this->socket, $bufferSize); + $info = stream_get_meta_data($this->socket); + if ($info['timed_out'] || $this->timeout && time() > $this->timeout) { + throw new HTTP_Request2_Exception( + 'Request timed out after ' . + $this->request->getConfig('timeout') . ' second(s)' + ); + } + 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_Exception In case of timeout + */ + protected function fread($length) + { + if ($this->timeout) { + stream_set_timeout($this->socket, max($this->timeout - time(), 1)); + } + $data = fread($this->socket, $length); + $info = stream_get_meta_data($this->socket); + if ($info['timed_out'] || $this->timeout && time() > $this->timeout) { + throw new HTTP_Request2_Exception( + 'Request timed out after ' . + $this->request->getConfig('timeout') . ' second(s)' + ); + } + 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_Exception + */ + 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_Exception( + "Cannot decode chunked response, invalid chunk length '{$line}'" + ); + } 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/extlib/HTTP/Request2/Exception.php b/extlib/HTTP/Request2/Exception.php new file mode 100644 index 000000000..bfef7d6c2 --- /dev/null +++ b/extlib/HTTP/Request2/Exception.php @@ -0,0 +1,62 @@ + + * 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 + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Exception.php 273003 2009-01-07 19:28:22Z avb $ + * @link http://pear.php.net/package/HTTP_Request2 + */ + +/** + * Base class for exceptions in PEAR + */ +require_once 'PEAR/Exception.php'; + +/** + * Exception class for HTTP_Request2 package + * + * Such a class is required by the Exception RFC: + * http://pear.php.net/pepr/pepr-proposal-show.php?id=132 + * + * @category HTTP + * @package HTTP_Request2 + * @version Release: 0.4.1 + */ +class HTTP_Request2_Exception extends PEAR_Exception +{ +} +?> \ No newline at end of file diff --git a/extlib/HTTP/Request2/MultipartBody.php b/extlib/HTTP/Request2/MultipartBody.php new file mode 100644 index 000000000..d8afd8344 --- /dev/null +++ b/extlib/HTTP/Request2/MultipartBody.php @@ -0,0 +1,274 @@ + + * 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 + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: MultipartBody.php 287306 2009-08-14 15:22:52Z 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 + * @version Release: 0.4.1 + * @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/extlib/HTTP/Request2/Observer/Log.php b/extlib/HTTP/Request2/Observer/Log.php new file mode 100644 index 000000000..b1a055278 --- /dev/null +++ b/extlib/HTTP/Request2/Observer/Log.php @@ -0,0 +1,215 @@ + + * 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 + * @author Alexey Borzov + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Log.php 272593 2009-01-02 16:27:14Z 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: + * + * 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(); + * + * + * A more complex example with PEAR Log: + * + * 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(); + * + * + * @category HTTP + * @package HTTP_Request2 + * @author David Jean Louis + * @author Alexey Borzov + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version Release: 0.4.1 + * @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', + 'sentBodyPart', + '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, 'w'))) { + throw new HTTP_Request2_Exception("Unable to open '{$target}'"); + } + } + + // }}} + // update() {{{ + + /** + * Called when the request notify 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 'sentBodyPart': + $this->log('> ' . $event['data']); + 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() {{{ + + /** + * Log 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/extlib/HTTP/Request2/Response.php b/extlib/HTTP/Request2/Response.php new file mode 100644 index 000000000..c7c1021fb --- /dev/null +++ b/extlib/HTTP/Request2/Response.php @@ -0,0 +1,549 @@ + + * 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 + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Response.php 287948 2009-09-01 17:12:18Z 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: + * + * $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()); + * + * + * + * @category HTTP + * @package HTTP_Request2 + * @author Alexey Borzov + * @version Release: 0.4.1 + * @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; + + /** + * 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 + * @throws HTTP_Request2_Exception if status line is invalid according to spec + */ + public function __construct($statusLine, $bodyEncoded = true) + { + if (!preg_match('!^HTTP/(\d\.\d) (\d{3})(?: (.+))?!', $statusLine, $m)) { + throw new HTTP_Request2_Exception("Malformed response: {$statusLine}"); + } + $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; + } + + /** + * 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; + + // string + } 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://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 status code + * @return integer + */ + public function getStatus() + { + return $this->code; + } + + /** + * Returns the reason phrase + * @return string + */ + public function getReasonPhrase() + { + return $this->reasonPhrase; + } + + /** + * 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 (!$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_Exception + * @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_Exception('Unable to decode body: gzip extension not available'); + } + $method = ord(substr($data, 2, 1)); + if (8 != $method) { + throw new HTTP_Request2_Exception('Error parsing gzip header: unknown compression method'); + } + $flags = ord(substr($data, 3, 1)); + if ($flags & 224) { + throw new HTTP_Request2_Exception('Error parsing gzip header: reserved bits are set'); + } + + // 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_Exception('Error parsing gzip header: data too short'); + } + $extraLength = unpack('v', substr($data, 10, 2)); + if ($length - $headerLength - 2 - $extraLength[1] < 8) { + throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); + } + $headerLength += $extraLength[1] + 2; + } + // file name, need to skip that + if ($flags & 8) { + if ($length - $headerLength - 1 < 8) { + throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); + } + $filenameLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $filenameLength || $length - $headerLength - $filenameLength - 1 < 8) { + throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); + } + $headerLength += $filenameLength + 1; + } + // comment, need to skip that also + if ($flags & 16) { + if ($length - $headerLength - 1 < 8) { + throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); + } + $commentLength = strpos(substr($data, $headerLength), chr(0)); + if (false === $commentLength || $length - $headerLength - $commentLength - 1 < 8) { + throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); + } + $headerLength += $commentLength + 1; + } + // have a CRC for header. let's check + if ($flags & 2) { + if ($length - $headerLength - 2 < 8) { + throw new HTTP_Request2_Exception('Error parsing gzip header: data too short'); + } + $crcReal = 0xffff & crc32(substr($data, 0, $headerLength)); + $crcStored = unpack('v', substr($data, $headerLength, 2)); + if ($crcReal != $crcStored[1]) { + throw new HTTP_Request2_Exception('Header CRC check failed'); + } + $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_Exception('gzinflate() call failed'); + } elseif ($dataSize != strlen($unpacked)) { + throw new HTTP_Request2_Exception('Data size check failed'); + } elseif ((0xffffffff & $dataCrc) != (0xffffffff & crc32($unpacked))) { + throw new HTTP_Request2_Exception('Data CRC check failed'); + } + return $unpacked; + } + + /** + * Decodes the message-body encoded by deflate + * + * @param string deflate-encoded data + * @return string decoded data + * @throws HTTP_Request2_Exception + */ + public static function decodeDeflate($data) + { + if (!function_exists('gzuncompress')) { + throw new HTTP_Request2_Exception('Unable to decode body: gzip extension not available'); + } + // 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/extlib/Net/URL2.php b/extlib/Net/URL2.php index 7a654aed8..f7fbcd9ce 100644 --- a/extlib/Net/URL2.php +++ b/extlib/Net/URL2.php @@ -1,44 +1,58 @@ | -// +-----------------------------------------------------------------------+ -// -// $Id: URL2.php,v 1.10 2008/04/26 21:57:08 schmidt Exp $ -// -// Net_URL2 Class (PHP5 Only) - -// This code is released under the BSD License - http://www.opensource.org/licenses/bsd-license.php /** - * @license BSD License + * 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 PHP_LexerGenerator 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 + * @copyright 2007-2008 Peytz & Co. A/S + * @license http://www.opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: URL2.php 286661 2009-08-02 12:50:54Z 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 + * @copyright 2007-2008 Peytz & Co. ApS + * @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 { @@ -46,24 +60,24 @@ class Net_URL2 * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default * is true. */ - const OPTION_STRICT = 'strict'; + const OPTION_STRICT = 'strict'; /** * Represent arrays in query using PHP's [] notation. Default is true. */ - const OPTION_USE_BRACKETS = 'use_brackets'; + const OPTION_USE_BRACKETS = 'use_brackets'; /** * URL-encode query variable keys. Default is true. */ - const OPTION_ENCODE_KEYS = 'encode_keys'; + const OPTION_ENCODE_KEYS = 'encode_keys'; /** * Query variable separators when parsing the query string. Every character * is considered a separator. Default is specified by the * arg_separator.input php.ini setting (this defaults to "&"). */ - const OPTION_SEPARATOR_INPUT = 'input_separator'; + const OPTION_SEPARATOR_INPUT = 'input_separator'; /** * Query variable separator used when generating the query string. Default @@ -75,7 +89,7 @@ class Net_URL2 /** * Default options corresponds to how PHP handles $_GET. */ - private $options = array( + private $_options = array( self::OPTION_STRICT => true, self::OPTION_USE_BRACKETS => true, self::OPTION_ENCODE_KEYS => true, @@ -86,41 +100,43 @@ class Net_URL2 /** * @var string|bool */ - private $scheme = false; + private $_scheme = false; /** * @var string|bool */ - private $userinfo = false; + private $_userinfo = false; /** * @var string|bool */ - private $host = false; + private $_host = false; /** * @var int|bool */ - private $port = false; + private $_port = false; /** * @var string */ - private $path = ''; + private $_path = ''; /** * @var string|bool */ - private $query = false; + private $_query = false; /** * @var string|bool */ - private $fragment = false; + private $_fragment = false; /** + * Constructor. + * * @param string $url an absolute or relative URL - * @param array $options + * @param array $options an array of OPTION_xxx constants */ public function __construct($url, $options = null) { @@ -130,12 +146,12 @@ class Net_URL2 ini_get('arg_separator.output')); if (is_array($options)) { foreach ($options as $optionName => $value) { - $this->setOption($optionName); + $this->setOption($optionName, $value); } } if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) { - $this->scheme = $reg[1]; + $this->_scheme = $reg[1]; $url = substr($url, strlen($reg[0])); } @@ -145,19 +161,58 @@ class Net_URL2 } $i = strcspn($url, '?#'); - $this->path = substr($url, 0, $i); + $this->_path = substr($url, 0, $i); $url = substr($url, $i); if (preg_match('@^\?([^#]*)@', $url, $reg)) { - $this->query = $reg[1]; + $this->_query = $reg[1]; $url = substr($url, strlen($reg[0])); } if ($url) { - $this->fragment = substr($url, 1); + $this->_fragment = substr($url, 1); } } + /** + * 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. @@ -166,18 +221,23 @@ class Net_URL2 */ public function getScheme() { - return $this->scheme; + return $this->_scheme; } /** - * @param string|bool $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; + $this->_scheme = $scheme; } /** @@ -188,7 +248,9 @@ class Net_URL2 */ public function getUser() { - return $this->userinfo !== false ? preg_replace('@:.*$@', '', $this->userinfo) : false; + return $this->_userinfo !== false + ? preg_replace('@:.*$@', '', $this->_userinfo) + : false; } /** @@ -201,7 +263,9 @@ class Net_URL2 */ public function getPassword() { - return $this->userinfo !== false ? substr(strstr($this->userinfo, ':'), 1) : false; + return $this->_userinfo !== false + ? substr(strstr($this->_userinfo, ':'), 1) + : false; } /** @@ -212,7 +276,7 @@ class Net_URL2 */ public function getUserinfo() { - return $this->userinfo; + return $this->_userinfo; } /** @@ -220,15 +284,15 @@ class Net_URL2 * in the userinfo part as username ":" password. * * @param string|bool $userinfo userinfo or username - * @param string|bool $password + * @param string|bool $password optional password, or false * * @return void */ public function setUserinfo($userinfo, $password = false) { - $this->userinfo = $userinfo; + $this->_userinfo = $userinfo; if ($password !== false) { - $this->userinfo .= ':' . $password; + $this->_userinfo .= ':' . $password; } } @@ -236,21 +300,24 @@ class Net_URL2 * Returns the host part, or false if there is no authority part, e.g. * relative URLs. * - * @return string|bool + * @return string|bool a hostname, an IP address, or false */ public function getHost() { - return $this->host; + return $this->_host; } /** - * @param string|bool $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; + $this->_host = $host; } /** @@ -261,65 +328,72 @@ class Net_URL2 */ public function getPort() { - return $this->port; + return $this->_port; } /** - * @param int|bool $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 int|bool $port a port number, or false * * @return void */ public function setPort($port) { - $this->port = intval($port); + $this->_port = intval($port); } /** * Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or - * false if there is no authority none. + * false if there is no authority. * * @return string|bool */ public function getAuthority() { - if (!$this->host) { + if (!$this->_host) { return false; } $authority = ''; - if ($this->userinfo !== false) { - $authority .= $this->userinfo . '@'; + if ($this->_userinfo !== false) { + $authority .= $this->_userinfo . '@'; } - $authority .= $this->host; + $authority .= $this->_host; - if ($this->port !== false) { - $authority .= ':' . $this->port; + if ($this->_port !== false) { + $authority .= ':' . $this->_port; } return $authority; } /** - * @param string|false $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->user = false; - $this->pass = false; - $this->host = false; - $this->port = false; - if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) { + $this->_userinfo = false; + $this->_host = false; + $this->_port = false; + if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) { if ($reg[1]) { - $this->userinfo = $reg[2]; + $this->_userinfo = $reg[2]; } - $this->host = $reg[3]; + $this->_host = $reg[3]; if (isset($reg[5])) { - $this->port = intval($reg[5]); + $this->_port = intval($reg[5]); } } } @@ -331,65 +405,74 @@ class Net_URL2 */ public function getPath() { - return $this->path; + return $this->_path; } /** - * @param string $path + * Sets the path part (possibly an empty string). + * + * @param string $path a path * * @return void */ public function setPath($path) { - $this->path = $path; + $this->_path = $path; } /** * Returns the query string (excluding the leading "?"), or false if "?" - * isn't present in the URL. + * is not present in the URL. * * @return string|bool * @see self::getQueryVariables() */ public function getQuery() { - return $this->query; + return $this->_query; } /** - * @param string|bool $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; + $this->_query = $query; } /** - * Returns the fragment name, or false if "#" isn't present in the URL. + * Returns the fragment name, or false if "#" is not present in the URL. * * @return string|bool */ public function getFragment() { - return $this->fragment; + return $this->_fragment; } /** - * @param string|bool $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; + $this->_fragment = $fragment; } /** * Returns the query string like an array as the variables would appear in - * $_GET in a PHP script. + * $_GET in a PHP script. If the URL does not contain a "?", an empty array + * is returned. * * @return array */ @@ -398,7 +481,7 @@ class Net_URL2 $pattern = '/[' . preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') . ']/'; - $parts = preg_split($pattern, $this->query, -1, PREG_SPLIT_NO_EMPTY); + $parts = preg_split($pattern, $this->_query, -1, PREG_SPLIT_NO_EMPTY); $return = array(); foreach ($parts as $part) { @@ -445,6 +528,8 @@ class Net_URL2 } /** + * Sets the query string to the specified variable in the query string. + * * @param array $array (name => value) array * * @return void @@ -452,11 +537,11 @@ class Net_URL2 public function setQueryVariables(array $array) { if (!$array) { - $this->query = false; + $this->_query = false; } else { foreach ($array as $name => $value) { if ($this->getOption(self::OPTION_ENCODE_KEYS)) { - $name = rawurlencode($name); + $name = self::urlencode($name); } if (is_array($value)) { @@ -466,19 +551,21 @@ class Net_URL2 : ($name . '=' . $v); } } elseif (!is_null($value)) { - $parts[] = $name . '=' . $value; + $parts[] = $name . '=' . self::urlencode($value); } else { $parts[] = $name; } } - $this->query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT), - $parts); + $this->_query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT), + $parts); } } /** - * @param string $name - * @param mixed $value + * Sets the specified variable in the query string. + * + * @param string $name variable name + * @param mixed $value variable value * * @return array */ @@ -490,7 +577,9 @@ class Net_URL2 } /** - * @param string $name + * Removes the specifed variable from the query string. + * + * @param string $name a query string variable, e.g. "foo" in "?foo=1" * * @return void */ @@ -511,27 +600,38 @@ class Net_URL2 // See RFC 3986, section 5.3 $url = ""; - if ($this->scheme !== false) { - $url .= $this->scheme . ':'; + if ($this->_scheme !== false) { + $url .= $this->_scheme . ':'; } $authority = $this->getAuthority(); if ($authority !== false) { $url .= '//' . $authority; } - $url .= $this->path; + $url .= $this->_path; - if ($this->query !== false) { - $url .= '?' . $this->query; + if ($this->_query !== false) { + $url .= '?' . $this->_query; } - if ($this->fragment !== false) { - $url .= '#' . $this->fragment; + 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. @@ -555,36 +655,38 @@ class Net_URL2 // See RFC 3886, section 6 // Schemes are case-insensitive - if ($this->scheme) { - $this->scheme = strtolower($this->scheme); + if ($this->_scheme) { + $this->_scheme = strtolower($this->_scheme); } // Hostnames are case-insensitive - if ($this->host) { - $this->host = strtolower($this->host); + 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')) { + if ($this->_port && + $this->_scheme && + $this->_port == getservbyname($this->_scheme, 'tcp')) { - $this->port = false; + $this->_port = false; } // Normalize case of %XX percentage-encodings (RFC 3986, section 6.2.2.1) - foreach (array('userinfo', 'host', 'path') as $part) { + foreach (array('_userinfo', '_host', '_path') as $part) { if ($this->$part) { - $this->$part = preg_replace('/%[0-9a-f]{2}/ie', 'strtoupper("\0")', $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); + $this->_path = self::removeDotSegments($this->_path); // Scheme based normalization (RFC 3986, section 6.2.3) - if ($this->host && !$this->path) { - $this->path = '/'; + if ($this->_host && !$this->_path) { + $this->_path = '/'; } } @@ -595,7 +697,7 @@ class Net_URL2 */ public function isAbsolute() { - return (bool) $this->scheme; + return (bool) $this->_scheme; } /** @@ -608,7 +710,7 @@ class Net_URL2 */ public function resolve($reference) { - if (is_string($reference)) { + if (!$reference instanceof Net_URL2) { $reference = new self($reference); } if (!$this->isAbsolute()) { @@ -617,54 +719,54 @@ class Net_URL2 // 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; + if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) { + $reference->_scheme = false; } $target = new self(''); - if ($reference->scheme !== false) { - $target->scheme = $reference->scheme; + if ($reference->_scheme !== false) { + $target->_scheme = $reference->_scheme; $target->setAuthority($reference->getAuthority()); - $target->path = self::removeDotSegments($reference->path); - $target->query = $reference->query; + $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; + $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; + if ($reference->_path == '') { + $target->_path = $this->_path; + if ($reference->_query !== false) { + $target->_query = $reference->_query; } else { - $target->query = $this->query; + $target->_query = $this->_query; } } else { - if (substr($reference->path, 0, 1) == '/') { - $target->path = self::removeDotSegments($reference->path); + 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; + if ($this->_host !== false && $this->_path == '') { + $target->_path = '/' . $this->_path; } else { - $i = strrpos($this->path, '/'); + $i = strrpos($this->_path, '/'); if ($i !== false) { - $target->path = substr($this->path, 0, $i + 1); + $target->_path = substr($this->_path, 0, $i + 1); } - $target->path .= $reference->path; + $target->_path .= $reference->_path; } - $target->path = self::removeDotSegments($target->path); + $target->_path = self::removeDotSegments($target->_path); } - $target->query = $reference->query; + $target->_query = $reference->_query; } $target->setAuthority($this->getAuthority()); } - $target->scheme = $this->scheme; + $target->_scheme = $this->_scheme; } - $target->fragment = $reference->fragment; + $target->_fragment = $reference->_fragment; return $target; } @@ -677,7 +779,7 @@ class Net_URL2 * * @return string a path */ - private static function removeDotSegments($path) + public static function removeDotSegments($path) { $output = ''; @@ -685,28 +787,25 @@ class Net_URL2 // method $j = 0; while ($path && $j++ < 100) { - // Step A if (substr($path, 0, 2) == './') { + // Step 2.A $path = substr($path, 2); } elseif (substr($path, 0, 3) == '../') { + // Step 2.A $path = substr($path, 3); - - // Step B } elseif (substr($path, 0, 3) == '/./' || $path == '/.') { + // Step 2.B $path = '/' . substr($path, 3); - - // Step C } elseif (substr($path, 0, 4) == '/../' || $path == '/..') { - $path = '/' . substr($path, 4); - $i = strrpos($output, '/'); + // Step 2.C + $path = '/' . substr($path, 4); + $i = strrpos($output, '/'); $output = $i === false ? '' : substr($output, 0, $i); - - // Step D } elseif ($path == '.' || $path == '..') { + // Step 2.D $path = ''; - - // Step E } else { + // Step 2.E $i = strpos($path, '/'); if ($i === 0) { $i = strpos($path, '/', 1); @@ -722,6 +821,22 @@ class Net_URL2 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. @@ -737,13 +852,13 @@ class Net_URL2 // Begin with a relative URL $url = new self($_SERVER['PHP_SELF']); - $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; - $url->host = $_SERVER['SERVER_NAME']; + $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + $url->_host = $_SERVER['SERVER_NAME']; $port = intval($_SERVER['SERVER_PORT']); - if ($url->scheme == 'http' && $port != 80 || - $url->scheme == 'https' && $port != 443) { + if ($url->_scheme == 'http' && $port != 80 || + $url->_scheme == 'https' && $port != 443) { - $url->port = $port; + $url->_port = $port; } return $url; } @@ -773,7 +888,7 @@ class Net_URL2 // Begin with a relative URL $url = new self($_SERVER['REQUEST_URI']); - $url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; + $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; // Set host and possibly port $url->setAuthority($_SERVER['HTTP_HOST']); return $url; @@ -792,10 +907,10 @@ class Net_URL2 */ function setOption($optionName, $value) { - if (!array_key_exists($optionName, $this->options)) { + if (!array_key_exists($optionName, $this->_options)) { return false; } - $this->options[$optionName] = $value; + $this->_options[$optionName] = $value; } /** @@ -807,7 +922,7 @@ class Net_URL2 */ function getOption($optionName) { - return isset($this->options[$optionName]) - ? $this->options[$optionName] : false; + return isset($this->_options[$optionName]) + ? $this->_options[$optionName] : false; } } diff --git a/install.php b/install.php index 6bfc4c2e2..d34e92dab 100644 --- a/install.php +++ b/install.php @@ -93,6 +93,13 @@ $external_libraries=array( 'include'=>'HTTP/Request.php', 'check_class'=>'HTTP_Request' ), + array( + 'name'=>'HTTP_Request2', + 'pear'=>'HTTP_Request2', + 'url'=>'http://pear.php.net/package/HTTP_Request2', + 'include'=>'HTTP/Request2.php', + 'check_class'=>'HTTP_Request2' + ), array( 'name'=>'Mail', 'pear'=>'Mail', diff --git a/lib/Shorturl_api.php b/lib/Shorturl_api.php index 18ae7719b..de4d55012 100644 --- a/lib/Shorturl_api.php +++ b/lib/Shorturl_api.php @@ -41,22 +41,18 @@ abstract class ShortUrlApi return strlen($url) >= common_config('site', 'shorturllength'); } - protected function http_post($data) { - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $this->service_url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); - $response = curl_exec($ch); - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); - curl_close($ch); - if (($code < 200) || ($code >= 400)) return false; - return $response; + protected function http_post($data) + { + $request = HTTPClient::start(); + $response = $request->post($this->service_url, null, $data); + return $response->getBody(); } - protected function http_get($url) { - $encoded_url = urlencode($url); - return file_get_contents("{$this->service_url}$encoded_url"); + protected function http_get($url) + { + $request = HTTPClient::start(); + $response = $request->get($this->service_url . urlencode($url)); + return $response->getBody(); } protected function tidy($response) { diff --git a/lib/curlclient.php b/lib/curlclient.php deleted file mode 100644 index c307c2984..000000000 --- a/lib/curlclient.php +++ /dev/null @@ -1,179 +0,0 @@ -. - * - * @category HTTP - * @package StatusNet - * @author Evan Prodromou - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - exit(1); -} - -define(CURLCLIENT_VERSION, "0.1"); - -/** - * Wrapper for Curl - * - * Makes Curl HTTP client calls within our HTTPClient framework - * - * @category HTTP - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -class CurlClient extends HTTPClient -{ - function __construct() - { - } - - function head($url, $headers=null) - { - $ch = curl_init($url); - - $this->setup($ch); - - curl_setopt_array($ch, - array(CURLOPT_NOBODY => true)); - - if (!is_null($headers)) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - } - - $result = curl_exec($ch); - - curl_close($ch); - - return $this->parseResults($result); - } - - function get($url, $headers=null) - { - $ch = curl_init($url); - - $this->setup($ch); - - if (!is_null($headers)) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - } - - $result = curl_exec($ch); - - curl_close($ch); - - return $this->parseResults($result); - } - - function post($url, $headers=null, $body=null) - { - $ch = curl_init($url); - - $this->setup($ch); - - curl_setopt($ch, CURLOPT_POST, true); - - if (!is_null($body)) { - curl_setopt($ch, CURLOPT_POSTFIELDS, $body); - } - - if (!is_null($headers)) { - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); - } - - $result = curl_exec($ch); - - curl_close($ch); - - return $this->parseResults($result); - } - - function setup($ch) - { - curl_setopt_array($ch, - array(CURLOPT_USERAGENT => $this->userAgent(), - CURLOPT_HEADER => true, - CURLOPT_RETURNTRANSFER => true)); - } - - function userAgent() - { - $version = curl_version(); - return parent::userAgent() . " CurlClient/".CURLCLIENT_VERSION . " cURL/" . $version['version']; - } - - function parseResults($results) - { - $resp = new HTTPResponse(); - - $lines = explode("\r\n", $results); - - if (preg_match("#^HTTP/1.[01] (\d\d\d) .+$#", $lines[0], $match)) { - $resp->code = $match[1]; - } else { - throw Exception("Bad format: initial line is not HTTP status line"); - } - - $lastk = null; - - for ($i = 1; $i < count($lines); $i++) { - $l =& $lines[$i]; - if (mb_strlen($l) == 0) { - $resp->body = implode("\r\n", array_slice($lines, $i + 1)); - break; - } - if (preg_match("#^(\S+):\s+(.*)$#", $l, $match)) { - $k = $match[1]; - $v = $match[2]; - - if (array_key_exists($k, $resp->headers)) { - if (is_array($resp->headers[$k])) { - $resp->headers[$k][] = $v; - } else { - $resp->headers[$k] = array($resp->headers[$k], $v); - } - } else { - $resp->headers[$k] = $v; - } - $lastk = $k; - } else if (preg_match("#^\s+(.*)$#", $l, $match)) { - // continuation line - if (is_null($lastk)) { - throw Exception("Bad format: initial whitespace in headers"); - } - $h =& $resp->headers[$lastk]; - if (is_array($h)) { - $n = count($h); - $h[$n-1] .= $match[1]; - } else { - $h .= $match[1]; - } - } - } - - return $resp; - } -} diff --git a/lib/default.php b/lib/default.php index 7ec8558b0..f6cc4b725 100644 --- a/lib/default.php +++ b/lib/default.php @@ -228,8 +228,6 @@ $default = array('contentlimit' => null), 'message' => array('contentlimit' => null), - 'http' => - array('client' => 'curl'), // XXX: should this be the default? 'location' => array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth ); diff --git a/lib/httpclient.php b/lib/httpclient.php index f16e31e10..3f8262076 100644 --- a/lib/httpclient.php +++ b/lib/httpclient.php @@ -31,6 +31,9 @@ if (!defined('STATUSNET')) { exit(1); } +require_once 'HTTP/Request2.php'; +require_once 'HTTP/Request2/Response.php'; + /** * Useful structure for HTTP responses * @@ -38,18 +41,53 @@ if (!defined('STATUSNET')) { * ways of doing them. This class hides the specifics of what underlying * library (curl or PHP-HTTP or whatever) that's used. * + * This extends the HTTP_Request2_Response class with methods to get info + * about any followed redirects. + * * @category HTTP - * @package StatusNet - * @author Evan Prodromou - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ + * @package StatusNet + * @author Evan Prodromou + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ */ - -class HTTPResponse +class HTTPResponse extends HTTP_Request2_Response { - public $code = null; - public $headers = array(); - public $body = null; + function __construct(HTTP_Request2_Response $response, $url, $redirects=0) + { + foreach (get_object_vars($response) as $key => $val) { + $this->$key = $val; + } + $this->url = strval($url); + $this->redirectCount = intval($redirects); + } + + /** + * Get the count of redirects that have been followed, if any. + * @return int + */ + function getRedirectCount() + { + return $this->redirectCount; + } + + /** + * Gets the final target URL, after any redirects have been followed. + * @return string URL + */ + function getUrl() + { + return $this->url; + } + + /** + * Check if the response is OK, generally a 200 status code. + * @return bool + */ + function isOk() + { + return ($this->getStatus() == 200); + } } /** @@ -59,64 +97,163 @@ class HTTPResponse * ways of doing them. This class hides the specifics of what underlying * library (curl or PHP-HTTP or whatever) that's used. * + * This extends the PEAR HTTP_Request2 package: + * - sends StatusNet-specific User-Agent header + * - 'follow_redirects' config option, defaulting off + * - 'max_redirs' config option, defaulting to 10 + * - extended response class adds getRedirectCount() and getUrl() methods + * - get() and post() convenience functions return body content directly + * * @category HTTP * @package StatusNet * @author Evan Prodromou + * @author Brion Vibber * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ */ -class HTTPClient +class HTTPClient extends HTTP_Request2 { - static $_client = null; - static function start() + function __construct($url=null, $method=self::METHOD_GET, $config=array()) { - if (!is_null(self::$_client)) { - return self::$_client; - } - - $type = common_config('http', 'client'); - - switch ($type) { - case 'curl': - self::$_client = new CurlClient(); - break; - default: - throw new Exception("Unknown HTTP client type '$type'"); - break; - } - - return self::$_client; + $this->config['max_redirs'] = 10; + $this->config['follow_redirects'] = true; + parent::__construct($url, $method, $config); + $this->setHeader('User-Agent', $this->userAgent()); } - function head($url, $headers) + /** + * Convenience/back-compat instantiator + * @return HTTPClient + */ + public static function start() { - throw new Exception("HEAD method unimplemented"); + return new HTTPClient(); } - function get($url, $headers) + /** + * Convenience function to run a GET request. + * + * @return HTTPResponse + * @throws HTTP_Request2_Exception + */ + public function get($url, $headers=array()) { - throw new Exception("GET method unimplemented"); + return $this->doRequest($url, self::METHOD_GET, $headers); } - function post($url, $headers, $body) + /** + * Convenience function to run a HEAD request. + * + * @return HTTPResponse + * @throws HTTP_Request2_Exception + */ + public function head($url, $headers=array()) { - throw new Exception("POST method unimplemented"); + return $this->doRequest($url, self::METHOD_HEAD, $headers); } - function put($url, $headers, $body) + /** + * Convenience function to POST form data. + * + * @param string $url + * @param array $headers optional associative array of HTTP headers + * @param array $data optional associative array or blob of form data to submit + * @return HTTPResponse + * @throws HTTP_Request2_Exception + */ + public function post($url, $headers=array(), $data=array()) { - throw new Exception("PUT method unimplemented"); + if ($data) { + $this->addPostParameter($data); + } + return $this->doRequest($url, self::METHOD_POST, $headers); } - function delete($url, $headers) + /** + * @return HTTPResponse + * @throws HTTP_Request2_Exception + */ + protected function doRequest($url, $method, $headers) { - throw new Exception("DELETE method unimplemented"); + $this->setUrl($url); + $this->setMethod($method); + if ($headers) { + foreach ($headers as $header) { + $this->setHeader($header); + } + } + $response = $this->send(); + return $response; + } + + protected function log($level, $detail) { + $method = $this->getMethod(); + $url = $this->getUrl(); + common_log($level, __CLASS__ . ": HTTP $method $url - $detail"); } + /** + * Pulls up StatusNet's customized user-agent string, so services + * we hit can track down the responsible software. + * + * @return string + */ function userAgent() { return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")"; } + + /** + * Actually performs the HTTP request and returns an HTTPResponse object + * with response body and header info. + * + * Wraps around parent send() to add logging and redirection processing. + * + * @return HTTPResponse + * @throw HTTP_Request2_Exception + */ + public function send() + { + $maxRedirs = intval($this->config['max_redirs']); + if (empty($this->config['follow_redirects'])) { + $maxRedirs = 0; + } + $redirs = 0; + do { + try { + $response = parent::send(); + } catch (HTTP_Request2_Exception $e) { + $this->log(LOG_ERR, $e->getMessage()); + throw $e; + } + $code = $response->getStatus(); + if ($code >= 200 && $code < 300) { + $reason = $response->getReasonPhrase(); + $this->log(LOG_INFO, "$code $reason"); + } elseif ($code >= 300 && $code < 400) { + $url = $this->getUrl(); + $target = $response->getHeader('Location'); + + if (++$redirs >= $maxRedirs) { + common_log(LOG_ERR, __CLASS__ . ": Too many redirects: skipping $code redirect from $url to $target"); + break; + } + try { + $this->setUrl($target); + $this->setHeader('Referer', $url); + common_log(LOG_INFO, __CLASS__ . ": Following $code redirect from $url to $target"); + continue; + } catch (HTTP_Request2_Exception $e) { + common_log(LOG_ERR, __CLASS__ . ": Invalid $code redirect from $url to $target"); + } + } else { + $reason = $response->getReasonPhrase(); + $this->log(LOG_ERR, "$code $reason"); + } + break; + } while ($maxRedirs); + return new HTTPResponse($response, $this->getUrl(), $redirs); + } } diff --git a/lib/oauthclient.php b/lib/oauthclient.php index f1827726e..1a86e2460 100644 --- a/lib/oauthclient.php +++ b/lib/oauthclient.php @@ -43,7 +43,7 @@ require_once 'OAuth.php'; * @link http://status.net/ * */ -class OAuthClientCurlException extends Exception +class OAuthClientException extends Exception { } @@ -97,9 +97,14 @@ class OAuthClient function getRequestToken($url) { $response = $this->oAuthGet($url); - parse_str($response); - $token = new OAuthToken($oauth_token, $oauth_token_secret); - return $token; + $arr = array(); + parse_str($response, $arr); + if (isset($arr['oauth_token']) && isset($arr['oauth_token_secret'])) { + $token = new OAuthToken($arr['oauth_token'], @$arr['oauth_token_secret']); + return $token; + } else { + throw new OAuthClientException(); + } } /** @@ -177,7 +182,7 @@ class OAuthClient } /** - * Make a HTTP request using cURL. + * Make a HTTP request. * * @param string $url Where to make the * @param array $params post parameters @@ -186,40 +191,32 @@ class OAuthClient */ function httpRequest($url, $params = null) { - $options = array( - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FAILONERROR => true, - CURLOPT_HEADER => false, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_USERAGENT => 'StatusNet', - CURLOPT_CONNECTTIMEOUT => 120, - CURLOPT_TIMEOUT => 120, - CURLOPT_HTTPAUTH => CURLAUTH_ANY, - CURLOPT_SSL_VERIFYPEER => false, - - // Twitter is strict about accepting invalid "Expect" headers - - CURLOPT_HTTPHEADER => array('Expect:') - ); + $request = new HTTPClient($url); + $request->setConfig(array( + 'connect_timeout' => 120, + 'timeout' => 120, + 'follow_redirects' => true, + 'ssl_verify_peer' => false, + )); + + // Twitter is strict about accepting invalid "Expect" headers + $request->setHeader('Expect', ''); if (isset($params)) { - $options[CURLOPT_POST] = true; - $options[CURLOPT_POSTFIELDS] = $params; + $request->setMethod(HTTP_Request2::METHOD_POST); + $request->setBody($params); } - $ch = curl_init($url); - curl_setopt_array($ch, $options); - $response = curl_exec($ch); - - if ($response === false) { - $msg = curl_error($ch); - $code = curl_errno($ch); - throw new OAuthClientCurlException($msg, $code); + try { + $response = $request->send(); + $code = $response->getStatus(); + if ($code < 200 || $code >= 400) { + throw new OAuthClientException($response->getBody(), $code); + } + return $response->getBody(); + } catch (Exception $e) { + throw new OAuthClientException($e->getMessage(), $e->getCode()); } - - curl_close($ch); - - return $response; } } diff --git a/lib/ping.php b/lib/ping.php index 175bf8440..5698c4038 100644 --- a/lib/ping.php +++ b/lib/ping.php @@ -44,20 +44,16 @@ function ping_broadcast_notice($notice) { array('nickname' => $profile->nickname)), $tags)); - $context = stream_context_create(array('http' => array('method' => "POST", - 'header' => - "Content-Type: text/xml\r\n". - "User-Agent: StatusNet/".STATUSNET_VERSION."\r\n", - 'content' => $req))); - $file = file_get_contents($notify_url, false, $context); + $request = HTTPClient::start(); + $httpResponse = $request->post($notify_url, array('Content-Type: text/xml'), $req); - if ($file === false || mb_strlen($file) == 0) { + if (!$httpResponse || mb_strlen($httpResponse->getBody()) == 0) { common_log(LOG_WARNING, "XML-RPC empty results for ping ($notify_url, $notice->id) "); continue; } - $response = xmlrpc_decode($file); + $response = xmlrpc_decode($httpResponse->getBody()); if (is_array($response) && xmlrpc_is_fault($response)) { common_log(LOG_WARNING, diff --git a/lib/snapshot.php b/lib/snapshot.php index ede846e5b..2a10c6b93 100644 --- a/lib/snapshot.php +++ b/lib/snapshot.php @@ -172,26 +172,9 @@ class Snapshot { // XXX: Use OICU2 and OAuth to make authorized requests - $postdata = http_build_query($this->stats); - - $opts = - array('http' => - array( - 'method' => 'POST', - 'header' => 'Content-type: '. - 'application/x-www-form-urlencoded', - 'content' => $postdata, - 'user_agent' => 'StatusNet/'.STATUSNET_VERSION - ) - ); - - $context = stream_context_create($opts); - $reporturl = common_config('snapshot', 'reporturl'); - - $result = @file_get_contents($reporturl, false, $context); - - return $result; + $request = HTTPClient::start(); + $request->post($reporturl, null, $this->stats); } /** diff --git a/plugins/BlogspamNetPlugin.php b/plugins/BlogspamNetPlugin.php index c14569746..51236001a 100644 --- a/plugins/BlogspamNetPlugin.php +++ b/plugins/BlogspamNetPlugin.php @@ -22,6 +22,7 @@ * @category Plugin * @package StatusNet * @author Evan Prodromou + * @author Brion Vibber * @copyright 2009 StatusNet, Inc. * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @link http://status.net/ @@ -69,14 +70,12 @@ class BlogspamNetPlugin extends Plugin { $args = $this->testArgs($notice); common_debug("Blogspamnet args = " . print_r($args, TRUE)); - $request = xmlrpc_encode_request('testComment', array($args)); - $context = stream_context_create(array('http' => array('method' => "POST", - 'header' => - "Content-Type: text/xml\r\n". - "User-Agent: " . $this->userAgent(), - 'content' => $request))); - $file = file_get_contents($this->baseUrl, false, $context); - $response = xmlrpc_decode($file); + $requestBody = xmlrpc_encode_request('testComment', array($args)); + + $request = HTTPClient::start(); + $httpResponse = $request->post($this->baseUrl, array('Content-Type: text/xml'), $requestBody); + + $response = xmlrpc_decode($httpResponse->getBody()); if (xmlrpc_is_fault($response)) { throw new ServerException("$response[faultString] ($response[faultCode])", 500); } else { diff --git a/plugins/GeonamesPlugin.php b/plugins/GeonamesPlugin.php index 80ef44cc9..e18957c36 100644 --- a/plugins/GeonamesPlugin.php +++ b/plugins/GeonamesPlugin.php @@ -74,8 +74,8 @@ class GeonamesPlugin extends Plugin $result = $client->get('http://ws.geonames.org/search?'.$str); - if ($result->code == "200") { - $rj = json_decode($result->body); + if ($result->isOk()) { + $rj = json_decode($result->getBody()); if (count($rj->geonames) > 0) { $n = $rj->geonames[0]; @@ -121,9 +121,9 @@ class GeonamesPlugin extends Plugin $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str); - if ($result->code == "200") { + if ($result->isOk()) { - $rj = json_decode($result->body); + $rj = json_decode($result->getBody()); if (count($rj->geonames) > 0) { @@ -182,9 +182,9 @@ class GeonamesPlugin extends Plugin $result = $client->get('http://ws.geonames.org/findNearbyPlaceNameJSON?'.$str); - if ($result->code == "200") { + if ($result->isOk()) { - $rj = json_decode($result->body); + $rj = json_decode($result->getBody()); if (count($rj->geonames) > 0) { @@ -249,9 +249,9 @@ class GeonamesPlugin extends Plugin $result = $client->get('http://ws.geonames.org/hierarchyJSON?'.$str); - if ($result->code == "200") { + if ($result->isOk()) { - $rj = json_decode($result->body); + $rj = json_decode($result->getBody()); if (count($rj->geonames) > 0) { diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php index 7665b6c1e..852253b02 100644 --- a/plugins/LilUrl/LilUrlPlugin.php +++ b/plugins/LilUrl/LilUrlPlugin.php @@ -58,7 +58,10 @@ class LilUrl extends ShortUrlApi $y = @simplexml_load_string($response); if (!isset($y->body)) return $url; $x = $y->body->p[0]->a->attributes(); - if (isset($x['href'])) return $x['href']; + if (isset($x['href'])) { + common_log(LOG_INFO, __CLASS__ . ": shortened $url to $x[href]"); + return $x['href']; + } return $url; } } diff --git a/plugins/LinkbackPlugin.php b/plugins/LinkbackPlugin.php index 60f7a60c7..915d15c07 100644 --- a/plugins/LinkbackPlugin.php +++ b/plugins/LinkbackPlugin.php @@ -129,18 +129,12 @@ class LinkbackPlugin extends Plugin } } - $request = xmlrpc_encode_request('pingback.ping', $args); - $context = stream_context_create(array('http' => array('method' => "POST", - 'header' => - "Content-Type: text/xml\r\n". - "User-Agent: " . $this->userAgent(), - 'content' => $request))); - $file = file_get_contents($endpoint, false, $context); - if (!$file) { - common_log(LOG_WARNING, - "Pingback request failed for '$url' ($endpoint)"); - } else { - $response = xmlrpc_decode($file); + $request = HTTPClient::start(); + try { + $response = $request->post($endpoint, + array('Content-Type: text/xml'), + xmlrpc_encode_request('pingback.ping', $args)); + $response = xmlrpc_decode($response->getBody()); if (xmlrpc_is_fault($response)) { common_log(LOG_WARNING, "Pingback error for '$url' ($endpoint): ". @@ -150,6 +144,9 @@ class LinkbackPlugin extends Plugin "Pingback success for '$url' ($endpoint): ". "'$response'"); } + } catch (HTTP_Request2_Exception $e) { + common_log(LOG_WARNING, + "Pingback request failed for '$url' ($endpoint)"); } } diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php index 82d772048..d59d63e47 100644 --- a/plugins/SimpleUrl/SimpleUrlPlugin.php +++ b/plugins/SimpleUrl/SimpleUrlPlugin.php @@ -65,15 +65,6 @@ class SimpleUrlPlugin extends Plugin class SimpleUrl extends ShortUrlApi { protected function shorten_imp($url) { - $curlh = curl_init(); - curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 20); // # seconds to wait - curl_setopt($curlh, CURLOPT_USERAGENT, 'StatusNet'); - curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true); - - curl_setopt($curlh, CURLOPT_URL, $this->service_url.urlencode($url)); - $short_url = curl_exec($curlh); - - curl_close($curlh); - return $short_url; + return $this->http_get($url); } } diff --git a/plugins/TwitterBridge/daemons/synctwitterfriends.php b/plugins/TwitterBridge/daemons/synctwitterfriends.php index ed2bf48a2..671e3c7af 100755 --- a/plugins/TwitterBridge/daemons/synctwitterfriends.php +++ b/plugins/TwitterBridge/daemons/synctwitterfriends.php @@ -152,8 +152,8 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon $friends_ids = $client->friendsIds(); } catch (Exception $e) { common_log(LOG_WARNING, $this->name() . - ' - cURL error getting friend ids ' . - $e->getCode() . ' - ' . $e->getMessage()); + ' - error getting friend ids: ' . + $e->getMessage()); return $friends; } diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index 81bbbc7c5..b5428316b 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -109,12 +109,16 @@ class TwitterStatusFetcher extends ParallelizingDaemon $flink->find(); $flinks = array(); + common_log(LOG_INFO, "hello"); while ($flink->fetch()) { if (($flink->noticesync & FOREIGN_NOTICE_RECV) == FOREIGN_NOTICE_RECV) { $flinks[] = clone($flink); + common_log(LOG_INFO, "sync: foreign id $flink->foreign_id"); + } else { + common_log(LOG_INFO, "nothing to sync"); } } @@ -515,31 +519,32 @@ class TwitterStatusFetcher extends ParallelizingDaemon return $id; } + /** + * Fetch a remote avatar image and save to local storage. + * + * @param string $url avatar source URL + * @param string $filename bare local filename for download + * @return bool true on success, false on failure + */ function fetchAvatar($url, $filename) { - $avatarfile = Avatar::path($filename); + common_debug($this->name() . " - Fetching Twitter avatar: $url"); - $out = fopen($avatarfile, 'wb'); - if (!$out) { - common_log(LOG_WARNING, $this->name() . - " - Couldn't open file $filename"); + $request = HTTPClient::start(); + $response = $request->get($url); + if ($response->isOk()) { + $avatarfile = Avatar::path($filename); + $ok = file_put_contents($avatarfile, $response->getBody()); + if (!$ok) { + common_log(LOG_WARNING, $this->name() . + " - Couldn't open file $filename"); + return false; + } + } else { return false; } - common_debug($this->name() . " - Fetching Twitter avatar: $url"); - - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_FILE, $out); - curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 0); - $result = curl_exec($ch); - curl_close($ch); - - fclose($out); - - return $result; + return true; } } diff --git a/plugins/TwitterBridge/twitter.php b/plugins/TwitterBridge/twitter.php index 1a5248a9b..3c6803e49 100644 --- a/plugins/TwitterBridge/twitter.php +++ b/plugins/TwitterBridge/twitter.php @@ -215,7 +215,7 @@ function broadcast_basicauth($notice, $flink) try { $status = $client->statusesUpdate($statustxt); - } catch (BasicAuthCurlException $e) { + } catch (HTTP_Request2_Exception $e) { return process_error($e, $flink); } diff --git a/plugins/TwitterBridge/twitterauthorization.php b/plugins/TwitterBridge/twitterauthorization.php index 2a93ff13e..f1daefab1 100644 --- a/plugins/TwitterBridge/twitterauthorization.php +++ b/plugins/TwitterBridge/twitterauthorization.php @@ -125,7 +125,7 @@ class TwitterauthorizationAction extends Action $auth_link = $client->getAuthorizeLink($req_tok); - } catch (TwitterOAuthClientException $e) { + } catch (OAuthClientException $e) { $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s', $e->getCode(), $e->getMessage()); $this->serverError(_('Couldn\'t link your Twitter account.')); diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php index 1040d72fb..d1cf45aec 100644 --- a/plugins/TwitterBridge/twitterbasicauthclient.php +++ b/plugins/TwitterBridge/twitterbasicauthclient.php @@ -31,26 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -/** - * Exception wrapper for cURL errors - * - * @category Integration - * @package StatusNet - * @author Adrian Lang - * @author Brenda Wallace - * @author Craig Andrews - * @author Dan Moore - * @author Evan Prodromou - * @author mEDI - * @author Sarven Capadisli - * @author Zach Copley * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - * - */ -class BasicAuthCurlException extends Exception -{ -} - /** * Class for talking to the Twitter API with HTTP Basic Auth. * @@ -198,45 +178,27 @@ class TwitterBasicAuthClient */ function httpRequest($url, $params = null, $auth = true) { - $options = array( - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FAILONERROR => true, - CURLOPT_HEADER => false, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_USERAGENT => 'StatusNet', - CURLOPT_CONNECTTIMEOUT => 120, - CURLOPT_TIMEOUT => 120, - CURLOPT_HTTPAUTH => CURLAUTH_ANY, - CURLOPT_SSL_VERIFYPEER => false, - - // Twitter is strict about accepting invalid "Expect" headers - - CURLOPT_HTTPHEADER => array('Expect:') - ); - - if (isset($params)) { - $options[CURLOPT_POST] = true; - $options[CURLOPT_POSTFIELDS] = $params; - } + $request = HTTPClient::start(); + $request->setConfig(array( + 'follow_redirects' => true, + 'connect_timeout' => 120, + 'timeout' => 120, + 'ssl_verifypeer' => false, + )); if ($auth) { - $options[CURLOPT_USERPWD] = $this->screen_name . - ':' . $this->password; + $request->setAuth($this->screen_name, $this->password); } - $ch = curl_init($url); - curl_setopt_array($ch, $options); - $response = curl_exec($ch); - - if ($response === false) { - $msg = curl_error($ch); - $code = curl_errno($ch); - throw new BasicAuthCurlException($msg, $code); + if (isset($params)) { + // Twitter is strict about accepting invalid "Expect" headers + $headers = array('Expect:'); + $response = $request->post($url, $headers, $params); + } else { + $response = $request->get($url); } - curl_close($ch); - - return $response; + return $response->getBody(); } } diff --git a/plugins/WikiHashtagsPlugin.php b/plugins/WikiHashtagsPlugin.php index 0c5649aa4..334fc13ba 100644 --- a/plugins/WikiHashtagsPlugin.php +++ b/plugins/WikiHashtagsPlugin.php @@ -68,14 +68,13 @@ class WikiHashtagsPlugin extends Plugin $editurl = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=edit', urlencode($tag)); - $context = stream_context_create(array('http' => array('method' => "GET", - 'header' => - "User-Agent: " . $this->userAgent()))); - $html = @file_get_contents($url, false, $context); + $request = HTTPClient::start(); + $response = $request->get($url); + $html = $response->getBody(); $action->elementStart('div', array('id' => 'wikihashtags', 'class' => 'section')); - if (!empty($html)) { + if ($response->isOk() && !empty($html)) { $action->element('style', null, "span.editsection { display: none }\n". "table.toc { display: none }"); @@ -100,10 +99,4 @@ class WikiHashtagsPlugin extends Plugin return true; } - - function userAgent() - { - return 'WikiHashtagsPlugin/'.WIKIHASHTAGSPLUGIN_VERSION . - ' StatusNet/' . STATUSNET_VERSION; - } } diff --git a/scripts/enjitqueuehandler.php b/scripts/enjitqueuehandler.php index 08f733b07..afcac539a 100755 --- a/scripts/enjitqueuehandler.php +++ b/scripts/enjitqueuehandler.php @@ -46,8 +46,8 @@ class EnjitQueueHandler extends QueueHandler function start() { - $this->log(LOG_INFO, "Starting EnjitQueueHandler"); - $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); + $this->log(LOG_INFO, "Starting EnjitQueueHandler"); + $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); return true; } @@ -56,16 +56,16 @@ class EnjitQueueHandler extends QueueHandler $profile = Profile::staticGet($notice->profile_id); - $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname); + $this->log(LOG_INFO, "Posting Notice ".$notice->id." from ".$profile->nickname); - if ( ! $notice->is_local ) { - $this->log(LOG_INFO, "Skipping remote notice"); - return "skipped"; - } + if ( ! $notice->is_local ) { + $this->log(LOG_INFO, "Skipping remote notice"); + return "skipped"; + } - # - # Build an Atom message from the notice - # + # + # Build an Atom message from the notice + # $noticeurl = common_local_url('shownotice', array('notice' => $notice->id)); $msg = $profile->nickname . ': ' . $notice->content; @@ -86,36 +86,18 @@ class EnjitQueueHandler extends QueueHandler $atom .= "".common_date_w3dtf($notice->modified)."\n"; $atom .= "\n"; - $url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey'); - $data = "msg=$atom"; + $url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey'); + $data = array( + 'msg' => $atom, + ); - # - # POST the message to $config['enjit']['apiurl'] - # - $ch = curl_init(); + # + # POST the message to $config['enjit']['apiurl'] + # + $request = HTTPClient::start(); + $response = $request->post($url, null, $data); - curl_setopt($ch, CURLOPT_URL, $url); - - curl_setopt($ch, CURLOPT_HEADER, 1); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($ch, CURLOPT_POST, 1) ; - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); - - # SSL and Debugging options - # - # curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); - # curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); - # curl_setopt($ch, CURLOPT_VERBOSE, 1); - - $result = curl_exec($ch); - - $code = curl_getinfo($ch, CURLINFO_HTTP_CODE ); - - $this->log(LOG_INFO, "Response Code: $code"); - - curl_close($ch); - - return $code; + return $response->isOk(); } } -- cgit v1.2.3-54-g00ecf