forked from GNUsocial/gnu-social
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.
This commit is contained in:
parent
73b9e531bf
commit
5581143bee
@ -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);
|
||||
try {
|
||||
$request = self::_commonHttp($short_url, $redirs);
|
||||
// Don't include body in output
|
||||
$request->setMethod(HTTP_Request2::METHOD_HEAD);
|
||||
$response = $request->send();
|
||||
|
||||
if (405 == $info['http_code']) {
|
||||
$curlh = File_redirection::_commonCurl($short_url, $redirs);
|
||||
curl_exec($curlh);
|
||||
$info = curl_getinfo($curlh);
|
||||
curl_close($curlh);
|
||||
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;
|
||||
}
|
||||
|
||||
|
844
extlib/HTTP/Request2.php
Normal file
844
extlib/HTTP/Request2.php
Normal file
@ -0,0 +1,844 @@
|
||||
<?php
|
||||
/**
|
||||
* Class representing a HTTP request
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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 <avb@php.net>
|
||||
* @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:
|
||||
* <ul>
|
||||
* <li> 'adapter' - adapter to use (string)</li>
|
||||
* <li> 'connect_timeout' - Connection timeout in seconds (integer)</li>
|
||||
* <li> 'timeout' - Total number of seconds a request can take.
|
||||
* Use 0 for no limit, should be greater than
|
||||
* 'connect_timeout' if set (integer)</li>
|
||||
* <li> 'use_brackets' - Whether to append [] to array variable names (bool)</li>
|
||||
* <li> 'protocol_version' - HTTP Version to use, '1.0' or '1.1' (string)</li>
|
||||
* <li> 'buffer_size' - Buffer size to use for reading and writing (int)</li>
|
||||
* <li> 'store_body' - Whether to store response body in response object.
|
||||
* Set to false if receiving a huge response and
|
||||
* using an Observer to save it (boolean)</li>
|
||||
* <li> 'proxy_host' - Proxy server host (string)</li>
|
||||
* <li> 'proxy_port' - Proxy server port (integer)</li>
|
||||
* <li> 'proxy_user' - Proxy auth username (string)</li>
|
||||
* <li> 'proxy_password' - Proxy auth password (string)</li>
|
||||
* <li> 'proxy_auth_scheme' - Proxy auth scheme, one of HTTP_Request2::AUTH_* constants (string)</li>
|
||||
* <li> 'ssl_verify_peer' - Whether to verify peer's SSL certificate (bool)</li>
|
||||
* <li> 'ssl_verify_host' - Whether to check that Common Name in SSL
|
||||
* certificate matches host name (bool)</li>
|
||||
* <li> 'ssl_cafile' - Cerificate Authority file to verify the peer
|
||||
* with (use with 'ssl_verify_peer') (string)</li>
|
||||
* <li> 'ssl_capath' - Directory holding multiple Certificate
|
||||
* Authority files (string)</li>
|
||||
* <li> 'ssl_local_cert' - Name of a file containing local cerificate (string)</li>
|
||||
* <li> 'ssl_passphrase' - Passphrase with which local certificate
|
||||
* was encoded (string)</li>
|
||||
* <li> 'digest_compat_ie' - Whether to imitate behaviour of MSIE 5 and 6
|
||||
* in using URL without query string in digest
|
||||
* authentication (boolean)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param string|array configuration parameter name or array
|
||||
* ('parameter name' => 'parameter value')
|
||||
* @param mixed parameter value if $nameOrConfig is not an array
|
||||
* @return HTTP_Request2
|
||||
* @throws HTTP_Request2_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.
|
||||
*
|
||||
* <code>
|
||||
* $req->setHeader('Foo: Bar'); // sets the value of 'Foo' header to 'Bar'
|
||||
* $req->setHeader('FoO', 'Baz'); // sets the value of 'Foo' header to 'Baz'
|
||||
* $req->setHeader(array('foo' => 'Quux')); // sets the value of 'Foo' header to 'Quux'
|
||||
* $req->setHeader('FOO'); // removes 'Foo' header from request
|
||||
* </code>
|
||||
*
|
||||
* @param string|array header name, header string ('Header: value')
|
||||
* or an array of headers
|
||||
* @param string|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:
|
||||
* <ul>
|
||||
* <li>'connect' - after connection to remote server,
|
||||
* data is the destination (string)</li>
|
||||
* <li>'disconnect' - after disconnection from server</li>
|
||||
* <li>'sentHeaders' - after sending the request headers,
|
||||
* data is the headers sent (string)</li>
|
||||
* <li>'sentBodyPart' - after sending a part of the request body,
|
||||
* data is the length of that part (int)</li>
|
||||
* <li>'receivedHeaders' - after receiving the response headers,
|
||||
* data is HTTP_Request2_Response object</li>
|
||||
* <li>'receivedBodyPart' - after receiving a part of the response
|
||||
* body, data is that part (string)</li>
|
||||
* <li>'receivedEncodedBodyPart' - as 'receivedBodyPart', but data is still
|
||||
* encoded by Content-Encoding</li>
|
||||
* <li>'receivedBody' - after receiving the complete response
|
||||
* body, data is HTTP_Request2_Response object</li>
|
||||
* </ul>
|
||||
* Different adapters may not send all the event types. Mock adapter does
|
||||
* not send any events to the observers.
|
||||
*
|
||||
* @return array The array has two keys: 'name' and 'data'
|
||||
*/
|
||||
public function getLastEvent()
|
||||
{
|
||||
return $this->lastEvent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the adapter used to actually perform the request
|
||||
*
|
||||
* You can pass either an instance of a class implementing HTTP_Request2_Adapter
|
||||
* or a class name. The method will only try to include a file if the class
|
||||
* name starts with HTTP_Request2_Adapter_, it will also try to prepend this
|
||||
* prefix to the class name if it doesn't contain any underscores, so that
|
||||
* <code>
|
||||
* $request->setAdapter('curl');
|
||||
* </code>
|
||||
* will work.
|
||||
*
|
||||
* @param string|HTTP_Request2_Adapter
|
||||
* @return HTTP_Request2
|
||||
* @throws HTTP_Request2_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;
|
||||
}
|
||||
}
|
||||
?>
|
152
extlib/HTTP/Request2/Adapter.php
Normal file
152
extlib/HTTP/Request2/Adapter.php
Normal file
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Base class for HTTP_Request2 adapters
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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 <avb@php.net>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
383
extlib/HTTP/Request2/Adapter/Curl.php
Normal file
383
extlib/HTTP/Request2/Adapter/Curl.php
Normal file
@ -0,0 +1,383 @@
|
||||
<?php
|
||||
/**
|
||||
* Adapter for HTTP_Request2 wrapping around cURL extension
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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 <avb@php.net>
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
?>
|
171
extlib/HTTP/Request2/Adapter/Mock.php
Normal file
171
extlib/HTTP/Request2/Adapter/Mock.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* Mock adapter intended for testing
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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()
|
||||
* <code>
|
||||
* $mock = new HTTP_Request2_Adapter_Mock();
|
||||
* $mock->addResponse("HTTP/1.1 ... ");
|
||||
*
|
||||
* $request = new HTTP_Request2();
|
||||
* $request->setAdapter($mock);
|
||||
*
|
||||
* // This will return the response set above
|
||||
* $response = $req->send();
|
||||
* </code>
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @version Release: 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;
|
||||
}
|
||||
}
|
||||
?>
|
971
extlib/HTTP/Request2/Adapter/Socket.php
Normal file
971
extlib/HTTP/Request2/Adapter/Socket.php
Normal file
@ -0,0 +1,971 @@
|
||||
<?php
|
||||
/**
|
||||
* Socket-based adapter for HTTP_Request2
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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 <avb@php.net>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
62
extlib/HTTP/Request2/Exception.php
Normal file
62
extlib/HTTP/Request2/Exception.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* Exception class for HTTP_Request2 package
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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
|
||||
{
|
||||
}
|
||||
?>
|
274
extlib/HTTP/Request2/MultipartBody.php
Normal file
274
extlib/HTTP/Request2/MultipartBody.php
Normal file
@ -0,0 +1,274 @@
|
||||
<?php
|
||||
/**
|
||||
* Helper class for building multipart/form-data request body
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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 <avb@php.net>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
215
extlib/HTTP/Request2/Observer/Log.php
Normal file
215
extlib/HTTP/Request2/Observer/Log.php
Normal file
@ -0,0 +1,215 @@
|
||||
<?php
|
||||
/**
|
||||
* An observer useful for debugging / testing.
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author David Jean Louis <izi@php.net>
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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:
|
||||
* <code>
|
||||
* require_once 'HTTP/Request2.php';
|
||||
* require_once 'HTTP/Request2/Observer/Log.php';
|
||||
*
|
||||
* $request = new HTTP_Request2('http://www.example.com');
|
||||
* $observer = new HTTP_Request2_Observer_Log();
|
||||
* $request->attach($observer);
|
||||
* $request->send();
|
||||
* </code>
|
||||
*
|
||||
* A more complex example with PEAR Log:
|
||||
* <code>
|
||||
* require_once 'HTTP/Request2.php';
|
||||
* require_once 'HTTP/Request2/Observer/Log.php';
|
||||
* require_once 'Log.php';
|
||||
*
|
||||
* $request = new HTTP_Request2('http://www.example.com');
|
||||
* // we want to log with PEAR log
|
||||
* $observer = new HTTP_Request2_Observer_Log(Log::factory('console'));
|
||||
*
|
||||
* // we only want to log received headers
|
||||
* $observer->events = array('receivedHeaders');
|
||||
*
|
||||
* $request->attach($observer);
|
||||
* $request->send();
|
||||
* </code>
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author David Jean Louis <izi@php.net>
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version Release: 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");
|
||||
}
|
||||
}
|
||||
|
||||
// }}}
|
||||
}
|
||||
|
||||
?>
|
549
extlib/HTTP/Request2/Response.php
Normal file
549
extlib/HTTP/Request2/Response.php
Normal file
@ -0,0 +1,549 @@
|
||||
<?php
|
||||
/**
|
||||
* Class representing a HTTP response
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENSE:
|
||||
*
|
||||
* Copyright (c) 2008, 2009, Alexey Borzov <avb@php.net>
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * The names of the authors may not be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
|
||||
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @license http://opensource.org/licenses/bsd-license.php New BSD License
|
||||
* @version 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:
|
||||
* <code>
|
||||
* $statusLine = read_status_line();
|
||||
* $response = new HTTP_Request2_Response($statusLine);
|
||||
* do {
|
||||
* $headerLine = read_header_line();
|
||||
* $response->parseHeaderLine($headerLine);
|
||||
* } while ($headerLine != '');
|
||||
*
|
||||
* while ($chunk = read_body()) {
|
||||
* $response->appendBody($chunk);
|
||||
* }
|
||||
*
|
||||
* var_dump($response->getHeader(), $response->getCookies(), $response->getBody());
|
||||
* </code>
|
||||
*
|
||||
*
|
||||
* @category HTTP
|
||||
* @package HTTP_Request2
|
||||
* @author Alexey Borzov <avb@php.net>
|
||||
* @version Release: 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);
|
||||
}
|
||||
}
|
||||
?>
|
@ -1,44 +1,58 @@
|
||||
<?php
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | Copyright (c) 2007-2008, Christian Schmidt, 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: |
|
||||
// | |
|
||||
// | o Redistributions of source code must retain the above copyright |
|
||||
// | notice, this list of conditions and the following disclaimer. |
|
||||
// | o 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.|
|
||||
// | o 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. |
|
||||
// | |
|
||||
// +-----------------------------------------------------------------------+
|
||||
// | Author: Christian Schmidt <schmidt at php dot net> |
|
||||
// +-----------------------------------------------------------------------+
|
||||
//
|
||||
// $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 <chsc@peytz.dk>
|
||||
* @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 <chsc@peytz.dk>
|
||||
* @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;
|
||||
}
|
||||
}
|
||||
|
@ -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',
|
||||
|
@ -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) {
|
||||
|
@ -1,179 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* StatusNet, the distributed open-source microblogging tool
|
||||
*
|
||||
* Utility class for wrapping Curl
|
||||
*
|
||||
* PHP version 5
|
||||
*
|
||||
* LICENCE: This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @category HTTP
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @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 <evan@status.net>
|
||||
* @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;
|
||||
}
|
||||
}
|
@ -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
|
||||
);
|
||||
|
@ -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 <evan@status.net>
|
||||
* @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 <evan@status.net>
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @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 <evan@status.net>
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @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;
|
||||
$this->config['max_redirs'] = 10;
|
||||
$this->config['follow_redirects'] = true;
|
||||
parent::__construct($url, $method, $config);
|
||||
$this->setHeader('User-Agent', $this->userAgent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience/back-compat instantiator
|
||||
* @return HTTPClient
|
||||
*/
|
||||
public static function start()
|
||||
{
|
||||
return new HTTPClient();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to run a GET request.
|
||||
*
|
||||
* @return HTTPResponse
|
||||
* @throws HTTP_Request2_Exception
|
||||
*/
|
||||
public function get($url, $headers=array())
|
||||
{
|
||||
return $this->doRequest($url, self::METHOD_GET, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function to run a HEAD request.
|
||||
*
|
||||
* @return HTTPResponse
|
||||
* @throws HTTP_Request2_Exception
|
||||
*/
|
||||
public function head($url, $headers=array())
|
||||
{
|
||||
return $this->doRequest($url, self::METHOD_HEAD, $headers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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())
|
||||
{
|
||||
if ($data) {
|
||||
$this->addPostParameter($data);
|
||||
}
|
||||
return $this->doRequest($url, self::METHOD_POST, $headers);
|
||||
}
|
||||
|
||||
$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 HTTPResponse
|
||||
* @throws HTTP_Request2_Exception
|
||||
*/
|
||||
protected function doRequest($url, $method, $headers)
|
||||
{
|
||||
$this->setUrl($url);
|
||||
$this->setMethod($method);
|
||||
if ($headers) {
|
||||
foreach ($headers as $header) {
|
||||
$this->setHeader($header);
|
||||
}
|
||||
}
|
||||
|
||||
return self::$_client;
|
||||
$response = $this->send();
|
||||
return $response;
|
||||
}
|
||||
|
||||
function head($url, $headers)
|
||||
{
|
||||
throw new Exception("HEAD method unimplemented");
|
||||
}
|
||||
|
||||
function get($url, $headers)
|
||||
{
|
||||
throw new Exception("GET method unimplemented");
|
||||
}
|
||||
|
||||
function post($url, $headers, $body)
|
||||
{
|
||||
throw new Exception("POST method unimplemented");
|
||||
}
|
||||
|
||||
function put($url, $headers, $body)
|
||||
{
|
||||
throw new Exception("PUT method unimplemented");
|
||||
}
|
||||
|
||||
function delete($url, $headers)
|
||||
{
|
||||
throw new Exception("DELETE method unimplemented");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
$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
|
||||
|
||||
CURLOPT_HTTPHEADER => array('Expect:')
|
||||
);
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
12
lib/ping.php
12
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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,6 +22,7 @@
|
||||
* @category Plugin
|
||||
* @package StatusNet
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author Brion Vibber <brion@status.net>
|
||||
* @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 {
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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)");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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.'));
|
||||
|
@ -31,26 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception wrapper for cURL errors
|
||||
*
|
||||
* @category Integration
|
||||
* @package StatusNet
|
||||
* @author Adrian Lang <mail@adrianlang.de>
|
||||
* @author Brenda Wallace <shiny@cpan.org>
|
||||
* @author Craig Andrews <candrews@integralblue.com>
|
||||
* @author Dan Moore <dan@moore.cx>
|
||||
* @author Evan Prodromou <evan@status.net>
|
||||
* @author mEDI <medi@milaro.net>
|
||||
* @author Sarven Capadisli <csarven@status.net>
|
||||
* @author Zach Copley <zach@status.net> * @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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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 .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
|
||||
$atom .= "</entry>\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();
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user