1<?php
2/**
3 * Copyright 2007-2016 Horde LLC (http://www.horde.org/)
4 *
5 * @author   Chuck Hagenbuch <chuck@horde.org>
6 * @license  http://www.horde.org/licenses/bsd BSD
7 * @category Horde
8 * @package  Http
9 */
10
11/**
12 * An HTTP client.
13 *
14 * @author   Chuck Hagenbuch <chuck@horde.org>
15 * @license  http://www.horde.org/licenses/bsd BSD
16 * @category Horde
17 * @package  Http
18 *
19 * @property       boolean      $httpMethodOverride
20 *                              @see $_httpMethodOverride
21 * @property       Horde_Http_Request_Base $request
22 *                              A concrete request instance.
23 * @property-write string|Horde_Url $request.uri
24 *                              Default URI if not specified for individual
25 *                              requests.
26 * @property-write array        $request.headers
27 *                              Hash with additional request headers.
28 * @property-write string       $request.method
29 *                              Default request method if not specified for
30 *                              individual requests.
31 * @property-write array|string $request.data
32 *                              POST data fields or POST/PUT data body.
33 * @property-write string       $request.username
34 *                              Authentication user name.
35 * @property-write string       $request.password
36 *                              Authentication password.
37 * @property-write string       $request.authenticationScheme
38 *                              Authentication method, one of the
39 *                              Horde_Http::AUTH_* constants.
40 * @property-write string       $request.proxyServer
41 *                              Host name of a proxy server.
42 * @property-write integer      $request.proxyPort
43 *                              Port number of a proxy server.
44 * @property-write integer      $request.proxyType
45 *                              Proxy server type, one of the
46 *                              Horde_Http::PROXY_* constants.
47 * @property-write string       $request.proxyUsername
48 *                              Proxy authentication user name.
49 * @property-write string       $request.proxyPassword
50 *                              Proxy authentication password.
51 * @property-write string       $request.proxyAuthenticationScheme
52 *                              Proxy authentication method, one of the
53 *                              Horde_Http::AUTH_* constants.
54 * @property-write integer      $request.redirects
55 *                              Maximum number of redirects to follow.
56 * @property-write integer      $request.timeout
57 *                              Timeout in seconds.
58 * @property-write boolean      $request.verifyPeer
59 *                              Verify SSL peer certificates?
60 */
61class Horde_Http_Client
62{
63    /**
64     * The current HTTP request.
65     *
66     * @var Horde_Http_Request_Base
67     */
68    protected $_request;
69
70    /**
71     * The previous HTTP request.
72     *
73     * @var Horde_Http_Request_Base
74     */
75    protected $_lastRequest;
76
77    /**
78     * The most recent HTTP response.
79     *
80     * @var Horde_Http_Response_Base
81     */
82    protected $_lastResponse;
83
84    /**
85     * Use POST instead of PUT and DELETE, sending X-HTTP-Method-Override with
86     * the intended method name instead.
87     *
88     * @var boolean
89     */
90    protected $_httpMethodOverride = false;
91
92    /**
93     * Horde_Http_Client constructor.
94     *
95     * @param array $args Any Http_Client settings to initialize in the
96     *                    constructor. See the class properties for available
97     *                    settings.
98     */
99    public function __construct($args = array())
100    {
101        // Set or create request object
102        if (isset($args['request'])) {
103            $this->_request = $args['request'];
104            unset($args['request']);
105        } else {
106            $requestFactory = new Horde_Http_Request_Factory();
107            $this->_request = $requestFactory->create();
108        }
109
110        foreach ($args as $key => $val) {
111            $this->$key = $val;
112        }
113    }
114
115    /**
116     * Sends a GET request.
117     *
118     * @param string $uri     Request URI.
119     * @param array $headers  Additional request headers.
120     *
121     * @throws Horde_Http_Exception
122     * @return Horde_Http_Response_Base
123     */
124    public function get($uri = null, $headers = array())
125    {
126        return $this->request('GET', $uri, null, $headers);
127    }
128
129    /**
130     * Sends a POST request.
131     *
132     * @param string $uri         Request URI.
133     * @param array|string $data  Data fields or data body.
134     * @param array $headers      Additional request headers.
135     *
136     * @throws Horde_Http_Exception
137     * @return Horde_Http_Response_Base
138     */
139    public function post($uri = null, $data = null, $headers = array())
140    {
141        return $this->request('POST', $uri, $data, $headers);
142    }
143
144    /**
145     * Sends a PUT request.
146     *
147     * @param string $uri     Request URI.
148     * @param string $data    Data body.
149     * @param array $headers  Additional request headers.
150     *
151     * @throws Horde_Http_Exception
152     * @return Horde_Http_Response_Base
153     */
154    public function put($uri = null, $data = null, $headers = array())
155    {
156        if ($this->_httpMethodOverride) {
157            $headers = array_merge(
158                array('X-HTTP-Method-Override' => 'PUT'),
159                $headers
160            );
161            return $this->post($uri, $data, $headers);
162        }
163
164        return $this->request('PUT', $uri, $data, $headers);
165    }
166
167    /**
168     * Sends a DELETE request.
169     *
170     * @param string $uri     Request URI.
171     * @param array $headers  Additional request headers.
172     *
173     * @throws Horde_Http_Exception
174     * @return Horde_Http_Response_Base
175     */
176    public function delete($uri = null, $headers = array())
177    {
178        if ($this->_httpMethodOverride) {
179            $headers = array_merge(
180                array('X-HTTP-Method-Override' => 'DELETE'),
181                $headers
182            );
183            return $this->post($uri, null, $headers);
184        }
185
186        return $this->request('DELETE', $uri, null, $headers);
187    }
188
189    /**
190     * Sends a HEAD request.
191     *
192     * @param string $uri     Request URI.
193     * @param array $headers  Additional request headers.
194     *
195     * @throws Horde_Http_Exception
196     * @return Horde_Http_Response_Base
197     */
198    public function head($uri = null, $headers = array())
199    {
200        return $this->request('HEAD', $uri, null, $headers);
201    }
202
203    /**
204     * Sends an HTTP request.
205     *
206     * @param string $method         HTTP request method (GET, PUT, etc.)
207     * @param string|Horde_Url $uri  URI to request, if different from
208     *                               $this->uri
209     * @param string|array $data     Request data. Array of form data that will
210     *                               be encoded automatically, or a raw string.
211     * @param array $headers         Any headers specific to this request. They
212     *                               will be combined with $this->_headers, and
213     *                               override headers of the same name for this
214     *                               request only.
215     *
216     * @throws Horde_Http_Exception
217     * @return Horde_Http_Response_Base
218     */
219    public function request(
220        $method, $uri = null, $data = null, $headers = array()
221    )
222    {
223        if ($method !== null) {
224            $this->request->method = $method;
225        }
226        if ($uri !== null) {
227            $this->request->uri = $uri;
228        }
229        if ($data !== null) {
230            $this->request->data = $data;
231        }
232        if (count($headers)) {
233            $this->request->setHeaders($headers);
234        }
235
236        $this->_lastRequest = $this->_request;
237        $this->_lastResponse = $this->_request->send();
238
239        return $this->_lastResponse;
240    }
241
242    /**
243     * Returns a client parameter.
244     *
245     * @param string $name  The parameter to return.
246     *
247     * @return mixed  The parameter value.
248     */
249    public function __get($name)
250    {
251        return isset($this->{'_' . $name}) ? $this->{'_' . $name} : null;
252    }
253
254    /**
255     * Sets a client parameter.
256     *
257     * @param string $name  The parameter to set.
258     * @param mixed $value  The parameter value.
259     */
260    public function __set($name, $value)
261    {
262        if ((strpos($name, '.') === false)) {
263            if (isset($this->{'_' . $name})) {
264                $this->{'_' . $name} = $value;
265                return true;
266            } else {
267                throw new Horde_Http_Exception('unknown parameter: "' . $name . '"');
268            }
269        }
270
271        list($object, $objectkey) = explode('.', $name, 2);
272        if ($object == 'request') {
273            $this->$object->$objectkey = $value;
274            return true;
275        } elseif ($object == 'client') {
276            $objectKey = '_' . $objectKey;
277            $this->$objectKey = $value;
278            return true;
279        }
280
281        throw new Horde_Http_Exception('unknown parameter: "' . $name . '"');
282    }
283}
284