Rebuilt HTTPClient class as an extension of PEAR HTTP_Request2 package, adding redirect handling and convenience functions.

Caching support will be added in future work after unit tests have been added.

* extlib: add PEAR HTTP_Request2 0.4.1 alpha
* extlib: update PEAR Net_URL2 to 0.3.0 beta for HTTP_Request2 compatibility
* moved direct usage of CURL and file_get_contents to HTTPClient class, excluding external-sourced libraries

Note some plugins haven't been tested yet.
This commit is contained in:
Brion Vibber 2009-10-28 15:29:20 -04:00
parent 8e64723813
commit fa37967858
29 changed files with 4245 additions and 673 deletions

View File

@ -47,18 +47,15 @@ class File_redirection extends Memcached_DataObject
/* the code above is auto generated do not remove the tag below */ /* the code above is auto generated do not remove the tag below */
###END_AUTOCODE ###END_AUTOCODE
function _commonCurl($url, $redirs) { static function _commonHttp($url, $redirs) {
$curlh = curl_init(); $request = new HTTPClient($url);
curl_setopt($curlh, CURLOPT_URL, $url); $request->setConfig(array(
curl_setopt($curlh, CURLOPT_AUTOREFERER, true); // # setup referer header when folowing redirects 'connect_timeout' => 10, // # seconds to wait
curl_setopt($curlh, CURLOPT_CONNECTTIMEOUT, 10); // # seconds to wait 'max_redirs' => $redirs, // # max number of http redirections to follow
curl_setopt($curlh, CURLOPT_MAXREDIRS, $redirs); // # max number of http redirections to follow 'follow_redirects' => true, // Follow redirects
curl_setopt($curlh, CURLOPT_USERAGENT, USER_AGENT); 'store_body' => false, // We won't need body content here.
curl_setopt($curlh, CURLOPT_FOLLOWLOCATION, true); // Follow redirects ));
curl_setopt($curlh, CURLOPT_RETURNTRANSFER, true); return $request;
curl_setopt($curlh, CURLOPT_FILETIME, true);
curl_setopt($curlh, CURLOPT_HEADER, true); // Include header in output
return $curlh;
} }
function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) { function _redirectWhere_imp($short_url, $redirs = 10, $protected = false) {
@ -82,32 +79,39 @@ class File_redirection extends Memcached_DataObject
if(strpos($short_url,'://') === false){ if(strpos($short_url,'://') === false){
return $short_url; return $short_url;
} }
$curlh = File_redirection::_commonCurl($short_url, $redirs); try {
// Don't include body in output $request = self::_commonHttp($short_url, $redirs);
curl_setopt($curlh, CURLOPT_NOBODY, true); // Don't include body in output
curl_exec($curlh); $request->setMethod(HTTP_Request2::METHOD_HEAD);
$info = curl_getinfo($curlh); $response = $request->send();
curl_close($curlh);
if (405 == $info['http_code']) { if (405 == $response->getCode()) {
$curlh = File_redirection::_commonCurl($short_url, $redirs); // Server doesn't support HEAD method? Can this really happen?
curl_exec($curlh); // We'll try again as a GET and ignore the response data.
$info = curl_getinfo($curlh); $request = self::_commonHttp($short_url, $redirs);
curl_close($curlh); $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'])) { if ($response->getRedirectCount() && File::isProtected($response->getUrl())) {
return File_redirection::_redirectWhere_imp($short_url, $info['redirect_count'] - 1, true); // 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'] $ret = array('code' => $response->getCode()
, 'redirects' => $info['redirect_count'] , 'redirects' => $response->getRedirectCount()
, 'url' => $info['url']); , '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 ($protected) $ret['protected'] = true;
if (!empty($info['download_content_length'])) $ret['size'] = $info['download_content_length']; $size = $request->getHeader('Content-Length'); // @fixme bytes?
if (isset($info['filetime']) && ($info['filetime'] > 0)) $ret['time'] = $info['filetime']; if ($size) $ret['size'] = $size;
$time = $request->getHeader('Last-Modified');
if ($time) $ret['time'] = strtotime($time);
return $ret; return $ret;
} }

844
extlib/HTTP/Request2.php Normal file
View 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;
}
}
?>

View 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;
}
}
}
?>

View 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);
}
}
?>

View 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;
}
}
?>

View 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;
}
}
?>

View 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
{
}
?>

View 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;
}
}
}
?>

View 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");
}
}
// }}}
}
?>

View 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);
}
}
?>

View File

@ -1,44 +1,58 @@
<?php <?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 class Net_URL2
{ {
@ -46,24 +60,24 @@ class Net_URL2
* Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default * Do strict parsing in resolve() (see RFC 3986, section 5.2.2). Default
* is true. * is true.
*/ */
const OPTION_STRICT = 'strict'; const OPTION_STRICT = 'strict';
/** /**
* Represent arrays in query using PHP's [] notation. Default is true. * 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. * 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 * Query variable separators when parsing the query string. Every character
* is considered a separator. Default is specified by the * is considered a separator. Default is specified by the
* arg_separator.input php.ini setting (this defaults to "&"). * 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 * 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. * Default options corresponds to how PHP handles $_GET.
*/ */
private $options = array( private $_options = array(
self::OPTION_STRICT => true, self::OPTION_STRICT => true,
self::OPTION_USE_BRACKETS => true, self::OPTION_USE_BRACKETS => true,
self::OPTION_ENCODE_KEYS => true, self::OPTION_ENCODE_KEYS => true,
@ -86,41 +100,43 @@ class Net_URL2
/** /**
* @var string|bool * @var string|bool
*/ */
private $scheme = false; private $_scheme = false;
/** /**
* @var string|bool * @var string|bool
*/ */
private $userinfo = false; private $_userinfo = false;
/** /**
* @var string|bool * @var string|bool
*/ */
private $host = false; private $_host = false;
/** /**
* @var int|bool * @var int|bool
*/ */
private $port = false; private $_port = false;
/** /**
* @var string * @var string
*/ */
private $path = ''; private $_path = '';
/** /**
* @var string|bool * @var string|bool
*/ */
private $query = false; private $_query = false;
/** /**
* @var string|bool * @var string|bool
*/ */
private $fragment = false; private $_fragment = false;
/** /**
* Constructor.
*
* @param string $url an absolute or relative URL * @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) public function __construct($url, $options = null)
{ {
@ -130,12 +146,12 @@ class Net_URL2
ini_get('arg_separator.output')); ini_get('arg_separator.output'));
if (is_array($options)) { if (is_array($options)) {
foreach ($options as $optionName => $value) { foreach ($options as $optionName => $value) {
$this->setOption($optionName); $this->setOption($optionName, $value);
} }
} }
if (preg_match('@^([a-z][a-z0-9.+-]*):@i', $url, $reg)) { 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])); $url = substr($url, strlen($reg[0]));
} }
@ -145,19 +161,58 @@ class Net_URL2
} }
$i = strcspn($url, '?#'); $i = strcspn($url, '?#');
$this->path = substr($url, 0, $i); $this->_path = substr($url, 0, $i);
$url = substr($url, $i); $url = substr($url, $i);
if (preg_match('@^\?([^#]*)@', $url, $reg)) { if (preg_match('@^\?([^#]*)@', $url, $reg)) {
$this->query = $reg[1]; $this->_query = $reg[1];
$url = substr($url, strlen($reg[0])); $url = substr($url, strlen($reg[0]));
} }
if ($url) { 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 * Returns the scheme, e.g. "http" or "urn", or false if there is no
* scheme specified, i.e. if this is a relative URL. * scheme specified, i.e. if this is a relative URL.
@ -166,18 +221,23 @@ class Net_URL2
*/ */
public function getScheme() 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 * @return void
* @see getScheme() * @see getScheme()
*/ */
public function setScheme($scheme) public function setScheme($scheme)
{ {
$this->scheme = $scheme; $this->_scheme = $scheme;
} }
/** /**
@ -188,7 +248,9 @@ class Net_URL2
*/ */
public function getUser() 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() 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() public function getUserinfo()
{ {
return $this->userinfo; return $this->_userinfo;
} }
/** /**
@ -220,15 +284,15 @@ class Net_URL2
* in the userinfo part as username ":" password. * in the userinfo part as username ":" password.
* *
* @param string|bool $userinfo userinfo or username * @param string|bool $userinfo userinfo or username
* @param string|bool $password * @param string|bool $password optional password, or false
* *
* @return void * @return void
*/ */
public function setUserinfo($userinfo, $password = false) public function setUserinfo($userinfo, $password = false)
{ {
$this->userinfo = $userinfo; $this->_userinfo = $userinfo;
if ($password !== false) { 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. * Returns the host part, or false if there is no authority part, e.g.
* relative URLs. * relative URLs.
* *
* @return string|bool * @return string|bool a hostname, an IP address, or false
*/ */
public function getHost() 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 * @return void
*/ */
public function setHost($host) public function setHost($host)
{ {
$this->host = $host; $this->_host = $host;
} }
/** /**
@ -261,65 +328,72 @@ class Net_URL2
*/ */
public function getPort() 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 * @return void
*/ */
public function setPort($port) public function setPort($port)
{ {
$this->port = intval($port); $this->_port = intval($port);
} }
/** /**
* Returns the authority part, i.e. [ userinfo "@" ] host [ ":" port ], or * 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 * @return string|bool
*/ */
public function getAuthority() public function getAuthority()
{ {
if (!$this->host) { if (!$this->_host) {
return false; return false;
} }
$authority = ''; $authority = '';
if ($this->userinfo !== false) { if ($this->_userinfo !== false) {
$authority .= $this->userinfo . '@'; $authority .= $this->_userinfo . '@';
} }
$authority .= $this->host; $authority .= $this->_host;
if ($this->port !== false) { if ($this->_port !== false) {
$authority .= ':' . $this->port; $authority .= ':' . $this->_port;
} }
return $authority; 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 * @return void
*/ */
public function setAuthority($authority) public function setAuthority($authority)
{ {
$this->user = false; $this->_userinfo = false;
$this->pass = false; $this->_host = false;
$this->host = false; $this->_port = false;
$this->port = false; if (preg_match('@^(([^\@]*)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
if (preg_match('@^(([^\@]+)\@)?([^:]+)(:(\d*))?$@', $authority, $reg)) {
if ($reg[1]) { if ($reg[1]) {
$this->userinfo = $reg[2]; $this->_userinfo = $reg[2];
} }
$this->host = $reg[3]; $this->_host = $reg[3];
if (isset($reg[5])) { if (isset($reg[5])) {
$this->port = intval($reg[5]); $this->_port = intval($reg[5]);
} }
} }
} }
@ -331,65 +405,74 @@ class Net_URL2
*/ */
public function getPath() 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 * @return void
*/ */
public function setPath($path) public function setPath($path)
{ {
$this->path = $path; $this->_path = $path;
} }
/** /**
* Returns the query string (excluding the leading "?"), or false if "?" * 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 * @return string|bool
* @see self::getQueryVariables() * @see self::getQueryVariables()
*/ */
public function getQuery() 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 * @return void
* @see self::setQueryVariables() * @see self::setQueryVariables()
*/ */
public function setQuery($query) 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 * @return string|bool
*/ */
public function getFragment() 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 * @return void
*/ */
public function setFragment($fragment) public function setFragment($fragment)
{ {
$this->fragment = $fragment; $this->_fragment = $fragment;
} }
/** /**
* Returns the query string like an array as the variables would appear in * 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 * @return array
*/ */
@ -398,7 +481,7 @@ class Net_URL2
$pattern = '/[' . $pattern = '/[' .
preg_quote($this->getOption(self::OPTION_SEPARATOR_INPUT), '/') . 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(); $return = array();
foreach ($parts as $part) { 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 * @param array $array (name => value) array
* *
* @return void * @return void
@ -452,11 +537,11 @@ class Net_URL2
public function setQueryVariables(array $array) public function setQueryVariables(array $array)
{ {
if (!$array) { if (!$array) {
$this->query = false; $this->_query = false;
} else { } else {
foreach ($array as $name => $value) { foreach ($array as $name => $value) {
if ($this->getOption(self::OPTION_ENCODE_KEYS)) { if ($this->getOption(self::OPTION_ENCODE_KEYS)) {
$name = rawurlencode($name); $name = self::urlencode($name);
} }
if (is_array($value)) { if (is_array($value)) {
@ -466,19 +551,21 @@ class Net_URL2
: ($name . '=' . $v); : ($name . '=' . $v);
} }
} elseif (!is_null($value)) { } elseif (!is_null($value)) {
$parts[] = $name . '=' . $value; $parts[] = $name . '=' . self::urlencode($value);
} else { } else {
$parts[] = $name; $parts[] = $name;
} }
} }
$this->query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT), $this->_query = implode($this->getOption(self::OPTION_SEPARATOR_OUTPUT),
$parts); $parts);
} }
} }
/** /**
* @param string $name * Sets the specified variable in the query string.
* @param mixed $value *
* @param string $name variable name
* @param mixed $value variable value
* *
* @return array * @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 * @return void
*/ */
@ -511,27 +600,38 @@ class Net_URL2
// See RFC 3986, section 5.3 // See RFC 3986, section 5.3
$url = ""; $url = "";
if ($this->scheme !== false) { if ($this->_scheme !== false) {
$url .= $this->scheme . ':'; $url .= $this->_scheme . ':';
} }
$authority = $this->getAuthority(); $authority = $this->getAuthority();
if ($authority !== false) { if ($authority !== false) {
$url .= '//' . $authority; $url .= '//' . $authority;
} }
$url .= $this->path; $url .= $this->_path;
if ($this->query !== false) { if ($this->_query !== false) {
$url .= '?' . $this->query; $url .= '?' . $this->_query;
} }
if ($this->fragment !== false) { if ($this->_fragment !== false) {
$url .= '#' . $this->fragment; $url .= '#' . $this->_fragment;
} }
return $url; 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 * Returns a normalized string representation of this URL. This is useful
* for comparison of URLs. * for comparison of URLs.
@ -555,36 +655,38 @@ class Net_URL2
// See RFC 3886, section 6 // See RFC 3886, section 6
// Schemes are case-insensitive // Schemes are case-insensitive
if ($this->scheme) { if ($this->_scheme) {
$this->scheme = strtolower($this->scheme); $this->_scheme = strtolower($this->_scheme);
} }
// Hostnames are case-insensitive // Hostnames are case-insensitive
if ($this->host) { if ($this->_host) {
$this->host = strtolower($this->host); $this->_host = strtolower($this->_host);
} }
// Remove default port number for known schemes (RFC 3986, section 6.2.3) // Remove default port number for known schemes (RFC 3986, section 6.2.3)
if ($this->port && if ($this->_port &&
$this->scheme && $this->_scheme &&
$this->port == getservbyname($this->scheme, 'tcp')) { $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) // 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) { 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) // 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) // Scheme based normalization (RFC 3986, section 6.2.3)
if ($this->host && !$this->path) { if ($this->_host && !$this->_path) {
$this->path = '/'; $this->_path = '/';
} }
} }
@ -595,7 +697,7 @@ class Net_URL2
*/ */
public function isAbsolute() public function isAbsolute()
{ {
return (bool) $this->scheme; return (bool) $this->_scheme;
} }
/** /**
@ -608,7 +710,7 @@ class Net_URL2
*/ */
public function resolve($reference) public function resolve($reference)
{ {
if (is_string($reference)) { if (!$reference instanceof Net_URL2) {
$reference = new self($reference); $reference = new self($reference);
} }
if (!$this->isAbsolute()) { if (!$this->isAbsolute()) {
@ -617,54 +719,54 @@ class Net_URL2
// A non-strict parser may ignore a scheme in the reference if it is // A non-strict parser may ignore a scheme in the reference if it is
// identical to the base URI's scheme. // identical to the base URI's scheme.
if (!$this->getOption(self::OPTION_STRICT) && $reference->scheme == $this->scheme) { if (!$this->getOption(self::OPTION_STRICT) && $reference->_scheme == $this->_scheme) {
$reference->scheme = false; $reference->_scheme = false;
} }
$target = new self(''); $target = new self('');
if ($reference->scheme !== false) { if ($reference->_scheme !== false) {
$target->scheme = $reference->scheme; $target->_scheme = $reference->_scheme;
$target->setAuthority($reference->getAuthority()); $target->setAuthority($reference->getAuthority());
$target->path = self::removeDotSegments($reference->path); $target->_path = self::removeDotSegments($reference->_path);
$target->query = $reference->query; $target->_query = $reference->_query;
} else { } else {
$authority = $reference->getAuthority(); $authority = $reference->getAuthority();
if ($authority !== false) { if ($authority !== false) {
$target->setAuthority($authority); $target->setAuthority($authority);
$target->path = self::removeDotSegments($reference->path); $target->_path = self::removeDotSegments($reference->_path);
$target->query = $reference->query; $target->_query = $reference->_query;
} else { } else {
if ($reference->path == '') { if ($reference->_path == '') {
$target->path = $this->path; $target->_path = $this->_path;
if ($reference->query !== false) { if ($reference->_query !== false) {
$target->query = $reference->query; $target->_query = $reference->_query;
} else { } else {
$target->query = $this->query; $target->_query = $this->_query;
} }
} else { } else {
if (substr($reference->path, 0, 1) == '/') { if (substr($reference->_path, 0, 1) == '/') {
$target->path = self::removeDotSegments($reference->path); $target->_path = self::removeDotSegments($reference->_path);
} else { } else {
// Merge paths (RFC 3986, section 5.2.3) // Merge paths (RFC 3986, section 5.2.3)
if ($this->host !== false && $this->path == '') { if ($this->_host !== false && $this->_path == '') {
$target->path = '/' . $this->path; $target->_path = '/' . $this->_path;
} else { } else {
$i = strrpos($this->path, '/'); $i = strrpos($this->_path, '/');
if ($i !== false) { 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->setAuthority($this->getAuthority());
} }
$target->scheme = $this->scheme; $target->_scheme = $this->_scheme;
} }
$target->fragment = $reference->fragment; $target->_fragment = $reference->_fragment;
return $target; return $target;
} }
@ -677,7 +779,7 @@ class Net_URL2
* *
* @return string a path * @return string a path
*/ */
private static function removeDotSegments($path) public static function removeDotSegments($path)
{ {
$output = ''; $output = '';
@ -685,28 +787,25 @@ class Net_URL2
// method // method
$j = 0; $j = 0;
while ($path && $j++ < 100) { while ($path && $j++ < 100) {
// Step A
if (substr($path, 0, 2) == './') { if (substr($path, 0, 2) == './') {
// Step 2.A
$path = substr($path, 2); $path = substr($path, 2);
} elseif (substr($path, 0, 3) == '../') { } elseif (substr($path, 0, 3) == '../') {
// Step 2.A
$path = substr($path, 3); $path = substr($path, 3);
// Step B
} elseif (substr($path, 0, 3) == '/./' || $path == '/.') { } elseif (substr($path, 0, 3) == '/./' || $path == '/.') {
// Step 2.B
$path = '/' . substr($path, 3); $path = '/' . substr($path, 3);
// Step C
} elseif (substr($path, 0, 4) == '/../' || $path == '/..') { } elseif (substr($path, 0, 4) == '/../' || $path == '/..') {
$path = '/' . substr($path, 4); // Step 2.C
$i = strrpos($output, '/'); $path = '/' . substr($path, 4);
$i = strrpos($output, '/');
$output = $i === false ? '' : substr($output, 0, $i); $output = $i === false ? '' : substr($output, 0, $i);
// Step D
} elseif ($path == '.' || $path == '..') { } elseif ($path == '.' || $path == '..') {
// Step 2.D
$path = ''; $path = '';
// Step E
} else { } else {
// Step 2.E
$i = strpos($path, '/'); $i = strpos($path, '/');
if ($i === 0) { if ($i === 0) {
$i = strpos($path, '/', 1); $i = strpos($path, '/', 1);
@ -722,6 +821,22 @@ class Net_URL2
return $output; 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 * Returns a Net_URL2 instance representing the canonical URL of the
* currently executing PHP script. * currently executing PHP script.
@ -737,13 +852,13 @@ class Net_URL2
// Begin with a relative URL // Begin with a relative URL
$url = new self($_SERVER['PHP_SELF']); $url = new self($_SERVER['PHP_SELF']);
$url->scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http'; $url->_scheme = isset($_SERVER['HTTPS']) ? 'https' : 'http';
$url->host = $_SERVER['SERVER_NAME']; $url->_host = $_SERVER['SERVER_NAME'];
$port = intval($_SERVER['SERVER_PORT']); $port = intval($_SERVER['SERVER_PORT']);
if ($url->scheme == 'http' && $port != 80 || if ($url->_scheme == 'http' && $port != 80 ||
$url->scheme == 'https' && $port != 443) { $url->_scheme == 'https' && $port != 443) {
$url->port = $port; $url->_port = $port;
} }
return $url; return $url;
} }
@ -773,7 +888,7 @@ class Net_URL2
// Begin with a relative URL // Begin with a relative URL
$url = new self($_SERVER['REQUEST_URI']); $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 // Set host and possibly port
$url->setAuthority($_SERVER['HTTP_HOST']); $url->setAuthority($_SERVER['HTTP_HOST']);
return $url; return $url;
@ -792,10 +907,10 @@ class Net_URL2
*/ */
function setOption($optionName, $value) function setOption($optionName, $value)
{ {
if (!array_key_exists($optionName, $this->options)) { if (!array_key_exists($optionName, $this->_options)) {
return false; return false;
} }
$this->options[$optionName] = $value; $this->_options[$optionName] = $value;
} }
/** /**
@ -807,7 +922,7 @@ class Net_URL2
*/ */
function getOption($optionName) function getOption($optionName)
{ {
return isset($this->options[$optionName]) return isset($this->_options[$optionName])
? $this->options[$optionName] : false; ? $this->_options[$optionName] : false;
} }
} }

View File

@ -93,6 +93,13 @@ $external_libraries=array(
'include'=>'HTTP/Request.php', 'include'=>'HTTP/Request.php',
'check_class'=>'HTTP_Request' '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( array(
'name'=>'Mail', 'name'=>'Mail',
'pear'=>'Mail', 'pear'=>'Mail',

View File

@ -41,22 +41,17 @@ abstract class ShortUrlApi
return strlen($url) >= common_config('site', 'shorturllength'); return strlen($url) >= common_config('site', 'shorturllength');
} }
protected function http_post($data) { protected function http_post($data)
$ch = curl_init(); {
curl_setopt($ch, CURLOPT_URL, $this->service_url); $request = new HTTPClient($this->service_url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); return $request->post($data);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
$response = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if (($code < 200) || ($code >= 400)) return false;
return $response;
} }
protected function http_get($url) { protected function http_get($url)
$encoded_url = urlencode($url); {
return file_get_contents("{$this->service_url}$encoded_url"); $service = $this->service_url . urlencode($url);
$request = new HTTPClient($service);
return $request->get();
} }
protected function tidy($response) { protected function tidy($response) {

View File

@ -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;
}
}

View File

@ -228,8 +228,6 @@ $default =
array('contentlimit' => null), array('contentlimit' => null),
'message' => 'message' =>
array('contentlimit' => null), array('contentlimit' => null),
'http' =>
array('client' => 'curl'), // XXX: should this be the default?
'location' => 'location' =>
array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth array('namespace' => 1), // 1 = geonames, 2 = Yahoo Where on Earth
); );

View File

@ -31,6 +31,9 @@ if (!defined('STATUSNET')) {
exit(1); exit(1);
} }
require_once 'HTTP/Request2.php';
require_once 'HTTP/Request2/Response.php';
/** /**
* Useful structure for HTTP responses * Useful structure for HTTP responses
* *
@ -38,18 +41,42 @@ if (!defined('STATUSNET')) {
* ways of doing them. This class hides the specifics of what underlying * ways of doing them. This class hides the specifics of what underlying
* library (curl or PHP-HTTP or whatever) that's used. * 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 * @category HTTP
* @package StatusNet * @package StatusNet
* @author Evan Prodromou <evan@status.net> * @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 * @author Brion Vibber <brion@status.net>
* @link http://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 extends HTTP_Request2_Response
class HTTPResponse
{ {
public $code = null; function __construct(HTTP_Request2_Response $response, $url, $redirects=0)
public $headers = array(); {
public $body = null; 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;
}
} }
/** /**
@ -59,64 +86,133 @@ class HTTPResponse
* ways of doing them. This class hides the specifics of what underlying * ways of doing them. This class hides the specifics of what underlying
* library (curl or PHP-HTTP or whatever) that's used. * 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 * @category HTTP
* @package StatusNet * @package StatusNet
* @author Evan Prodromou <evan@status.net> * @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 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @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)) { $this->config['max_redirs'] = 10;
return self::$_client; $this->config['follow_redirects'] = false;
parent::__construct($url, $method, $config);
$this->setHeader('User-Agent', $this->userAgent());
}
/**
* Convenience function to run a get request and return the response body.
* Use when you don't need to get into details of the response.
*
* @return mixed string on success, false on failure
*/
function get()
{
$this->setMethod(self::METHOD_GET);
return $this->doRequest();
}
/**
* Convenience function to post form data and return the response body.
* Use when you don't need to get into details of the response.
*
* @param array associative array of form data to submit
* @return mixed string on success, false on failure
*/
public function post($data=array())
{
$this->setMethod(self::METHOD_POST);
if ($data) {
$this->addPostParameter($data);
} }
return $this->doRequest();
}
$type = common_config('http', 'client'); /**
* @return mixed string on success, false on failure
switch ($type) { */
case 'curl': protected function doRequest()
self::$_client = new CurlClient(); {
break; try {
default: $response = $this->send();
throw new Exception("Unknown HTTP client type '$type'"); $code = $response->getStatus();
break; if (($code < 200) || ($code >= 400)) {
return false;
}
return $response->getBody();
} catch (HTTP_Request2_Exception $e) {
$this->log(LOG_ERR, $e->getMessage());
return false;
} }
return self::$_client;
} }
function head($url, $headers) protected function log($level, $detail) {
{ $method = $this->getMethod();
throw new Exception("HEAD method unimplemented"); $url = $this->getUrl();
} common_log($level, __CLASS__ . ": HTTP $method $url - $detail");
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");
} }
/**
* Pulls up StatusNet's customized user-agent string, so services
* we hit can track down the responsible software.
*/
function userAgent() function userAgent()
{ {
return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")"; return "StatusNet/".STATUSNET_VERSION." (".STATUSNET_CODENAME.")";
} }
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);
}
} }

View File

@ -43,7 +43,7 @@ require_once 'OAuth.php';
* @link http://status.net/ * @link http://status.net/
* *
*/ */
class OAuthClientCurlException extends Exception class OAuthClientException extends Exception
{ {
} }
@ -97,9 +97,14 @@ class OAuthClient
function getRequestToken($url) function getRequestToken($url)
{ {
$response = $this->oAuthGet($url); $response = $this->oAuthGet($url);
parse_str($response); $arr = array();
$token = new OAuthToken($oauth_token, $oauth_token_secret); parse_str($response, $arr);
return $token; 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 string $url Where to make the
* @param array $params post parameters * @param array $params post parameters
@ -186,40 +191,32 @@ class OAuthClient
*/ */
function httpRequest($url, $params = null) function httpRequest($url, $params = null)
{ {
$options = array( $request = new HTTPClient($url);
CURLOPT_RETURNTRANSFER => true, $request->setConfig(array(
CURLOPT_FAILONERROR => true, 'connect_timeout' => 120,
CURLOPT_HEADER => false, 'timeout' => 120,
CURLOPT_FOLLOWLOCATION => true, 'follow_redirects' => true,
CURLOPT_USERAGENT => 'StatusNet', 'ssl_verify_peer' => false,
CURLOPT_CONNECTTIMEOUT => 120, ));
CURLOPT_TIMEOUT => 120,
CURLOPT_HTTPAUTH => CURLAUTH_ANY,
CURLOPT_SSL_VERIFYPEER => false,
// Twitter is strict about accepting invalid "Expect" headers // Twitter is strict about accepting invalid "Expect" headers
$request->setHeader('Expect', '');
CURLOPT_HTTPHEADER => array('Expect:')
);
if (isset($params)) { if (isset($params)) {
$options[CURLOPT_POST] = true; $request->setMethod(HTTP_Request2::METHOD_POST);
$options[CURLOPT_POSTFIELDS] = $params; $request->setBody($params);
} }
$ch = curl_init($url); try {
curl_setopt_array($ch, $options); $response = $request->send();
$response = curl_exec($ch); $code = $response->getStatus();
if ($code < 200 || $code >= 400) {
if ($response === false) { throw new OAuthClientException($response->getBody(), $code);
$msg = curl_error($ch); }
$code = curl_errno($ch); return $response->getBody();
throw new OAuthClientCurlException($msg, $code); } catch (Exception $e) {
throw new OAuthClientException($e->getMessage(), $e->getCode());
} }
curl_close($ch);
return $response;
} }
} }

View File

@ -44,20 +44,18 @@ function ping_broadcast_notice($notice) {
array('nickname' => $profile->nickname)), array('nickname' => $profile->nickname)),
$tags)); $tags));
$context = stream_context_create(array('http' => array('method' => "POST", $request = new HTTPClient($notify_url, HTTP_Request2::METHOD_POST);
'header' => $request->setHeader('Content-Type', 'text/xml');
"Content-Type: text/xml\r\n". $request->setBody($req);
"User-Agent: StatusNet/".STATUSNET_VERSION."\r\n", $httpResponse = $request->send();
'content' => $req)));
$file = file_get_contents($notify_url, false, $context);
if ($file === false || mb_strlen($file) == 0) { if (!$httpResponse || mb_strlen($httpResponse->getBody()) == 0) {
common_log(LOG_WARNING, common_log(LOG_WARNING,
"XML-RPC empty results for ping ($notify_url, $notice->id) "); "XML-RPC empty results for ping ($notify_url, $notice->id) ");
continue; continue;
} }
$response = xmlrpc_decode($file); $response = xmlrpc_decode($httpResponse->getBody());
if (is_array($response) && xmlrpc_is_fault($response)) { if (is_array($response) && xmlrpc_is_fault($response)) {
common_log(LOG_WARNING, common_log(LOG_WARNING,

View File

@ -172,26 +172,11 @@ class Snapshot
{ {
// XXX: Use OICU2 and OAuth to make authorized requests // 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'); $reporturl = common_config('snapshot', 'reporturl');
$result = @file_get_contents($reporturl, false, $context); $request = new HTTPClient($reporturl, HTTP_Request2::METHOD_POST);
$request->addPostParameter($this->stats);
return $result; $request->send();
} }
/** /**

View File

@ -22,6 +22,7 @@
* @category Plugin * @category Plugin
* @package StatusNet * @package StatusNet
* @author Evan Prodromou <evan@status.net> * @author Evan Prodromou <evan@status.net>
* @author Brion Vibber <brion@status.net>
* @copyright 2009 StatusNet, Inc. * @copyright 2009 StatusNet, Inc.
* @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
* @link http://status.net/ * @link http://status.net/
@ -69,14 +70,14 @@ class BlogspamNetPlugin extends Plugin
{ {
$args = $this->testArgs($notice); $args = $this->testArgs($notice);
common_debug("Blogspamnet args = " . print_r($args, TRUE)); common_debug("Blogspamnet args = " . print_r($args, TRUE));
$request = xmlrpc_encode_request('testComment', array($args)); $requestBody = xmlrpc_encode_request('testComment', array($args));
$context = stream_context_create(array('http' => array('method' => "POST",
'header' => $request = new HTTPClient($this->baseUrl, HTTP_Request2::METHOD_POST);
"Content-Type: text/xml\r\n". $request->addHeader('Content-Type: text/xml');
"User-Agent: " . $this->userAgent(), $request->setBody($requestBody);
'content' => $request))); $httpResponse = $request->send();
$file = file_get_contents($this->baseUrl, false, $context);
$response = xmlrpc_decode($file); $response = xmlrpc_decode($httpResponse->getBody());
if (xmlrpc_is_fault($response)) { if (xmlrpc_is_fault($response)) {
throw new ServerException("$response[faultString] ($response[faultCode])", 500); throw new ServerException("$response[faultString] ($response[faultCode])", 500);
} else { } else {

View File

@ -129,27 +129,25 @@ class LinkbackPlugin extends Plugin
} }
} }
$request = xmlrpc_encode_request('pingback.ping', $args); $request = new HTTPClient($endpoint, 'POST');
$context = stream_context_create(array('http' => array('method' => "POST", $request->setHeader('User-Agent', $this->userAgent());
'header' => $request->setHeader('Content-Type', 'text/xml');
"Content-Type: text/xml\r\n". $request->setBody(xmlrpc_encode_request('pingback.ping', $args));
"User-Agent: " . $this->userAgent(), try {
'content' => $request))); $response = $request->send();
$file = file_get_contents($endpoint, false, $context); } catch (HTTP_Request2_Exception $e) {
if (!$file) {
common_log(LOG_WARNING, common_log(LOG_WARNING,
"Pingback request failed for '$url' ($endpoint)"); "Pingback request failed for '$url' ($endpoint)");
}
$response = xmlrpc_decode($response->getBody());
if (xmlrpc_is_fault($response)) {
common_log(LOG_WARNING,
"Pingback error for '$url' ($endpoint): ".
"$response[faultString] ($response[faultCode])");
} else { } else {
$response = xmlrpc_decode($file); common_log(LOG_INFO,
if (xmlrpc_is_fault($response)) { "Pingback success for '$url' ($endpoint): ".
common_log(LOG_WARNING, "'$response'");
"Pingback error for '$url' ($endpoint): ".
"$response[faultString] ($response[faultCode])");
} else {
common_log(LOG_INFO,
"Pingback success for '$url' ($endpoint): ".
"'$response'");
}
} }
} }

View File

@ -65,15 +65,6 @@ class SimpleUrlPlugin extends Plugin
class SimpleUrl extends ShortUrlApi class SimpleUrl extends ShortUrlApi
{ {
protected function shorten_imp($url) { protected function shorten_imp($url) {
$curlh = curl_init(); return $this->http_get($url);
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;
} }
} }

View File

@ -152,8 +152,8 @@ class SyncTwitterFriendsDaemon extends ParallelizingDaemon
$friends_ids = $client->friendsIds(); $friends_ids = $client->friendsIds();
} catch (Exception $e) { } catch (Exception $e) {
common_log(LOG_WARNING, $this->name() . common_log(LOG_WARNING, $this->name() .
' - cURL error getting friend ids ' . ' - error getting friend ids: ' .
$e->getCode() . ' - ' . $e->getMessage()); $e->getMessage());
return $friends; return $friends;
} }

View File

@ -109,12 +109,16 @@ class TwitterStatusFetcher extends ParallelizingDaemon
$flink->find(); $flink->find();
$flinks = array(); $flinks = array();
common_log(LOG_INFO, "hello");
while ($flink->fetch()) { while ($flink->fetch()) {
if (($flink->noticesync & FOREIGN_NOTICE_RECV) == if (($flink->noticesync & FOREIGN_NOTICE_RECV) ==
FOREIGN_NOTICE_RECV) { FOREIGN_NOTICE_RECV) {
$flinks[] = clone($flink); $flinks[] = clone($flink);
common_log(LOG_INFO, "sync: foreign id $flink->foreign_id");
} else {
common_log(LOG_INFO, "nothing to sync");
} }
} }
@ -515,31 +519,34 @@ class TwitterStatusFetcher extends ParallelizingDaemon
return $id; 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) function fetchAvatar($url, $filename)
{ {
$avatarfile = Avatar::path($filename); common_debug($this->name() . " - Fetching Twitter avatar: $url");
$out = fopen($avatarfile, 'wb'); $request = new HTTPClient($url, 'GET', array(
if (!$out) { 'follow_redirects' => true,
common_log(LOG_WARNING, $this->name() . ));
" - Couldn't open file $filename"); $data = $request->get();
if ($data) {
$avatarfile = Avatar::path($filename);
$ok = file_put_contents($avatarfile, $data);
if (!$ok) {
common_log(LOG_WARNING, $this->name() .
" - Couldn't open file $filename");
return false;
}
} else {
return false; return false;
} }
common_debug($this->name() . " - Fetching Twitter avatar: $url"); return true;
$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;
} }
} }

View File

@ -215,7 +215,7 @@ function broadcast_basicauth($notice, $flink)
try { try {
$status = $client->statusesUpdate($statustxt); $status = $client->statusesUpdate($statustxt);
} catch (BasicAuthCurlException $e) { } catch (HTTP_Request2_Exception $e) {
return process_error($e, $flink); return process_error($e, $flink);
} }

View File

@ -125,7 +125,7 @@ class TwitterauthorizationAction extends Action
$auth_link = $client->getAuthorizeLink($req_tok); $auth_link = $client->getAuthorizeLink($req_tok);
} catch (TwitterOAuthClientException $e) { } catch (OAuthClientException $e) {
$msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s', $msg = sprintf('OAuth client cURL error - code: %1s, msg: %2s',
$e->getCode(), $e->getMessage()); $e->getCode(), $e->getMessage());
$this->serverError(_('Couldn\'t link your Twitter account.')); $this->serverError(_('Couldn\'t link your Twitter account.'));

View File

@ -31,26 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) {
exit(1); 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. * Class for talking to the Twitter API with HTTP Basic Auth.
* *
@ -198,45 +178,27 @@ class TwitterBasicAuthClient
*/ */
function httpRequest($url, $params = null, $auth = true) function httpRequest($url, $params = null, $auth = true)
{ {
$options = array( $request = new HTTPClient($url, 'GET', array(
CURLOPT_RETURNTRANSFER => true, 'follow_redirects' => true,
CURLOPT_FAILONERROR => true, 'connect_timeout' => 120,
CURLOPT_HEADER => false, 'timeout' => 120,
CURLOPT_FOLLOWLOCATION => true, 'ssl_verifypeer' => false,
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 // Twitter is strict about accepting invalid "Expect" headers
$request->setHeader('Expect', '');
CURLOPT_HTTPHEADER => array('Expect:')
);
if (isset($params)) { if (isset($params)) {
$options[CURLOPT_POST] = true; $request->setMethod('POST');
$options[CURLOPT_POSTFIELDS] = $params; $request->addPostParameter($params);
} }
if ($auth) { if ($auth) {
$options[CURLOPT_USERPWD] = $this->screen_name . $request->setAuth($this->screen_name, $this->password);
':' . $this->password;
} }
$ch = curl_init($url); $response = $request->send();
curl_setopt_array($ch, $options); return $response->getBody();
$response = curl_exec($ch);
if ($response === false) {
$msg = curl_error($ch);
$code = curl_errno($ch);
throw new BasicAuthCurlException($msg, $code);
}
curl_close($ch);
return $response;
} }
} }

View File

@ -68,10 +68,8 @@ class WikiHashtagsPlugin extends Plugin
$editurl = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=edit', $editurl = sprintf('http://hashtags.wikia.com/index.php?title=%s&action=edit',
urlencode($tag)); urlencode($tag));
$context = stream_context_create(array('http' => array('method' => "GET", $request = new HTTPClient($url);
'header' => $html = $request->get();
"User-Agent: " . $this->userAgent())));
$html = @file_get_contents($url, false, $context);
$action->elementStart('div', array('id' => 'wikihashtags', 'class' => 'section')); $action->elementStart('div', array('id' => 'wikihashtags', 'class' => 'section'));
@ -100,10 +98,4 @@ class WikiHashtagsPlugin extends Plugin
return true; return true;
} }
function userAgent()
{
return 'WikiHashtagsPlugin/'.WIKIHASHTAGSPLUGIN_VERSION .
' StatusNet/' . STATUSNET_VERSION;
}
} }

View File

@ -46,8 +46,8 @@ class EnjitQueueHandler extends QueueHandler
function start() function start()
{ {
$this->log(LOG_INFO, "Starting EnjitQueueHandler"); $this->log(LOG_INFO, "Starting EnjitQueueHandler");
$this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl'));
return true; return true;
} }
@ -56,16 +56,16 @@ class EnjitQueueHandler extends QueueHandler
$profile = Profile::staticGet($notice->profile_id); $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 ) { if ( ! $notice->is_local ) {
$this->log(LOG_INFO, "Skipping remote notice"); $this->log(LOG_INFO, "Skipping remote notice");
return "skipped"; 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)); $noticeurl = common_local_url('shownotice', array('notice' => $notice->id));
$msg = $profile->nickname . ': ' . $notice->content; $msg = $profile->nickname . ': ' . $notice->content;
@ -86,36 +86,20 @@ class EnjitQueueHandler extends QueueHandler
$atom .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n"; $atom .= "<updated>".common_date_w3dtf($notice->modified)."</updated>\n";
$atom .= "</entry>\n"; $atom .= "</entry>\n";
$url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey'); $url = common_config('enjit', 'apiurl') . "/submit/". common_config('enjit','apikey');
$data = "msg=$atom"; $data = array(
'msg' => $atom,
);
# #
# POST the message to $config['enjit']['apiurl'] # POST the message to $config['enjit']['apiurl']
# #
$ch = curl_init(); $request = new HTTPClient($url, HTTP_Request2::METHOD_POST);
$request->addPostFields($data);
$response = $request->send();
curl_setopt($ch, CURLOPT_URL, $url); // @fixme handle_notice() is supposed to return true/false. Somethin' funky?
return $response->getStatus();
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;
} }
} }