--- /dev/null
+<?php
+/**
+ * Basic example for fetching a page with Horde_Http_Client
+ *
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ */
+
+require 'Horde/Http/Client.php';
+require 'Horde/Http/Client/Exception.php';
+require 'Horde/Http/Client/Response.php';
+
+$client = new Horde_Http_Client();
+$response = $client->GET('http://www.example.com/');
+var_dump($response);
+echo $response->getBody();
--- /dev/null
+<?php
+/**
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ *
+ * @TODO - add support for http://pecl.php.net/package/pecl_http ?
+ */
+
+/**
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ */
+class Horde_Http_Client
+{
+ /**
+ * URI to make our next request to
+ * @var string
+ */
+ protected $_uri = null;
+
+ /**
+ * Request headers
+ * @var array
+ */
+ protected $_headers = array();
+
+ /**
+ * Proxy server
+ * @var string
+ */
+ protected $_proxyServer = null;
+
+ /**
+ * Proxy username
+ * @var string
+ */
+ protected $_proxyUser = null;
+
+ /**
+ * Proxy password
+ * @var string
+ */
+ protected $_proxyPass = null;
+
+ /**
+ * The most recent HTTP request
+ *
+ * An array with these values:
+ * 'uri'
+ * 'method'
+ * 'headers'
+ * 'data'
+ *
+ * @var array
+ */
+ protected $_lastRequest;
+
+ /**
+ * The most recent HTTP response
+ * @var Horde_Http_Client_Response
+ */
+ protected $_lastResponse;
+
+ /**
+ * Horde_Http_Client constructor.
+ *
+ * @param array $args Any Http_Client settings to initialize in the
+ * constructor. Available settings are:
+ * uri
+ * headers
+ * proxyServer
+ * proxyUser
+ * proxyPass
+ */
+ public function __construct($args = array())
+ {
+ if (!ini_get('allow_url_fopen')) {
+ throw new Horde_Http_Client_Exception('allow_url_fopen must be enabled');
+ }
+
+ foreach ($args as $key => $val) {
+ $this->$key = $val;
+ }
+ }
+
+ /**
+ * Set one or more headers
+ *
+ * @param mixed $headers A hash of header + value pairs, or a single header name
+ * @param string $value A header value
+ */
+ public function setHeaders($headers, $value = null)
+ {
+ if (!is_array($headers)) {
+ $headers = array($headers => $value);
+ }
+
+ foreach ($headers as $header => $value) {
+ $this->_headers[$header] = $value;
+ }
+ }
+
+ /**
+ * Get the current value of $header
+ *
+ * @param string $header Header name to get
+ * @return string $header's current value
+ */
+ public function getHeader($header)
+ {
+ return isset($this->_headers[$header]) ? $this->_headers[$header] : null;
+ }
+
+ /**
+ * Send a GET request
+ *
+ * @return Horde_Http_Client_Response
+ */
+ public function get($uri = null, $headers = array())
+ {
+ return $this->request('GET', $uri, null, $headers);
+ }
+
+ /**
+ * Send a POST request
+ *
+ * @return Horde_Http_Client_Response
+ */
+ public function post($uri = null, $data = null, $headers = array())
+ {
+ return $this->request('POST', $uri, $data, $headers);
+ }
+
+ /**
+ * Send a PUT request
+ *
+ * @return Horde_Http_Client_Response
+ */
+ public function put($uri = null, $data = null, $headers = array())
+ {
+ // FIXME: suport method override (X-Method-Override: PUT).
+ return $this->request('PUT', $uri, $data, $headers);
+ }
+
+ /**
+ * Send a DELETE request
+ *
+ * @return Horde_Http_Client_Response
+ */
+ public function delete($uri = null, $headers = array())
+ {
+ // FIXME: suport method override (X-Method-Override: DELETE).
+ return $this->request('DELETE', $uri, null, $headers);
+ }
+
+ /**
+ * Send an HTTP request
+ *
+ * @param string $method HTTP request method (GET, PUT, etc.)
+ * @param string $uri URI to request, if different from $this->uri
+ * @param mixed $data Request data. Can be an array of form data that will be
+ * encoded automatically, or a raw string.
+ * @param array $headers Any headers specific to this request. They will
+ * be combined with $this->_headers, and override
+ * headers of the same name for this request only.
+ *
+ * @return Horde_Http_Client_Response
+ */
+ public function request($method, $uri = null, $data = null, $headers = array())
+ {
+ if (is_null($uri)) {
+ $uri = $this->uri;
+ }
+
+ if (is_array($data)) {
+ $data = http_build_query($data, '', '&');
+ }
+
+ $headers = array_merge($this->_headers, $headers);
+
+ // Store the last request for ease of debugging.
+ $this->_lastRequest = array(
+ 'uri' => $uri,
+ 'method' => $method,
+ 'headers' => $headers,
+ 'data' => $data,
+ );
+
+ // Stream context config.
+ $opts = array('http' => array(
+ 'method' => $method,
+ 'header' => implode("\n", $headers),
+ 'content' => $data));
+
+ // Proxy settings
+ if ($this->proxyServer) {
+ $opts['http']['proxy'] = 'tcp://' . $this->proxyServer;
+ $opts['http']['request_fulluri'] = true;
+ if ($this->proxyUser && $this->proxyPass) {
+ $headers['Proxy-Authorization'] = 'Basic ' . base64_encode($this->proxyUser . ':' . $this->proxyPass);
+ }
+ }
+
+ $context = stream_context_create($opts);
+ $stream = @fopen($uri, 'rb', false, $context);
+ if (!$stream) {
+ throw new Horde_Http_Client_Exception('Problem with ' . $uri . ': ', error_get_last());
+ }
+
+ $meta = stream_get_meta_data($stream);
+ $headers = isset($meta['wrapper_data']) ? $meta['wrapper_data'] : array();
+
+ $this->_lastResponse = new Horde_Http_Client_Response($uri, $stream, $headers);
+ return $this->_lastResponse;
+ }
+
+ /**
+ * Get a client parameter
+ *
+ * @param string $name The parameter to get.
+ * @return mixed Parameter value.
+ */
+ public function __get($name)
+ {
+ return isset($this->{'_' . $name}) ? $this->{'_' . $name} : null;
+ }
+
+ /**
+ * Set a client parameter
+ *
+ * @param string $name The parameter to set.
+ * @param mixed $value Parameter value.
+ */
+ public function __set($name, $value)
+ {
+ switch ($name) {
+ case 'headers':
+ $this->setHeaders($value);
+ break;
+ }
+
+ $this->{'_' . $name} = $value;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ */
+
+/**
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ */
+class Horde_Http_Client_Exception extends Exception
+{
+ /**
+ * Constructor
+ */
+ public function __construct($message = null, $code_or_lasterror = 0)
+ {
+ if (is_array($code_or_lasterror)) {
+ if ($message) {
+ $message .= $code_or_lasterror['message'];
+ } else {
+ $message = $code_or_lasterror['message'];
+ }
+
+ $this->file = $code_or_lasterror['file'];
+ $this->line = $code_or_lasterror['line'];
+ $code = $code_or_lasterror['type'];
+ } else {
+ $code = $code_or_lasterror;
+ }
+
+ parent::__construct($message, $code);
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ */
+
+/**
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ */
+class Horde_Http_Client_Mock extends Horde_Http_Client
+{
+ /**
+ * Array of mock responses
+ * @var array
+ */
+ protected $_responses = array();
+
+ /**
+ * Current mock response
+ * @var integer
+ */
+ protected $_responseIndex = 0;
+
+ /**
+ * Send an HTTP request
+ *
+ * @param string $method HTTP request method (GET, PUT, etc.)
+ * @param string $uri URI to request, if different from $this->uri
+ * @param mixed $data Request data. Can be an array of form data that will be
+ * encoded automatically, or a raw string.
+ * @param array $headers Any headers specific to this request. They will
+ * be combined with $this->_headers, and override
+ * headers of the same name for this request only.
+ *
+ * @return Horde_Http_Client_Response
+ *
+ * @TODO make lastRequest work somehow.
+ */
+ public function request($method, $uri = null, $data = null, $headers = array())
+ {
+ if ($this->_responseIndex >= count($this->_responses)) {
+ $this->_responseIndex = 0;
+ }
+ return $this->_responses[$this->_responseIndex++];
+ }
+
+ /**
+ * Set the HTTP response(s) to be returned by this adapter
+ *
+ * @param Horde_Http_Client_Response $response
+ */
+ public function setResponse($response)
+ {
+ $this->_responses = array($response);
+ $this->_responseIndex = 0;
+ }
+
+ /**
+ * Add another response to the response buffer.
+ *
+ * @param string $response
+ */
+ public function addResponse($response)
+ {
+ $this->_responses[] = $response;
+ }
+
+ /**
+ * Sets the position of the response buffer. Selects which
+ * response will be returned on the next call to read().
+ *
+ * @param integer $index
+ */
+ public function setResponseIndex($index)
+ {
+ if ($index < 0 || $index >= count($this->_responses)) {
+ throw new OutOfBoundsException;
+ }
+ $this->_responseIndex = $index;
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ */
+
+/**
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @license http://opensource.org/licenses/bsd-license.php BSD
+ * @category Horde
+ * @package Horde_Http_Client
+ */
+class Horde_Http_Client_Response
+{
+ /**
+ * Fetched URI
+ * @var string
+ */
+ public $uri;
+
+ /**
+ * HTTP protocol version that was used
+ * @var float
+ */
+ public $httpVersion;
+
+ /**
+ * HTTP response code
+ * @var integer
+ */
+ public $code;
+
+ /**
+ * Response headers
+ * @var array
+ */
+ public $headers;
+
+ /**
+ * Response body
+ * @var stream
+ */
+ protected $_stream;
+
+ /**
+ * Constructor
+ */
+ public function __construct($uri, $stream, $headers = array())
+ {
+ $this->uri = $uri;
+ $this->_stream = $stream;
+ $this->_parseHeaders($headers);
+ }
+
+ /**
+ * Parse an array of response headers, mindful of line
+ * continuations, etc.
+ *
+ * @param array $headers
+ * @return array
+ */
+ protected function _parseHeaders($headers)
+ {
+ $lastHeader = null;
+ foreach ($headers as $headerLine) {
+ // stream_get_meta returns all headers generated while processing a
+ // request, including ones for redirects before an eventually successful
+ // request. We just want the last one, so whenever we hit a new HTTP
+ // header, throw out anything parsed previously and start over.
+ if (preg_match('/^HTTP\/(\d.\d) (\d{3})/', $headerLine, $httpMatches)) {
+ $this->httpVersion = $httpMatches[1];
+ $this->code = (int)$httpMatches[2];
+ $this->headers = array();
+ $lastHeader = null;
+ }
+
+ $headerLine = trim($headerLine, "\r\n");
+ if ($headerLine == '') {
+ break;
+ }
+ if (preg_match('|^([\w-]+):\s+(.+)|', $headerLine, $m)) {
+ unset($lastHeader);
+ $headerName = strtolower($m[1]);
+ $headerValue = $m[2];
+
+ if (isset($this->headers[$headerName])) {
+ if (!is_array($this->headers[$headerName])) {
+ $this->headers[$headerName] = array($this->headers[$headerName]);
+ }
+
+ $this->headers[$headerName][] = $headerValue;
+ } else {
+ $this->headers[$headerName] = $headerValue;
+ }
+ $lastHeader = $headerName;
+ } elseif (preg_match("|^\s+(.+)$|", $headerLine, $m) && !is_null($lastHeader)) {
+ if (is_array($this->headers[$lastHeader])) {
+ end($this->headers[$lastHeader]);
+ $this->headers[$lastHeader][key($this->headers[$lastHeader])] .= $m[1];
+ } else {
+ $this->headers[$lastHeader] .= $m[1];
+ }
+ }
+ }
+ }
+
+ /**
+ * Return the body of the HTTP response.
+ *
+ * @return string HTTP response body.
+ */
+ public function getBody()
+ {
+ $content = @stream_get_contents($this->_stream);
+ if ($content === false) {
+ throw new Horde_Http_Client_Exception('Problem reading data from ' . $this->uri . ': ' . $php_errormsg);
+ }
+ return $content;
+ }
+
+ /**
+ * Return a stream pointing to the response body that can be used
+ * with all standard PHP stream functions.
+ */
+ public function getStream()
+ {
+ return $this->_stream;
+ }
+
+ /**
+ * Get the value of a single response header.
+ *
+ * @param string $header Header name to get ('Content-Type', 'Content-Length', etc.). This is case sensitive.
+ *
+ * @return string HTTP header value.
+ */
+ public function getHeader($header)
+ {
+ return isset($this->headers[$header]) ? $this->headers[$header] : null;
+ }
+
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
+http://pear.php.net/dtd/tasks-1.0.xsd
+http://pear.php.net/dtd/package-2.0
+http://pear.php.net/dtd/package-2.0.xsd">
+ <name>Http_Client</name>
+ <channel>pear.horde.org</channel>
+ <summary>Horde HTTP client libraries</summary>
+ <description>This package provides a light wrapper around PHP's stream support for performing HTTP requests.
+ </description>
+ <lead>
+ <name>Chuck Hagenbuch</name>
+ <user>chuck</user>
+ <email>chuck@horde.org</email>
+ <active>yes</active>
+ </lead>
+ <date>2008-09-06</date>
+ <version>
+ <release>0.3.0</release>
+ <api>0.3.0</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <license uri="http://opensource.org/licenses/bsd-license.php">BSD</license>
+ <notes>* Refactor the constructor and setter/getter methods to be more generic.
+* Add support for proxy servers.</notes>
+ <contents>
+ <dir name="/">
+ <dir name="lib">
+ <dir name="Horde">
+ <dir name="Http">
+ <dir name="Client">
+ <file name="Exception.php" role="php" />
+ <file name="Mock.php" role="php" />
+ <file name="Response.php" role="php" />
+ </dir> <!-- /lib/Horde/Http/Client -->
+ <file name="Client.php" role="php" />
+ </dir> <!-- /lib/Horde/Http -->
+ </dir> <!-- /lib/Horde -->
+ </dir> <!-- /lib/ -->
+ </dir> <!-- / -->
+ </contents>
+ <dependencies>
+ <required>
+ <php>
+ <min>5.2.0</min>
+ </php>
+ <pearinstaller>
+ <min>1.5.0</min>
+ </pearinstaller>
+ </required>
+ </dependencies>
+ <phprelease>
+ <filelist>
+ <install name="lib/Horde/Http/Client/Exception.php" as="Horde/Http/Client/Exception.php" />
+ <install name="lib/Horde/Http/Client/Mock.php" as="Horde/Http/Client/Mock.php" />
+ <install name="lib/Horde/Http/Client/Response.php" as="Horde/Http/Client/Response.php" />
+ <install name="lib/Horde/Http/Client.php" as="Horde/Http/Client.php" />
+ </filelist>
+ </phprelease>
+</package>