PageRenderTime 53ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Cake/Network/Http/HttpSocket.php

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