Current File : //opt/RZphp72/includes/HTTP/Client.php |
<?php
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/**
* A simple HTTP client class.
*
* PHP versions 4 and 5
*
* LICENSE:
*
* Copyright (c) 2003-2008, 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 name of the author 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_Client
* @author Alexey Borzov <avb@php.net>
* @license http://opensource.org/licenses/bsd-license.php New BSD License
* @version CVS: $Id: Client.php,v 1.11 2008/10/11 14:29:27 avb Exp $
* @link http://pear.php.net/package/HTTP_Client
*/
/*
* Do this define in your script if you wish HTTP_Client to follow browser
* quirks rather than HTTP specification (RFC2616). This means:
* - do a GET request after redirect with code 301, rather than use the
* same method as before redirect.
*/
// define('HTTP_CLIENT_QUIRK_MODE', true);
/**
* Class for performing HTTP requests
*/
require_once 'HTTP/Request.php';
/**
* Class used to store cookies and pass them between HTTP requests.
*/
require_once 'HTTP/Client/CookieManager.php';
/**
* A simple HTTP client class.
*
* The class wraps around HTTP_Request providing a higher-level
* API for performing multiple HTTP requests
*
* Note that the class implements all the methods defined by the Iterator
* interface for going over the received responses. Being PHP4-compatible it is
* not declared with 'implements Iterator', though. Therefore if you are
* running PHP5 and want to use HTTP_Client in 'foreach' context you should do
* the following:
* <code>
* class PHP5_HTTP_Client extends HTTP_Client implements Iterator {}
*
* $client = new PHP5_HTTP_Client();
*
* // ... perform requests ...
*
* foreach ($client as $url => $response) {
* // do something
* }
* </code>
*
* If you are running PHP4 you'll have to call the methods manually:
* <code>
* for ($client->rewind(); $client->valid(); $client->next()) {
* $url = $client->key();
* $response = $client->current();
* // do something
* }
* </code>
*
* @category HTTP
* @package HTTP_Client
* @author Alexey Borzov <avb@php.net>
* @version Release: 1.2.1
*/
class HTTP_Client
{
/**#@+
* @access private
*/
/**
* Cookie manager object
* @var HTTP_Client_CookieManager
*/
var $_cookieManager;
/**
* Received HTTP responses
* @var array
*/
var $_responses;
/**
* Default headers to send on every request
* @var array
*/
var $_defaultHeaders = array();
/**
* Default parameters for HTTP_Request's constructor
* @var array
*/
var $_defaultRequestParams = array();
/**
* How many redirects were done
* @var integer
*/
var $_redirectCount = 0;
/**
* Maximum allowed redirects
* @var integer
*/
var $_maxRedirects = 5;
/**
* Listeners attached to the client
* @var array
*/
var $_listeners = array();
/**
* Whether the listener should be propagated to Request objects
* @var array
*/
var $_propagate = array();
/**
* Whether to keep all the responses or just the most recent one
* @var boolean
*/
var $_isHistoryEnabled = true;
/**
* Index for iteration over the responses
* @var integer
*/
var $_idx = 0;
/**#@-*/
/**
* Constructor
*
* @access public
* @param array Parameters to pass to HTTP_Request's constructor
* @param array Default headers to send on every request
* @param HTTP_Client_CookieManager Cookie manager object to use
*/
function HTTP_Client($defaultRequestParams = null, $defaultHeaders = null, $cookieManager = null)
{
if (!empty($cookieManager) && is_a($cookieManager, 'HTTP_Client_CookieManager')) {
$this->_cookieManager = $cookieManager;
} else {
$this->_cookieManager =& new HTTP_Client_CookieManager();
}
if (isset($defaultHeaders)) {
$this->setDefaultHeader($defaultHeaders);
}
if (isset($defaultRequestParams)) {
$this->setRequestParameter($defaultRequestParams);
}
}
/**
* Sets the maximum redirects that will be processed.
*
* Setting this to 0 disables redirect processing. If not 0 and the
* number of redirects in a request is bigger than this number, then an
* error will be raised.
*
* @access public
* @param int Max number of redirects to process
*/
function setMaxRedirects($value)
{
$this->_maxRedirects = $value;
}
/**
* Sets whether to keep all the responses or just the most recent one
*
* @access public
* @param bool Whether to enable history
*/
function enableHistory($enable)
{
$this->_isHistoryEnabled = (bool)$enable;
}
/**
* Creates a HTTP_Request objects, applying all the necessary defaults
*
* @param string URL
* @param string Method, constants are defined in HTTP_Request
* @param array Extra headers to send
* @access private
* @return HTTP_Request Request object with all defaults applied
*/
function &_createRequest($url, $method = HTTP_REQUEST_METHOD_GET, $headers = array())
{
$req =& new HTTP_Request($url, $this->_defaultRequestParams);
$req->setMethod($method);
foreach ($this->_defaultHeaders as $name => $value) {
$req->addHeader($name, $value);
}
foreach ($headers as $name => $value) {
$req->addHeader($name, $value);
}
$this->_cookieManager->passCookies($req);
foreach ($this->_propagate as $id => $propagate) {
if ($propagate) {
$req->attach($this->_listeners[$id]);
}
}
return $req;
}
/**
* Sends a 'HEAD' HTTP request
*
* @param string URL
* @param array Extra headers to send
* @access public
* @return integer HTTP response code
* @throws PEAR_Error
*/
function head($url, $headers = array())
{
$request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_HEAD, $headers);
return $this->_performRequest($request);
}
/**
* Sends a 'GET' HTTP request
*
* @param string URL
* @param mixed additional data to send
* @param boolean Whether the data is already urlencoded
* @param array Extra headers to send
* @access public
* @return integer HTTP response code
* @throws PEAR_Error
*/
function get($url, $data = null, $preEncoded = false, $headers = array())
{
$request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_GET, $headers);
if (is_array($data)) {
foreach ($data as $name => $value) {
$request->addQueryString($name, $value, $preEncoded);
}
} elseif (isset($data)) {
$request->addRawQueryString($data, $preEncoded);
}
return $this->_performRequest($request);
}
/**
* Sends a 'POST' HTTP request
*
* @param string URL
* @param mixed Data to send
* @param boolean Whether the data is already urlencoded
* @param array Files to upload. Elements of the array should have the form:
* array(name, filename(s)[, content type]), see HTTP_Request::addFile()
* @param array Extra headers to send
* @access public
* @return integer HTTP response code
* @throws PEAR_Error
*/
function post($url, $data, $preEncoded = false, $files = array(), $headers = array())
{
$request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_POST, $headers);
if (is_array($data)) {
foreach ($data as $name => $value) {
$request->addPostData($name, $value, $preEncoded);
}
} else {
$request->addRawPostData($data, $preEncoded);
}
foreach ($files as $fileData) {
$res = call_user_func_array(array(&$request, 'addFile'), $fileData);
if (PEAR::isError($res)) {
return $res;
}
}
return $this->_performRequest($request);
}
/**
* Sends a 'PUT' HTTP request
*
* @param string URL
* @param string Request body
* @param array Extra headers to send
* @access public
* @return integer HTTP response code
* @throws PEAR_Error
*/
function put($url, $body = '', $headers = array())
{
$request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_PUT, $headers);
$request->setBody($body);
return $this->_performRequest($request);
}
/**
* Sends a 'DELETE' HTTP request
*
* @param string URL
* @param array Extra headers to send
* @access public
* @return integer HTTP response code
* @throws PEAR_Error
*/
function delete($url, $headers = array())
{
$request =& $this->_createRequest($url, HTTP_REQUEST_METHOD_DELETE, $headers);
return $this->_performRequest($request);
}
/**
* Sets default header(s) for HTTP requests
*
* @param mixed header name or array ('header name' => 'header value')
* @param string header value if $name is not an array
* @access public
*/
function setDefaultHeader($name, $value = null)
{
if (is_array($name)) {
$this->_defaultHeaders = array_merge($this->_defaultHeaders, $name);
} else {
$this->_defaultHeaders[$name] = $value;
}
}
/**
* Sets parameter(s) for HTTP requests
*
* @param mixed parameter name or array ('parameter name' => 'parameter value')
* @param string parameter value if $name is not an array
* @access public
*/
function setRequestParameter($name, $value = null)
{
if (is_array($name)) {
$this->_defaultRequestParams = array_merge($this->_defaultRequestParams, $name);
} else {
$this->_defaultRequestParams[$name] = $value;
}
}
/**
* Performs a request, processes redirects
*
* @param HTTP_Request Request object
* @access private
* @return integer HTTP response code
* @throws PEAR_Error
*/
function _performRequest(&$request)
{
// If this is not a redirect, notify the listeners of new request
if (0 == $this->_redirectCount && '' != $request->getUrl()) {
$this->_notify('request', $request->getUrl());
}
if (PEAR::isError($err = $request->sendRequest())) {
$this->_redirectCount = 0;
return $err;
}
$this->_pushResponse($request);
$code = $request->getResponseCode();
if ($this->_maxRedirects > 0) {
if (in_array($code, array(300, 301, 302, 303, 307))) {
if ('' == ($location = $request->getResponseHeader('Location'))) {
$this->_redirectCount = 0;
return PEAR::raiseError("No 'Location' field on redirect");
}
// Bug #5759: do not try to follow non-HTTP redirects
if (null === ($redirectUrl = $this->_redirectUrl($request->_url, $location))) {
$this->_redirectCount = 0;
return $code;
}
// Redirect via <meta http-equiv="Refresh"> tag, see request #5734
} elseif (200 <= $code && $code < 300) {
$redirectUrl = $this->_getMetaRedirect($request);
}
}
if (!empty($redirectUrl)) {
if (++$this->_redirectCount > $this->_maxRedirects) {
$this->_redirectCount = 0;
return PEAR::raiseError('Too many redirects');
}
// Notify of redirection
$this->_notify('httpRedirect', $redirectUrl);
// we access the private properties directly, as there are no accessors for them
switch ($request->_method) {
case HTTP_REQUEST_METHOD_POST:
// Bug #13487: if doing a redirect via <meta>, use GET
if (302 == $code || 303 == $code || $code < 300 ||
(301 == $code && defined('HTTP_CLIENT_QUIRK_MODE')))
{
return $this->get($redirectUrl);
} elseif (!empty($request->_postData) || !empty($request->_postFiles)) {
$postFiles = array();
foreach ($request->_postFiles as $name => $data) {
$postFiles[] = array($name, $data['name'], $data['type']);
}
return $this->post($redirectUrl, $request->_postData, true, $postFiles);
} else {
return $this->post($redirectUrl, $request->_body, true);
}
case HTTP_REQUEST_METHOD_HEAD:
return (303 == $code? $this->get($redirectUrl): $this->head($redirectUrl));
case HTTP_REQUEST_METHOD_GET:
default:
return $this->get($redirectUrl);
} // switch
} else {
$this->_redirectCount = 0;
if (400 >= $code) {
$this->_notify('httpSuccess');
$this->setDefaultHeader('Referer', $request->getUrl());
// some result processing should go here
} else {
$this->_notify('httpError');
}
}
return $code;
}
/**
* Returns the most recent HTTP response
*
* To access previous responses use iteration methods
*
* @access public
* @return array
*/
function ¤tResponse()
{
return $this->_responses[count($this->_responses) - 1];
}
/**
* Saves the server's response to responses list
*
* @param HTTP_Request Request object already containing the response
* @access private
*/
function _pushResponse(&$request)
{
$this->_cookieManager->updateCookies($request);
$idx = $this->_isHistoryEnabled? count($this->_responses): 0;
$this->_responses[$idx] = array(
'url' => $request->getUrl(),
'code' => $request->getResponseCode(),
'headers' => $request->getResponseHeader(),
'body' => $request->getResponseBody()
);
}
/**
* Clears object's internal properties
*
* @access public
*/
function reset()
{
$this->_cookieManager->reset();
$this->_responses = array();
$this->_defaultHeaders = array();
$this->_defaultRequestParams = array();
$this->_idx = 0;
}
/**
* Adds a Listener to the list of listeners that are notified of
* the object's events
*
* Events sent by HTTP_Client objects:
* - 'request': sent on HTTP request that is not a redirect
* - 'httpSuccess': sent when we receive a successfull 2xx response
* - 'httpRedirect': sent when we receive a redirection response
* - 'httpError': sent on 4xx, 5xx response
*
* @param HTTP_Request_Listener Listener to attach
* @param boolean Whether the listener should be attached
* to the created HTTP_Request objects
* @return boolean whether the listener was successfully attached
* @access public
*/
function attach(&$listener, $propagate = false)
{
if (!is_a($listener, 'HTTP_Request_Listener')) {
return false;
}
$this->_listeners[$listener->getId()] =& $listener;
$this->_propagate[$listener->getId()] = $propagate;
return true;
}
/**
* Removes a Listener from the list of listeners
*
* @param HTTP_Request_Listener Listener to detach
* @return boolean Whether the listener was successfully detached
* @access public
*/
function detach(&$listener)
{
if (!is_a($listener, 'HTTP_Request_Listener') ||
!isset($this->_listeners[$listener->getId()])) {
return false;
}
unset($this->_listeners[$listener->getId()], $this->_propagate[$listener->getId()]);
return true;
}
/**
* Notifies all registered listeners of an event.
*
* @param string Event name
* @param mixed Additional data
* @access private
*/
function _notify($event, $data = null)
{
foreach (array_keys($this->_listeners) as $id) {
$this->_listeners[$id]->update($this, $event, $data);
}
}
/**
* Calculates the absolute URL of a redirect
*
* @param Net_Url Object containing the request URL
* @param string Value of the 'Location' response header
* @return string|null Absolute URL we are being redirected to, null in case of non-HTTP URL
* @access private
*/
function _redirectUrl($url, $location)
{
// If it begins with a scheme (as defined in RFC 2396) then it is absolute URI
if (preg_match('/^([a-zA-Z][a-zA-Z0-9+.-]*):/', $location, $matches)) {
// Bug #5759: we shouldn't try to follow non-HTTP redirects
if ('http' == strtolower($matches[1]) || 'https' == strtolower($matches[1])) {
return $location;
} else {
return null;
}
} else {
if ('/' == $location[0]) {
$url->path = Net_URL::resolvePath($location);
} elseif('/' == substr($url->path, -1)) {
$url->path = Net_URL::resolvePath($url->path . $location);
} else {
$dirname = (DIRECTORY_SEPARATOR == dirname($url->path)? '/': dirname($url->path));
$url->path = Net_URL::resolvePath($dirname . '/' . $location);
}
$url->querystring = array();
$url->anchor = '';
return $url->getUrl();
}
}
/**
* Returns the cookie manager object (e.g. for storing it somewhere)
*
* @return HTTP_Client_CookieManager
* @access public
*/
function getCookieManager()
{
return $this->_cookieManager;
}
/**
* Tries to extract a redirect URL from <<meta http-equiv=Refresh>> tag (request #5734)
*
* @param HTTP_Request A request object already containing the response
* @return string|null Absolute URI we are being redirected to, null if no redirect / invalid redirect
* @access private
*/
function _getMetaRedirect(&$request)
{
// Non-HTML response or empty response body
if ('text/html' != substr($request->getResponseHeader('content-type'), 0, 9) ||
'' == ($body = $request->getResponseBody())) {
return null;
}
// No <meta http-equiv=Refresh> tag
if (!preg_match('!<meta\\s+([^>]*http-equiv\\s*=\\s*("Refresh"|\'Refresh\'|Refresh)[^>]*)>!is', $body, $matches)) {
return null;
}
// Just a refresh, no redirect
if (!preg_match('!content\\s*=\\s*("[^"]+"|\'[^\']+\'|\\S+)!is', $matches[1], $urlMatches)) {
return null;
}
$parts = explode(';', ('\'' == substr($urlMatches[1], 0, 1) || '"' == substr($urlMatches[1], 0, 1))?
substr($urlMatches[1], 1, -1): $urlMatches[1]);
if (empty($parts[1]) || !preg_match('/url\\s*=\\s*("[^"]+"|\'[^\']+\'|\\S+)/is', $parts[1], $urlMatches)) {
return null;
}
$url = ('\'' == substr($urlMatches[1], 0, 1) || '"' == substr($urlMatches[1], 0, 1))?
substr($urlMatches[1], 1, -1): $urlMatches[1];
// We do finally have an url... Now check that it's:
// a) HTTP, b) not to the same page
$previousUrl = $request->getUrl();
$redirectUrl = $this->_redirectUrl($request->_url, html_entity_decode($url));
return (null === $redirectUrl || $redirectUrl == $previousUrl)? null: $redirectUrl;
}
/**
* Returns the current element (HTTP response)
*
* @access public
* @return array
*/
function current()
{
$response = $this->_responses[$this->_idx];
unset($response['url']);
return $response;
}
/**
* Returns the key of the current element
*
* @access public
* @return string URL producing the response
*/
function key()
{
return $this->_responses[$this->_idx]['url'];
}
/**
* Moves forward to next element
*
* @access public
*/
function next()
{
$this->_idx++;
}
/**
* Rewinds to the first element (response)
*
* @access public
*/
function rewind()
{
$this->_idx = 0;
}
/**
* Checks if there is a current element after call to rewind() or next()
*
* @access public
* @return bool
*/
function valid()
{
return $this->_idx < count($this->_responses);
}
}
?>