pictcode / lib / Cake / Network / Http / HttpSocket.php @ master
履歴 | 表示 | アノテート | ダウンロード (31.605 KB)
1 | 635eef61 | spyder1211 | <?php
|
---|---|---|---|
2 | /**
|
||
3 | * HTTP Socket connection class.
|
||
4 | *
|
||
5 | * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
|
||
6 | * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||
7 | *
|
||
8 | * Licensed under The MIT License
|
||
9 | * For full copyright and license information, please see the LICENSE.txt
|
||
10 | * Redistributions of files must retain the above copyright notice.
|
||
11 | *
|
||
12 | * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
|
||
13 | * @link http://cakephp.org CakePHP(tm) Project
|
||
14 | * @package Cake.Network.Http
|
||
15 | * @since CakePHP(tm) v 1.2.0
|
||
16 | * @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||
17 | */
|
||
18 | |||
19 | App::uses('CakeSocket', 'Network'); |
||
20 | App::uses('Router', 'Routing'); |
||
21 | App::uses('Hash', 'Utility'); |
||
22 | |||
23 | /**
|
||
24 | * CakePHP network socket connection class.
|
||
25 | *
|
||
26 | * Core base class for HTTP network communication. HttpSocket can be used as an
|
||
27 | * Object Oriented replacement for cURL in many places.
|
||
28 | *
|
||
29 | * @package Cake.Network.Http
|
||
30 | */
|
||
31 | class HttpSocket extends CakeSocket { |
||
32 | |||
33 | /**
|
||
34 | * When one activates the $quirksMode by setting it to true, all checks meant to
|
||
35 | * enforce RFC 2616 (HTTP/1.1 specs).
|
||
36 | * will be disabled and additional measures to deal with non-standard responses will be enabled.
|
||
37 | *
|
||
38 | * @var bool
|
||
39 | */
|
||
40 | public $quirksMode = false; |
||
41 | |||
42 | /**
|
||
43 | * Contain information about the last request (read only)
|
||
44 | *
|
||
45 | * @var array
|
||
46 | */
|
||
47 | public $request = array( |
||
48 | 'method' => 'GET', |
||
49 | 'uri' => array( |
||
50 | 'scheme' => 'http', |
||
51 | 'host' => null, |
||
52 | 'port' => 80, |
||
53 | 'user' => null, |
||
54 | 'pass' => null, |
||
55 | 'path' => null, |
||
56 | 'query' => null, |
||
57 | 'fragment' => null |
||
58 | ), |
||
59 | 'version' => '1.1', |
||
60 | 'body' => '', |
||
61 | 'line' => null, |
||
62 | 'header' => array( |
||
63 | 'Connection' => 'close', |
||
64 | 'User-Agent' => 'CakePHP' |
||
65 | ), |
||
66 | 'raw' => null, |
||
67 | 'redirect' => false, |
||
68 | 'cookies' => array(), |
||
69 | ); |
||
70 | |||
71 | /**
|
||
72 | * Contain information about the last response (read only)
|
||
73 | *
|
||
74 | * @var array
|
||
75 | */
|
||
76 | public $response = null; |
||
77 | |||
78 | /**
|
||
79 | * Response class name
|
||
80 | *
|
||
81 | * @var string
|
||
82 | */
|
||
83 | public $responseClass = 'HttpSocketResponse'; |
||
84 | |||
85 | /**
|
||
86 | * Configuration settings for the HttpSocket and the requests
|
||
87 | *
|
||
88 | * @var array
|
||
89 | */
|
||
90 | public $config = array( |
||
91 | 'persistent' => false, |
||
92 | 'host' => 'localhost', |
||
93 | 'protocol' => 'tcp', |
||
94 | 'port' => 80, |
||
95 | 'timeout' => 30, |
||
96 | 'ssl_verify_peer' => true, |
||
97 | 'ssl_allow_self_signed' => false, |
||
98 | 'ssl_verify_depth' => 5, |
||
99 | 'ssl_verify_host' => true, |
||
100 | 'request' => array( |
||
101 | 'uri' => array( |
||
102 | 'scheme' => array('http', 'https'), |
||
103 | 'host' => 'localhost', |
||
104 | 'port' => array(80, 443) |
||
105 | ), |
||
106 | 'redirect' => false, |
||
107 | 'cookies' => array(), |
||
108 | ) |
||
109 | ); |
||
110 | |||
111 | /**
|
||
112 | * Authentication settings
|
||
113 | *
|
||
114 | * @var array
|
||
115 | */
|
||
116 | protected $_auth = array(); |
||
117 | |||
118 | /**
|
||
119 | * Proxy settings
|
||
120 | *
|
||
121 | * @var array
|
||
122 | */
|
||
123 | protected $_proxy = array(); |
||
124 | |||
125 | /**
|
||
126 | * Resource to receive the content of request
|
||
127 | *
|
||
128 | * @var mixed
|
||
129 | */
|
||
130 | protected $_contentResource = null; |
||
131 | |||
132 | /**
|
||
133 | * Build an HTTP Socket using the specified configuration.
|
||
134 | *
|
||
135 | * You can use a URL string to set the URL and use default configurations for
|
||
136 | * all other options:
|
||
137 | *
|
||
138 | * `$http = new HttpSocket('http://cakephp.org/');`
|
||
139 | *
|
||
140 | * Or use an array to configure multiple options:
|
||
141 | *
|
||
142 | * ```
|
||
143 | * $http = new HttpSocket(array(
|
||
144 | * 'host' => 'cakephp.org',
|
||
145 | * 'timeout' => 20
|
||
146 | * ));
|
||
147 | * ```
|
||
148 | *
|
||
149 | * See HttpSocket::$config for options that can be used.
|
||
150 | *
|
||
151 | * @param string|array $config Configuration information, either a string URL or an array of options.
|
||
152 | */
|
||
153 | public function __construct($config = array()) { |
||
154 | if (is_string($config)) { |
||
155 | $this->_configUri($config); |
||
156 | } elseif (is_array($config)) { |
||
157 | if (isset($config['request']['uri']) && is_string($config['request']['uri'])) { |
||
158 | $this->_configUri($config['request']['uri']); |
||
159 | unset($config['request']['uri']); |
||
160 | } |
||
161 | $this->config = Hash::merge($this->config, $config); |
||
162 | } |
||
163 | parent::__construct($this->config); |
||
164 | } |
||
165 | |||
166 | /**
|
||
167 | * Set authentication settings.
|
||
168 | *
|
||
169 | * Accepts two forms of parameters. If all you need is a username + password, as with
|
||
170 | * Basic authentication you can do the following:
|
||
171 | *
|
||
172 | * ```
|
||
173 | * $http->configAuth('Basic', 'mark', 'secret');
|
||
174 | * ```
|
||
175 | *
|
||
176 | * If you are using an authentication strategy that requires more inputs, like Digest authentication
|
||
177 | * you can call `configAuth()` with an array of user information.
|
||
178 | *
|
||
179 | * ```
|
||
180 | * $http->configAuth('Digest', array(
|
||
181 | * 'user' => 'mark',
|
||
182 | * 'pass' => 'secret',
|
||
183 | * 'realm' => 'my-realm',
|
||
184 | * 'nonce' => 1235
|
||
185 | * ));
|
||
186 | * ```
|
||
187 | *
|
||
188 | * To remove any set authentication strategy, call `configAuth()` with no parameters:
|
||
189 | *
|
||
190 | * `$http->configAuth();`
|
||
191 | *
|
||
192 | * @param string $method Authentication method (ie. Basic, Digest). If empty, disable authentication
|
||
193 | * @param string|array $user Username for authentication. Can be an array with settings to authentication class
|
||
194 | * @param string $pass Password for authentication
|
||
195 | * @return void
|
||
196 | */
|
||
197 | public function configAuth($method, $user = null, $pass = null) { |
||
198 | if (empty($method)) { |
||
199 | $this->_auth = array(); |
||
200 | return;
|
||
201 | } |
||
202 | if (is_array($user)) { |
||
203 | $this->_auth = array($method => $user); |
||
204 | return;
|
||
205 | } |
||
206 | $this->_auth = array($method => compact('user', 'pass')); |
||
207 | } |
||
208 | |||
209 | /**
|
||
210 | * Set proxy settings
|
||
211 | *
|
||
212 | * @param string|array $host Proxy host. Can be an array with settings to authentication class
|
||
213 | * @param int $port Port. Default 3128.
|
||
214 | * @param string $method Proxy method (ie, Basic, Digest). If empty, disable proxy authentication
|
||
215 | * @param string $user Username if your proxy need authentication
|
||
216 | * @param string $pass Password to proxy authentication
|
||
217 | * @return void
|
||
218 | */
|
||
219 | public function configProxy($host, $port = 3128, $method = null, $user = null, $pass = null) { |
||
220 | if (empty($host)) { |
||
221 | $this->_proxy = array(); |
||
222 | return;
|
||
223 | } |
||
224 | if (is_array($host)) { |
||
225 | $this->_proxy = $host + array('host' => null); |
||
226 | return;
|
||
227 | } |
||
228 | $this->_proxy = compact('host', 'port', 'method', 'user', 'pass'); |
||
229 | } |
||
230 | |||
231 | /**
|
||
232 | * Set the resource to receive the request content. This resource must support fwrite.
|
||
233 | *
|
||
234 | * @param resource|bool $resource Resource or false to disable the resource use
|
||
235 | * @return void
|
||
236 | * @throws SocketException
|
||
237 | */
|
||
238 | public function setContentResource($resource) { |
||
239 | if ($resource === false) { |
||
240 | $this->_contentResource = null; |
||
241 | return;
|
||
242 | } |
||
243 | if (!is_resource($resource)) { |
||
244 | throw new SocketException(__d('cake_dev', 'Invalid resource.')); |
||
245 | } |
||
246 | $this->_contentResource = $resource; |
||
247 | } |
||
248 | |||
249 | /**
|
||
250 | * Issue the specified request. HttpSocket::get() and HttpSocket::post() wrap this
|
||
251 | * method and provide a more granular interface.
|
||
252 | *
|
||
253 | * @param string|array $request Either an URI string, or an array defining host/uri
|
||
254 | * @return mixed false on error, HttpSocketResponse on success
|
||
255 | * @throws SocketException
|
||
256 | */
|
||
257 | public function request($request = array()) { |
||
258 | $this->reset(false); |
||
259 | |||
260 | if (is_string($request)) { |
||
261 | $request = array('uri' => $request); |
||
262 | } elseif (!is_array($request)) { |
||
263 | return false; |
||
264 | } |
||
265 | |||
266 | if (!isset($request['uri'])) { |
||
267 | $request['uri'] = null; |
||
268 | } |
||
269 | $uri = $this->_parseUri($request['uri']); |
||
270 | if (!isset($uri['host'])) { |
||
271 | $host = $this->config['host']; |
||
272 | } |
||
273 | if (isset($request['host'])) { |
||
274 | $host = $request['host']; |
||
275 | unset($request['host']); |
||
276 | } |
||
277 | $request['uri'] = $this->url($request['uri']); |
||
278 | $request['uri'] = $this->_parseUri($request['uri'], true); |
||
279 | $this->request = Hash::merge($this->request, array_diff_key($this->config['request'], array('cookies' => true)), $request); |
||
280 | |||
281 | $this->_configUri($this->request['uri']); |
||
282 | |||
283 | $Host = $this->request['uri']['host']; |
||
284 | if (!empty($this->config['request']['cookies'][$Host])) { |
||
285 | if (!isset($this->request['cookies'])) { |
||
286 | $this->request['cookies'] = array(); |
||
287 | } |
||
288 | if (!isset($request['cookies'])) { |
||
289 | $request['cookies'] = array(); |
||
290 | } |
||
291 | $this->request['cookies'] = array_merge($this->request['cookies'], $this->config['request']['cookies'][$Host], $request['cookies']); |
||
292 | } |
||
293 | |||
294 | if (isset($host)) { |
||
295 | $this->config['host'] = $host; |
||
296 | } |
||
297 | |||
298 | $this->_setProxy();
|
||
299 | $this->request['proxy'] = $this->_proxy; |
||
300 | |||
301 | $cookies = null; |
||
302 | |||
303 | if (is_array($this->request['header'])) { |
||
304 | if (!empty($this->request['cookies'])) { |
||
305 | $cookies = $this->buildCookies($this->request['cookies']); |
||
306 | } |
||
307 | $scheme = ''; |
||
308 | $port = 0; |
||
309 | if (isset($this->request['uri']['scheme'])) { |
||
310 | $scheme = $this->request['uri']['scheme']; |
||
311 | } |
||
312 | if (isset($this->request['uri']['port'])) { |
||
313 | $port = $this->request['uri']['port']; |
||
314 | } |
||
315 | if (($scheme === 'http' && $port != 80) || |
||
316 | ($scheme === 'https' && $port != 443) || |
||
317 | ($port != 80 && $port != 443) |
||
318 | ) { |
||
319 | $Host .= ':' . $port; |
||
320 | } |
||
321 | $this->request['header'] = array_merge(compact('Host'), $this->request['header']); |
||
322 | } |
||
323 | |||
324 | if (isset($this->request['uri']['user'], $this->request['uri']['pass'])) { |
||
325 | $this->configAuth('Basic', $this->request['uri']['user'], $this->request['uri']['pass']); |
||
326 | } elseif (isset($this->request['auth'], $this->request['auth']['method'], $this->request['auth']['user'], $this->request['auth']['pass'])) { |
||
327 | $this->configAuth($this->request['auth']['method'], $this->request['auth']['user'], $this->request['auth']['pass']); |
||
328 | } |
||
329 | $authHeader = Hash::get($this->request, 'header.Authorization'); |
||
330 | if (empty($authHeader)) { |
||
331 | $this->_setAuth();
|
||
332 | $this->request['auth'] = $this->_auth; |
||
333 | } |
||
334 | |||
335 | if (is_array($this->request['body'])) { |
||
336 | $this->request['body'] = http_build_query($this->request['body'], '', '&'); |
||
337 | } |
||
338 | |||
339 | if (!empty($this->request['body']) && !isset($this->request['header']['Content-Type'])) { |
||
340 | $this->request['header']['Content-Type'] = 'application/x-www-form-urlencoded'; |
||
341 | } |
||
342 | |||
343 | if (!empty($this->request['body']) && !isset($this->request['header']['Content-Length'])) { |
||
344 | $this->request['header']['Content-Length'] = strlen($this->request['body']); |
||
345 | } |
||
346 | if (isset($this->request['uri']['scheme']) && $this->request['uri']['scheme'] === 'https' && in_array($this->config['protocol'], array(false, 'tcp'))) { |
||
347 | $this->config['protocol'] = 'ssl'; |
||
348 | } |
||
349 | |||
350 | $connectionType = null; |
||
351 | if (isset($this->request['header']['Connection'])) { |
||
352 | $connectionType = $this->request['header']['Connection']; |
||
353 | } |
||
354 | $this->request['header'] = $this->_buildHeader($this->request['header']) . $cookies; |
||
355 | |||
356 | if (empty($this->request['line'])) { |
||
357 | $this->request['line'] = $this->_buildRequestLine($this->request); |
||
358 | } |
||
359 | |||
360 | if ($this->quirksMode === false && $this->request['line'] === false) { |
||
361 | return false; |
||
362 | } |
||
363 | |||
364 | $this->request['raw'] = ''; |
||
365 | if ($this->request['line'] !== false) { |
||
366 | $this->request['raw'] = $this->request['line']; |
||
367 | } |
||
368 | |||
369 | if ($this->request['header'] !== false) { |
||
370 | $this->request['raw'] .= $this->request['header']; |
||
371 | } |
||
372 | |||
373 | $this->request['raw'] .= "\r\n"; |
||
374 | $this->request['raw'] .= $this->request['body']; |
||
375 | |||
376 | // SSL context is set during the connect() method.
|
||
377 | $this->write($this->request['raw']); |
||
378 | |||
379 | $response = null; |
||
380 | $inHeader = true; |
||
381 | while (($data = $this->read()) !== false) { |
||
382 | if ($this->_contentResource) { |
||
383 | if ($inHeader) { |
||
384 | $response .= $data; |
||
385 | $pos = strpos($response, "\r\n\r\n"); |
||
386 | if ($pos !== false) { |
||
387 | $pos += 4; |
||
388 | $data = substr($response, $pos); |
||
389 | fwrite($this->_contentResource, $data); |
||
390 | |||
391 | $response = substr($response, 0, $pos); |
||
392 | $inHeader = false; |
||
393 | } |
||
394 | } else {
|
||
395 | fwrite($this->_contentResource, $data); |
||
396 | fflush($this->_contentResource); |
||
397 | } |
||
398 | } else {
|
||
399 | $response .= $data; |
||
400 | } |
||
401 | } |
||
402 | |||
403 | if ($connectionType === 'close') { |
||
404 | $this->disconnect();
|
||
405 | } |
||
406 | |||
407 | list($plugin, $responseClass) = pluginSplit($this->responseClass, true); |
||
408 | App::uses($responseClass, $plugin . 'Network/Http'); |
||
409 | if (!class_exists($responseClass)) { |
||
410 | throw new SocketException(__d('cake_dev', 'Class %s not found.', $this->responseClass)); |
||
411 | } |
||
412 | $this->response = new $responseClass($response); |
||
413 | |||
414 | if (!empty($this->response->cookies)) { |
||
415 | if (!isset($this->config['request']['cookies'][$Host])) { |
||
416 | $this->config['request']['cookies'][$Host] = array(); |
||
417 | } |
||
418 | $this->config['request']['cookies'][$Host] = array_merge($this->config['request']['cookies'][$Host], $this->response->cookies); |
||
419 | } |
||
420 | |||
421 | if ($this->request['redirect'] && $this->response->isRedirect()) { |
||
422 | $location = trim($this->response->getHeader('Location'), '='); |
||
423 | $request['uri'] = str_replace('%2F', '/', $location); |
||
424 | $request['redirect'] = is_int($this->request['redirect']) ? $this->request['redirect'] - 1 : $this->request['redirect']; |
||
425 | $this->response = $this->request($request); |
||
426 | } |
||
427 | |||
428 | return $this->response; |
||
429 | } |
||
430 | |||
431 | /**
|
||
432 | * Issues a GET request to the specified URI, query, and request.
|
||
433 | *
|
||
434 | * Using a string uri and an array of query string parameters:
|
||
435 | *
|
||
436 | * `$response = $http->get('http://google.com/search', array('q' => 'cakephp', 'client' => 'safari'));`
|
||
437 | *
|
||
438 | * Would do a GET request to `http://google.com/search?q=cakephp&client=safari`
|
||
439 | *
|
||
440 | * You could express the same thing using a uri array and query string parameters:
|
||
441 | *
|
||
442 | * ```
|
||
443 | * $response = $http->get(
|
||
444 | * array('host' => 'google.com', 'path' => '/search'),
|
||
445 | * array('q' => 'cakephp', 'client' => 'safari')
|
||
446 | * );
|
||
447 | * ```
|
||
448 | *
|
||
449 | * @param string|array $uri URI to request. Either a string uri, or a uri array, see HttpSocket::_parseUri()
|
||
450 | * @param array $query Querystring parameters to append to URI
|
||
451 | * @param array $request An indexed array with indexes such as 'method' or uri
|
||
452 | * @return mixed Result of request, either false on failure or the response to the request.
|
||
453 | */
|
||
454 | public function get($uri = null, $query = array(), $request = array()) { |
||
455 | if (!empty($query)) { |
||
456 | $uri = $this->_parseUri($uri, $this->config['request']['uri']); |
||
457 | if (isset($uri['query'])) { |
||
458 | $uri['query'] = array_merge($uri['query'], $query); |
||
459 | } else {
|
||
460 | $uri['query'] = $query; |
||
461 | } |
||
462 | $uri = $this->_buildUri($uri); |
||
463 | } |
||
464 | |||
465 | $request = Hash::merge(array('method' => 'GET', 'uri' => $uri), $request); |
||
466 | return $this->request($request); |
||
467 | } |
||
468 | |||
469 | /**
|
||
470 | * Issues a HEAD request to the specified URI, query, and request.
|
||
471 | *
|
||
472 | * By definition HEAD request are identical to GET request except they return no response body. This means that all
|
||
473 | * information and examples relevant to GET also applys to HEAD.
|
||
474 | *
|
||
475 | * @param string|array $uri URI to request. Either a string URI, or a URI array, see HttpSocket::_parseUri()
|
||
476 | * @param array $query Querystring parameters to append to URI
|
||
477 | * @param array $request An indexed array with indexes such as 'method' or uri
|
||
478 | * @return mixed Result of request, either false on failure or the response to the request.
|
||
479 | */
|
||
480 | public function head($uri = null, $query = array(), $request = array()) { |
||
481 | if (!empty($query)) { |
||
482 | $uri = $this->_parseUri($uri, $this->config['request']['uri']); |
||
483 | if (isset($uri['query'])) { |
||
484 | $uri['query'] = array_merge($uri['query'], $query); |
||
485 | } else {
|
||
486 | $uri['query'] = $query; |
||
487 | } |
||
488 | $uri = $this->_buildUri($uri); |
||
489 | } |
||
490 | |||
491 | $request = Hash::merge(array('method' => 'HEAD', 'uri' => $uri), $request); |
||
492 | return $this->request($request); |
||
493 | } |
||
494 | |||
495 | /**
|
||
496 | * Issues a POST request to the specified URI, query, and request.
|
||
497 | *
|
||
498 | * `post()` can be used to post simple data arrays to a URL:
|
||
499 | *
|
||
500 | * ```
|
||
501 | * $response = $http->post('http://example.com', array(
|
||
502 | * 'username' => 'batman',
|
||
503 | * 'password' => 'bruce_w4yne'
|
||
504 | * ));
|
||
505 | * ```
|
||
506 | *
|
||
507 | * @param string|array $uri URI to request. See HttpSocket::_parseUri()
|
||
508 | * @param array $data Array of request body data keys and values.
|
||
509 | * @param array $request An indexed array with indexes such as 'method' or uri
|
||
510 | * @return mixed Result of request, either false on failure or the response to the request.
|
||
511 | */
|
||
512 | public function post($uri = null, $data = array(), $request = array()) { |
||
513 | $request = Hash::merge(array('method' => 'POST', 'uri' => $uri, 'body' => $data), $request); |
||
514 | return $this->request($request); |
||
515 | } |
||
516 | |||
517 | /**
|
||
518 | * Issues a PUT request to the specified URI, query, and request.
|
||
519 | *
|
||
520 | * @param string|array $uri URI to request, See HttpSocket::_parseUri()
|
||
521 | * @param array $data Array of request body data keys and values.
|
||
522 | * @param array $request An indexed array with indexes such as 'method' or uri
|
||
523 | * @return mixed Result of request
|
||
524 | */
|
||
525 | public function put($uri = null, $data = array(), $request = array()) { |
||
526 | $request = Hash::merge(array('method' => 'PUT', 'uri' => $uri, 'body' => $data), $request); |
||
527 | return $this->request($request); |
||
528 | } |
||
529 | |||
530 | /**
|
||
531 | * Issues a PATCH request to the specified URI, query, and request.
|
||
532 | *
|
||
533 | * @param string|array $uri URI to request, See HttpSocket::_parseUri()
|
||
534 | * @param array $data Array of request body data keys and values.
|
||
535 | * @param array $request An indexed array with indexes such as 'method' or uri
|
||
536 | * @return mixed Result of request
|
||
537 | */
|
||
538 | public function patch($uri = null, $data = array(), $request = array()) { |
||
539 | $request = Hash::merge(array('method' => 'PATCH', 'uri' => $uri, 'body' => $data), $request); |
||
540 | return $this->request($request); |
||
541 | } |
||
542 | |||
543 | /**
|
||
544 | * Issues a DELETE request to the specified URI, query, and request.
|
||
545 | *
|
||
546 | * @param string|array $uri URI to request (see {@link _parseUri()})
|
||
547 | * @param array $data Array of request body data keys and values.
|
||
548 | * @param array $request An indexed array with indexes such as 'method' or uri
|
||
549 | * @return mixed Result of request
|
||
550 | */
|
||
551 | public function delete($uri = null, $data = array(), $request = array()) { |
||
552 | $request = Hash::merge(array('method' => 'DELETE', 'uri' => $uri, 'body' => $data), $request); |
||
553 | return $this->request($request); |
||
554 | } |
||
555 | |||
556 | /**
|
||
557 | * Normalizes URLs into a $uriTemplate. If no template is provided
|
||
558 | * a default one will be used. Will generate the URL using the
|
||
559 | * current config information.
|
||
560 | *
|
||
561 | * ### Usage:
|
||
562 | *
|
||
563 | * After configuring part of the request parameters, you can use url() to generate
|
||
564 | * URLs.
|
||
565 | *
|
||
566 | * ```
|
||
567 | * $http = new HttpSocket('http://www.cakephp.org');
|
||
568 | * $url = $http->url('/search?q=bar');
|
||
569 | * ```
|
||
570 | *
|
||
571 | * Would return `http://www.cakephp.org/search?q=bar`
|
||
572 | *
|
||
573 | * url() can also be used with custom templates:
|
||
574 | *
|
||
575 | * `$url = $http->url('http://www.cakephp/search?q=socket', '/%path?%query');`
|
||
576 | *
|
||
577 | * Would return `/search?q=socket`.
|
||
578 | *
|
||
579 | * @param string|array $url Either a string or array of URL options to create a URL with.
|
||
580 | * @param string $uriTemplate A template string to use for URL formatting.
|
||
581 | * @return mixed Either false on failure or a string containing the composed URL.
|
||
582 | */
|
||
583 | public function url($url = null, $uriTemplate = null) { |
||
584 | if ($url === null) { |
||
585 | $url = '/'; |
||
586 | } |
||
587 | if (is_string($url)) { |
||
588 | $scheme = $this->config['request']['uri']['scheme']; |
||
589 | if (is_array($scheme)) { |
||
590 | $scheme = $scheme[0]; |
||
591 | } |
||
592 | $port = $this->config['request']['uri']['port']; |
||
593 | if (is_array($port)) { |
||
594 | $port = $port[0]; |
||
595 | } |
||
596 | if ($url{0} === '/') { |
||
597 | $url = $this->config['request']['uri']['host'] . ':' . $port . $url; |
||
598 | } |
||
599 | if (!preg_match('/^.+:\/\/|\*|^\//', $url)) { |
||
600 | $url = $scheme . '://' . $url; |
||
601 | } |
||
602 | } elseif (!is_array($url) && !empty($url)) { |
||
603 | return false; |
||
604 | } |
||
605 | |||
606 | $base = array_merge($this->config['request']['uri'], array('scheme' => array('http', 'https'), 'port' => array(80, 443))); |
||
607 | $url = $this->_parseUri($url, $base); |
||
608 | |||
609 | if (empty($url)) { |
||
610 | $url = $this->config['request']['uri']; |
||
611 | } |
||
612 | |||
613 | if (!empty($uriTemplate)) { |
||
614 | return $this->_buildUri($url, $uriTemplate); |
||
615 | } |
||
616 | return $this->_buildUri($url); |
||
617 | } |
||
618 | |||
619 | /**
|
||
620 | * Set authentication in request
|
||
621 | *
|
||
622 | * @return void
|
||
623 | * @throws SocketException
|
||
624 | */
|
||
625 | protected function _setAuth() { |
||
626 | if (empty($this->_auth)) { |
||
627 | return;
|
||
628 | } |
||
629 | $method = key($this->_auth); |
||
630 | list($plugin, $authClass) = pluginSplit($method, true); |
||
631 | $authClass = Inflector::camelize($authClass) . 'Authentication'; |
||
632 | App::uses($authClass, $plugin . 'Network/Http'); |
||
633 | |||
634 | if (!class_exists($authClass)) { |
||
635 | throw new SocketException(__d('cake_dev', 'Unknown authentication method.')); |
||
636 | } |
||
637 | if (!method_exists($authClass, 'authentication')) { |
||
638 | throw new SocketException(__d('cake_dev', 'The %s does not support authentication.', $authClass)); |
||
639 | } |
||
640 | call_user_func_array("$authClass::authentication", array($this, &$this->_auth[$method])); |
||
641 | } |
||
642 | |||
643 | /**
|
||
644 | * Set the proxy configuration and authentication
|
||
645 | *
|
||
646 | * @return void
|
||
647 | * @throws SocketException
|
||
648 | */
|
||
649 | protected function _setProxy() { |
||
650 | if (empty($this->_proxy) || !isset($this->_proxy['host'], $this->_proxy['port'])) { |
||
651 | return;
|
||
652 | } |
||
653 | $this->config['host'] = $this->_proxy['host']; |
||
654 | $this->config['port'] = $this->_proxy['port']; |
||
655 | $this->config['proxy'] = true; |
||
656 | |||
657 | if (empty($this->_proxy['method']) || !isset($this->_proxy['user'], $this->_proxy['pass'])) { |
||
658 | return;
|
||
659 | } |
||
660 | list($plugin, $authClass) = pluginSplit($this->_proxy['method'], true); |
||
661 | $authClass = Inflector::camelize($authClass) . 'Authentication'; |
||
662 | App::uses($authClass, $plugin . 'Network/Http'); |
||
663 | |||
664 | if (!class_exists($authClass)) { |
||
665 | throw new SocketException(__d('cake_dev', 'Unknown authentication method for proxy.')); |
||
666 | } |
||
667 | if (!method_exists($authClass, 'proxyAuthentication')) { |
||
668 | throw new SocketException(__d('cake_dev', 'The %s does not support proxy authentication.', $authClass)); |
||
669 | } |
||
670 | call_user_func_array("$authClass::proxyAuthentication", array($this, &$this->_proxy)); |
||
671 | |||
672 | if (!empty($this->request['header']['Proxy-Authorization'])) { |
||
673 | $this->config['proxyauth'] = $this->request['header']['Proxy-Authorization']; |
||
674 | if ($this->request['uri']['scheme'] === 'https') { |
||
675 | $this->request['header'] = Hash::remove($this->request['header'], 'Proxy-Authorization'); |
||
676 | } |
||
677 | } |
||
678 | } |
||
679 | |||
680 | /**
|
||
681 | * Parses and sets the specified URI into current request configuration.
|
||
682 | *
|
||
683 | * @param string|array $uri URI, See HttpSocket::_parseUri()
|
||
684 | * @return bool If uri has merged in config
|
||
685 | */
|
||
686 | protected function _configUri($uri = null) { |
||
687 | if (empty($uri)) { |
||
688 | return false; |
||
689 | } |
||
690 | |||
691 | if (is_array($uri)) { |
||
692 | $uri = $this->_parseUri($uri); |
||
693 | } else {
|
||
694 | $uri = $this->_parseUri($uri, true); |
||
695 | } |
||
696 | |||
697 | if (!isset($uri['host'])) { |
||
698 | return false; |
||
699 | } |
||
700 | $config = array( |
||
701 | 'request' => array( |
||
702 | 'uri' => array_intersect_key($uri, $this->config['request']['uri']) |
||
703 | ) |
||
704 | ); |
||
705 | $this->config = Hash::merge($this->config, $config); |
||
706 | $this->config = Hash::merge($this->config, array_intersect_key($this->config['request']['uri'], $this->config)); |
||
707 | return true; |
||
708 | } |
||
709 | |||
710 | /**
|
||
711 | * Takes a $uri array and turns it into a fully qualified URL string
|
||
712 | *
|
||
713 | * @param string|array $uri Either A $uri array, or a request string. Will use $this->config if left empty.
|
||
714 | * @param string $uriTemplate The Uri template/format to use.
|
||
715 | * @return mixed A fully qualified URL formatted according to $uriTemplate, or false on failure
|
||
716 | */
|
||
717 | protected function _buildUri($uri = array(), $uriTemplate = '%scheme://%user:%pass@%host:%port/%path?%query#%fragment') { |
||
718 | if (is_string($uri)) { |
||
719 | $uri = array('host' => $uri); |
||
720 | } |
||
721 | $uri = $this->_parseUri($uri, true); |
||
722 | |||
723 | if (!is_array($uri) || empty($uri)) { |
||
724 | return false; |
||
725 | } |
||
726 | |||
727 | $uri['path'] = preg_replace('/^\//', null, $uri['path']); |
||
728 | $uri['query'] = http_build_query($uri['query'], '', '&'); |
||
729 | $uri['query'] = rtrim($uri['query'], '='); |
||
730 | $stripIfEmpty = array( |
||
731 | 'query' => '?%query', |
||
732 | 'fragment' => '#%fragment', |
||
733 | 'user' => '%user:%pass@', |
||
734 | 'host' => '%host:%port/' |
||
735 | ); |
||
736 | |||
737 | foreach ($stripIfEmpty as $key => $strip) { |
||
738 | if (empty($uri[$key])) { |
||
739 | $uriTemplate = str_replace($strip, null, $uriTemplate); |
||
740 | } |
||
741 | } |
||
742 | |||
743 | $defaultPorts = array('http' => 80, 'https' => 443); |
||
744 | if (array_key_exists($uri['scheme'], $defaultPorts) && $defaultPorts[$uri['scheme']] == $uri['port']) { |
||
745 | $uriTemplate = str_replace(':%port', null, $uriTemplate); |
||
746 | } |
||
747 | foreach ($uri as $property => $value) { |
||
748 | $uriTemplate = str_replace('%' . $property, $value, $uriTemplate); |
||
749 | } |
||
750 | |||
751 | if ($uriTemplate === '/*') { |
||
752 | $uriTemplate = '*'; |
||
753 | } |
||
754 | return $uriTemplate; |
||
755 | } |
||
756 | |||
757 | /**
|
||
758 | * Parses the given URI and breaks it down into pieces as an indexed array with elements
|
||
759 | * such as 'scheme', 'port', 'query'.
|
||
760 | *
|
||
761 | * @param string|array $uri URI to parse
|
||
762 | * @param bool|array $base If true use default URI config, otherwise indexed array to set 'scheme', 'host', 'port', etc.
|
||
763 | * @return array Parsed URI
|
||
764 | */
|
||
765 | protected function _parseUri($uri = null, $base = array()) { |
||
766 | $uriBase = array( |
||
767 | 'scheme' => array('http', 'https'), |
||
768 | 'host' => null, |
||
769 | 'port' => array(80, 443), |
||
770 | 'user' => null, |
||
771 | 'pass' => null, |
||
772 | 'path' => '/', |
||
773 | 'query' => null, |
||
774 | 'fragment' => null |
||
775 | ); |
||
776 | |||
777 | if (is_string($uri)) { |
||
778 | $uri = parse_url($uri); |
||
779 | } |
||
780 | if (!is_array($uri) || empty($uri)) { |
||
781 | return false; |
||
782 | } |
||
783 | if ($base === true) { |
||
784 | $base = $uriBase; |
||
785 | } |
||
786 | |||
787 | if (isset($base['port'], $base['scheme']) && is_array($base['port']) && is_array($base['scheme'])) { |
||
788 | if (isset($uri['scheme']) && !isset($uri['port'])) { |
||
789 | $base['port'] = $base['port'][array_search($uri['scheme'], $base['scheme'])]; |
||
790 | } elseif (isset($uri['port']) && !isset($uri['scheme'])) { |
||
791 | $base['scheme'] = $base['scheme'][array_search($uri['port'], $base['port'])]; |
||
792 | } |
||
793 | } |
||
794 | |||
795 | if (is_array($base) && !empty($base)) { |
||
796 | $uri = array_merge($base, $uri); |
||
797 | } |
||
798 | |||
799 | if (isset($uri['scheme']) && is_array($uri['scheme'])) { |
||
800 | $uri['scheme'] = array_shift($uri['scheme']); |
||
801 | } |
||
802 | if (isset($uri['port']) && is_array($uri['port'])) { |
||
803 | $uri['port'] = array_shift($uri['port']); |
||
804 | } |
||
805 | |||
806 | if (array_key_exists('query', $uri)) { |
||
807 | $uri['query'] = $this->_parseQuery($uri['query']); |
||
808 | } |
||
809 | |||
810 | if (!array_intersect_key($uriBase, $uri)) { |
||
811 | return false; |
||
812 | } |
||
813 | return $uri; |
||
814 | } |
||
815 | |||
816 | /**
|
||
817 | * This function can be thought of as a reverse to PHP5's http_build_query(). It takes a given query string and turns it into an array and
|
||
818 | * supports nesting by using the php bracket syntax. So this means you can parse queries like:
|
||
819 | *
|
||
820 | * - ?key[subKey]=value
|
||
821 | * - ?key[]=value1&key[]=value2
|
||
822 | *
|
||
823 | * A leading '?' mark in $query is optional and does not effect the outcome of this function.
|
||
824 | * For the complete capabilities of this implementation take a look at HttpSocketTest::testparseQuery()
|
||
825 | *
|
||
826 | * @param string|array $query A query string to parse into an array or an array to return directly "as is"
|
||
827 | * @return array The $query parsed into a possibly multi-level array. If an empty $query is
|
||
828 | * given, an empty array is returned.
|
||
829 | */
|
||
830 | protected function _parseQuery($query) { |
||
831 | if (is_array($query)) { |
||
832 | return $query; |
||
833 | } |
||
834 | |||
835 | $parsedQuery = array(); |
||
836 | |||
837 | if (is_string($query) && !empty($query)) { |
||
838 | $query = preg_replace('/^\?/', '', $query); |
||
839 | $items = explode('&', $query); |
||
840 | |||
841 | foreach ($items as $item) { |
||
842 | if (strpos($item, '=') !== false) { |
||
843 | list($key, $value) = explode('=', $item, 2); |
||
844 | } else {
|
||
845 | $key = $item; |
||
846 | $value = null; |
||
847 | } |
||
848 | |||
849 | $key = urldecode($key); |
||
850 | $value = urldecode($value); |
||
851 | |||
852 | if (preg_match_all('/\[([^\[\]]*)\]/iUs', $key, $matches)) { |
||
853 | $subKeys = $matches[1]; |
||
854 | $rootKey = substr($key, 0, strpos($key, '[')); |
||
855 | if (!empty($rootKey)) { |
||
856 | array_unshift($subKeys, $rootKey); |
||
857 | } |
||
858 | $queryNode =& $parsedQuery; |
||
859 | |||
860 | foreach ($subKeys as $subKey) { |
||
861 | if (!is_array($queryNode)) { |
||
862 | $queryNode = array(); |
||
863 | } |
||
864 | |||
865 | if ($subKey === '') { |
||
866 | $queryNode[] = array(); |
||
867 | end($queryNode); |
||
868 | $subKey = key($queryNode); |
||
869 | } |
||
870 | $queryNode =& $queryNode[$subKey]; |
||
871 | } |
||
872 | $queryNode = $value; |
||
873 | continue;
|
||
874 | } |
||
875 | if (!isset($parsedQuery[$key])) { |
||
876 | $parsedQuery[$key] = $value; |
||
877 | } else {
|
||
878 | $parsedQuery[$key] = (array)$parsedQuery[$key]; |
||
879 | $parsedQuery[$key][] = $value; |
||
880 | } |
||
881 | } |
||
882 | } |
||
883 | return $parsedQuery; |
||
884 | } |
||
885 | |||
886 | /**
|
||
887 | * Builds a request line according to HTTP/1.1 specs. Activate quirks mode to work outside specs.
|
||
888 | *
|
||
889 | * @param array $request Needs to contain a 'uri' key. Should also contain a 'method' key, otherwise defaults to GET.
|
||
890 | * @return string Request line
|
||
891 | * @throws SocketException
|
||
892 | */
|
||
893 | protected function _buildRequestLine($request = array()) { |
||
894 | $asteriskMethods = array('OPTIONS'); |
||
895 | |||
896 | if (is_string($request)) { |
||
897 | $isValid = preg_match("/(.+) (.+) (.+)\r\n/U", $request, $match); |
||
898 | if (!$this->quirksMode && (!$isValid || ($match[2] === '*' && !in_array($match[3], $asteriskMethods)))) { |
||
899 | throw new SocketException(__d('cake_dev', 'HttpSocket::_buildRequestLine - Passed an invalid request line string. Activate quirks mode to do this.')); |
||
900 | } |
||
901 | return $request; |
||
902 | } elseif (!is_array($request)) { |
||
903 | return false; |
||
904 | } elseif (!array_key_exists('uri', $request)) { |
||
905 | return false; |
||
906 | } |
||
907 | |||
908 | $request['uri'] = $this->_parseUri($request['uri']); |
||
909 | $request += array('method' => 'GET'); |
||
910 | if (!empty($this->_proxy['host']) && $request['uri']['scheme'] !== 'https') { |
||
911 | $request['uri'] = $this->_buildUri($request['uri'], '%scheme://%host:%port/%path?%query'); |
||
912 | } else {
|
||
913 | $request['uri'] = $this->_buildUri($request['uri'], '/%path?%query'); |
||
914 | } |
||
915 | |||
916 | if (!$this->quirksMode && $request['uri'] === '*' && !in_array($request['method'], $asteriskMethods)) { |
||
917 | throw new SocketException(__d('cake_dev', 'HttpSocket::_buildRequestLine - The "*" asterisk character is only allowed for the following methods: %s. Activate quirks mode to work outside of HTTP/1.1 specs.', implode(',', $asteriskMethods))); |
||
918 | } |
||
919 | $version = isset($request['version']) ? $request['version'] : '1.1'; |
||
920 | return $request['method'] . ' ' . $request['uri'] . ' HTTP/' . $version . "\r\n"; |
||
921 | } |
||
922 | |||
923 | /**
|
||
924 | * Builds the header.
|
||
925 | *
|
||
926 | * @param array $header Header to build
|
||
927 | * @param string $mode Mode
|
||
928 | * @return string Header built from array
|
||
929 | */
|
||
930 | protected function _buildHeader($header, $mode = 'standard') { |
||
931 | if (is_string($header)) { |
||
932 | return $header; |
||
933 | } elseif (!is_array($header)) { |
||
934 | return false; |
||
935 | } |
||
936 | |||
937 | $fieldsInHeader = array(); |
||
938 | foreach ($header as $key => $value) { |
||
939 | $lowKey = strtolower($key); |
||
940 | if (array_key_exists($lowKey, $fieldsInHeader)) { |
||
941 | $header[$fieldsInHeader[$lowKey]] = $value; |
||
942 | unset($header[$key]); |
||
943 | } else {
|
||
944 | $fieldsInHeader[$lowKey] = $key; |
||
945 | } |
||
946 | } |
||
947 | |||
948 | $returnHeader = ''; |
||
949 | foreach ($header as $field => $contents) { |
||
950 | if (is_array($contents) && $mode === 'standard') { |
||
951 | $contents = implode(',', $contents); |
||
952 | } |
||
953 | foreach ((array)$contents as $content) { |
||
954 | $contents = preg_replace("/\r\n(?![\t ])/", "\r\n ", $content); |
||
955 | $field = $this->_escapeToken($field); |
||
956 | |||
957 | $returnHeader .= $field . ': ' . $contents . "\r\n"; |
||
958 | } |
||
959 | } |
||
960 | return $returnHeader; |
||
961 | } |
||
962 | |||
963 | /**
|
||
964 | * Builds cookie headers for a request.
|
||
965 | *
|
||
966 | * Cookies can either be in the format returned in responses, or
|
||
967 | * a simple key => value pair.
|
||
968 | *
|
||
969 | * @param array $cookies Array of cookies to send with the request.
|
||
970 | * @return string Cookie header string to be sent with the request.
|
||
971 | */
|
||
972 | public function buildCookies($cookies) { |
||
973 | $header = array(); |
||
974 | foreach ($cookies as $name => $cookie) { |
||
975 | if (is_array($cookie)) { |
||
976 | $value = $this->_escapeToken($cookie['value'], array(';')); |
||
977 | } else {
|
||
978 | $value = $this->_escapeToken($cookie, array(';')); |
||
979 | } |
||
980 | $header[] = $name . '=' . $value; |
||
981 | } |
||
982 | return $this->_buildHeader(array('Cookie' => implode('; ', $header)), 'pragmatic'); |
||
983 | } |
||
984 | |||
985 | /**
|
||
986 | * Escapes a given $token according to RFC 2616 (HTTP 1.1 specs)
|
||
987 | *
|
||
988 | * @param string $token Token to escape
|
||
989 | * @param array $chars Characters to escape
|
||
990 | * @return string Escaped token
|
||
991 | */
|
||
992 | protected function _escapeToken($token, $chars = null) { |
||
993 | $regex = '/([' . implode('', $this->_tokenEscapeChars(true, $chars)) . '])/'; |
||
994 | $token = preg_replace($regex, '"\\1"', $token); |
||
995 | return $token; |
||
996 | } |
||
997 | |||
998 | /**
|
||
999 | * Gets escape chars according to RFC 2616 (HTTP 1.1 specs).
|
||
1000 | *
|
||
1001 | * @param bool $hex true to get them as HEX values, false otherwise
|
||
1002 | * @param array $chars Characters to escape
|
||
1003 | * @return array Escape chars
|
||
1004 | */
|
||
1005 | protected function _tokenEscapeChars($hex = true, $chars = null) { |
||
1006 | if (!empty($chars)) { |
||
1007 | $escape = $chars; |
||
1008 | } else {
|
||
1009 | $escape = array('"', "(", ")", "<", ">", "@", ",", ";", ":", "\\", "/", "[", "]", "?", "=", "{", "}", " "); |
||
1010 | for ($i = 0; $i <= 31; $i++) { |
||
1011 | $escape[] = chr($i); |
||
1012 | } |
||
1013 | $escape[] = chr(127); |
||
1014 | } |
||
1015 | |||
1016 | if (!$hex) { |
||
1017 | return $escape; |
||
1018 | } |
||
1019 | foreach ($escape as $key => $char) { |
||
1020 | $escape[$key] = '\\x' . str_pad(dechex(ord($char)), 2, '0', STR_PAD_LEFT); |
||
1021 | } |
||
1022 | return $escape; |
||
1023 | } |
||
1024 | |||
1025 | /**
|
||
1026 | * Resets the state of this HttpSocket instance to it's initial state (before Object::__construct got executed) or does
|
||
1027 | * the same thing partially for the request and the response property only.
|
||
1028 | *
|
||
1029 | * @param bool $full If set to false only HttpSocket::response and HttpSocket::request are reset
|
||
1030 | * @return bool True on success
|
||
1031 | */
|
||
1032 | public function reset($full = true) { |
||
1033 | static $initalState = array(); |
||
1034 | if (empty($initalState)) { |
||
1035 | $initalState = get_class_vars(__CLASS__); |
||
1036 | } |
||
1037 | if (!$full) { |
||
1038 | $this->request = $initalState['request']; |
||
1039 | $this->response = $initalState['response']; |
||
1040 | return true; |
||
1041 | } |
||
1042 | parent::reset($initalState); |
||
1043 | return true; |
||
1044 | } |
||
1045 | |||
1046 | } |