PageRenderTime 44ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Cake/Network/CakeSocket.php

https://gitlab.com/nghiep5890/bakery
PHP | 464 lines | 253 code | 42 blank | 169 comment | 47 complexity | a99786daeb572d315a6a7deb7fb73df1 MD5 | raw file
  1. <?php
  2. /**
  3. * CakePHP 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
  15. * @since CakePHP(tm) v 1.2.0
  16. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  17. */
  18. App::uses('Validation', 'Utility');
  19. /**
  20. * CakePHP network socket connection class.
  21. *
  22. * Core base class for network communication.
  23. *
  24. * @package Cake.Network
  25. */
  26. class CakeSocket {
  27. /**
  28. * Object description
  29. *
  30. * @var string
  31. */
  32. public $description = 'Remote DataSource Network Socket Interface';
  33. /**
  34. * Base configuration settings for the socket connection
  35. *
  36. * @var array
  37. */
  38. protected $_baseConfig = array(
  39. 'persistent' => false,
  40. 'host' => 'localhost',
  41. 'protocol' => 'tcp',
  42. 'port' => 80,
  43. 'timeout' => 30
  44. );
  45. /**
  46. * Configuration settings for the socket connection
  47. *
  48. * @var array
  49. */
  50. public $config = array();
  51. /**
  52. * Reference to socket connection resource
  53. *
  54. * @var resource
  55. */
  56. public $connection = null;
  57. /**
  58. * This boolean contains the current state of the CakeSocket class
  59. *
  60. * @var bool
  61. */
  62. public $connected = false;
  63. /**
  64. * This variable contains an array with the last error number (num) and string (str)
  65. *
  66. * @var array
  67. */
  68. public $lastError = array();
  69. /**
  70. * True if the socket stream is encrypted after a CakeSocket::enableCrypto() call
  71. *
  72. * @var bool
  73. */
  74. public $encrypted = false;
  75. /**
  76. * Contains all the encryption methods available
  77. *
  78. * @var array
  79. */
  80. protected $_encryptMethods = array(
  81. // @codingStandardsIgnoreStart
  82. 'sslv2_client' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT,
  83. 'sslv3_client' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
  84. 'sslv23_client' => STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
  85. 'tls_client' => STREAM_CRYPTO_METHOD_TLS_CLIENT,
  86. 'sslv2_server' => STREAM_CRYPTO_METHOD_SSLv2_SERVER,
  87. 'sslv3_server' => STREAM_CRYPTO_METHOD_SSLv3_SERVER,
  88. 'sslv23_server' => STREAM_CRYPTO_METHOD_SSLv23_SERVER,
  89. 'tls_server' => STREAM_CRYPTO_METHOD_TLS_SERVER
  90. // @codingStandardsIgnoreEnd
  91. );
  92. /**
  93. * Used to capture connection warnings which can happen when there are
  94. * SSL errors for example.
  95. *
  96. * @var array
  97. */
  98. protected $_connectionErrors = array();
  99. /**
  100. * Constructor.
  101. *
  102. * @param array $config Socket configuration, which will be merged with the base configuration
  103. * @see CakeSocket::$_baseConfig
  104. */
  105. public function __construct($config = array()) {
  106. $this->config = array_merge($this->_baseConfig, $config);
  107. }
  108. /**
  109. * Connects the socket to the given host and port.
  110. *
  111. * @return bool Success
  112. * @throws SocketException
  113. */
  114. public function connect() {
  115. if ($this->connection) {
  116. $this->disconnect();
  117. }
  118. $hasProtocol = strpos($this->config['host'], '://') !== false;
  119. if ($hasProtocol) {
  120. list($this->config['protocol'], $this->config['host']) = explode('://', $this->config['host']);
  121. }
  122. $scheme = null;
  123. if (!empty($this->config['protocol'])) {
  124. $scheme = $this->config['protocol'] . '://';
  125. }
  126. if (!empty($this->config['proxy'])) {
  127. $scheme = 'tcp://';
  128. }
  129. $host = $this->config['host'];
  130. if (isset($this->config['request']['uri']['host'])) {
  131. $host = $this->config['request']['uri']['host'];
  132. }
  133. $this->_setSslContext($host);
  134. if (!empty($this->config['context'])) {
  135. $context = stream_context_create($this->config['context']);
  136. } else {
  137. $context = stream_context_create();
  138. }
  139. $connectAs = STREAM_CLIENT_CONNECT;
  140. if ($this->config['persistent']) {
  141. $connectAs |= STREAM_CLIENT_PERSISTENT;
  142. }
  143. set_error_handler(array($this, '_connectionErrorHandler'));
  144. $this->connection = stream_socket_client(
  145. $scheme . $this->config['host'] . ':' . $this->config['port'],
  146. $errNum,
  147. $errStr,
  148. $this->config['timeout'],
  149. $connectAs,
  150. $context
  151. );
  152. restore_error_handler();
  153. if (!empty($errNum) || !empty($errStr)) {
  154. $this->setLastError($errNum, $errStr);
  155. throw new SocketException($errStr, $errNum);
  156. }
  157. if (!$this->connection && $this->_connectionErrors) {
  158. $message = implode("\n", $this->_connectionErrors);
  159. throw new SocketException($message, E_WARNING);
  160. }
  161. $this->connected = is_resource($this->connection);
  162. if ($this->connected) {
  163. stream_set_timeout($this->connection, $this->config['timeout']);
  164. if (!empty($this->config['request']) &&
  165. $this->config['request']['uri']['scheme'] === 'https' &&
  166. !empty($this->config['proxy'])
  167. ) {
  168. $req = array();
  169. $req[] = 'CONNECT ' . $this->config['request']['uri']['host'] . ':' .
  170. $this->config['request']['uri']['port'] . ' HTTP/1.1';
  171. $req[] = 'Host: ' . $this->config['host'];
  172. $req[] = 'User-Agent: php proxy';
  173. if (!empty($this->config['proxyauth'])) {
  174. $req[] = 'Proxy-Authorization: ' . $this->config['proxyauth'];
  175. }
  176. fwrite($this->connection, implode("\r\n", $req) . "\r\n\r\n");
  177. while (!feof($this->connection)) {
  178. $s = rtrim(fgets($this->connection, 4096));
  179. if (preg_match('/^$/', $s)) {
  180. break;
  181. }
  182. }
  183. $this->enableCrypto('tls', 'client');
  184. }
  185. }
  186. return $this->connected;
  187. }
  188. /**
  189. * Configure the SSL context options.
  190. *
  191. * @param string $host The host name being connected to.
  192. * @return void
  193. */
  194. protected function _setSslContext($host) {
  195. foreach ($this->config as $key => $value) {
  196. if (substr($key, 0, 4) !== 'ssl_') {
  197. continue;
  198. }
  199. $contextKey = substr($key, 4);
  200. if (empty($this->config['context']['ssl'][$contextKey])) {
  201. $this->config['context']['ssl'][$contextKey] = $value;
  202. }
  203. unset($this->config[$key]);
  204. }
  205. if (version_compare(PHP_VERSION, '5.3.2', '>=')) {
  206. if (!isset($this->config['context']['ssl']['SNI_enabled'])) {
  207. $this->config['context']['ssl']['SNI_enabled'] = true;
  208. }
  209. if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
  210. if (empty($this->config['context']['ssl']['peer_name'])) {
  211. $this->config['context']['ssl']['peer_name'] = $host;
  212. }
  213. } else {
  214. if (empty($this->config['context']['ssl']['SNI_server_name'])) {
  215. $this->config['context']['ssl']['SNI_server_name'] = $host;
  216. }
  217. }
  218. }
  219. if (empty($this->config['context']['ssl']['cafile'])) {
  220. $this->config['context']['ssl']['cafile'] = CAKE . 'Config' . DS . 'cacert.pem';
  221. }
  222. if (!empty($this->config['context']['ssl']['verify_host'])) {
  223. $this->config['context']['ssl']['CN_match'] = $host;
  224. }
  225. unset($this->config['context']['ssl']['verify_host']);
  226. }
  227. /**
  228. * socket_stream_client() does not populate errNum, or $errStr when there are
  229. * connection errors, as in the case of SSL verification failure.
  230. *
  231. * Instead we need to handle those errors manually.
  232. *
  233. * @param int $code Code.
  234. * @param string $message Message.
  235. * @return void
  236. */
  237. protected function _connectionErrorHandler($code, $message) {
  238. $this->_connectionErrors[] = $message;
  239. }
  240. /**
  241. * Gets the connection context.
  242. *
  243. * @return null|array Null when there is no connection, an array when there is.
  244. */
  245. public function context() {
  246. if (!$this->connection) {
  247. return null;
  248. }
  249. return stream_context_get_options($this->connection);
  250. }
  251. /**
  252. * Gets the host name of the current connection.
  253. *
  254. * @return string Host name
  255. */
  256. public function host() {
  257. if (Validation::ip($this->config['host'])) {
  258. return gethostbyaddr($this->config['host']);
  259. }
  260. return gethostbyaddr($this->address());
  261. }
  262. /**
  263. * Gets the IP address of the current connection.
  264. *
  265. * @return string IP address
  266. */
  267. public function address() {
  268. if (Validation::ip($this->config['host'])) {
  269. return $this->config['host'];
  270. }
  271. return gethostbyname($this->config['host']);
  272. }
  273. /**
  274. * Gets all IP addresses associated with the current connection.
  275. *
  276. * @return array IP addresses
  277. */
  278. public function addresses() {
  279. if (Validation::ip($this->config['host'])) {
  280. return array($this->config['host']);
  281. }
  282. return gethostbynamel($this->config['host']);
  283. }
  284. /**
  285. * Gets the last error as a string.
  286. *
  287. * @return string|null Last error
  288. */
  289. public function lastError() {
  290. if (!empty($this->lastError)) {
  291. return $this->lastError['num'] . ': ' . $this->lastError['str'];
  292. }
  293. return null;
  294. }
  295. /**
  296. * Sets the last error.
  297. *
  298. * @param int $errNum Error code
  299. * @param string $errStr Error string
  300. * @return void
  301. */
  302. public function setLastError($errNum, $errStr) {
  303. $this->lastError = array('num' => $errNum, 'str' => $errStr);
  304. }
  305. /**
  306. * Writes data to the socket.
  307. *
  308. * @param string $data The data to write to the socket
  309. * @return bool Success
  310. */
  311. public function write($data) {
  312. if (!$this->connected) {
  313. if (!$this->connect()) {
  314. return false;
  315. }
  316. }
  317. $totalBytes = strlen($data);
  318. for ($written = 0, $rv = 0; $written < $totalBytes; $written += $rv) {
  319. $rv = fwrite($this->connection, substr($data, $written));
  320. if ($rv === false || $rv === 0) {
  321. return $written;
  322. }
  323. }
  324. return $written;
  325. }
  326. /**
  327. * Reads data from the socket. Returns false if no data is available or no connection could be
  328. * established.
  329. *
  330. * @param int $length Optional buffer length to read; defaults to 1024
  331. * @return mixed Socket data
  332. */
  333. public function read($length = 1024) {
  334. if (!$this->connected) {
  335. if (!$this->connect()) {
  336. return false;
  337. }
  338. }
  339. if (!feof($this->connection)) {
  340. $buffer = fread($this->connection, $length);
  341. $info = stream_get_meta_data($this->connection);
  342. if ($info['timed_out']) {
  343. $this->setLastError(E_WARNING, __d('cake_dev', 'Connection timed out'));
  344. return false;
  345. }
  346. return $buffer;
  347. }
  348. return false;
  349. }
  350. /**
  351. * Disconnects the socket from the current connection.
  352. *
  353. * @return bool Success
  354. */
  355. public function disconnect() {
  356. if (!is_resource($this->connection)) {
  357. $this->connected = false;
  358. return true;
  359. }
  360. $this->connected = !fclose($this->connection);
  361. if (!$this->connected) {
  362. $this->connection = null;
  363. }
  364. return !$this->connected;
  365. }
  366. /**
  367. * Destructor, used to disconnect from current connection.
  368. */
  369. public function __destruct() {
  370. $this->disconnect();
  371. }
  372. /**
  373. * Resets the state of this Socket instance to it's initial state (before Object::__construct got executed)
  374. *
  375. * @param array $state Array with key and values to reset
  376. * @return bool True on success
  377. */
  378. public function reset($state = null) {
  379. if (empty($state)) {
  380. static $initalState = array();
  381. if (empty($initalState)) {
  382. $initalState = get_class_vars(__CLASS__);
  383. }
  384. $state = $initalState;
  385. }
  386. foreach ($state as $property => $value) {
  387. $this->{$property} = $value;
  388. }
  389. return true;
  390. }
  391. /**
  392. * Encrypts current stream socket, using one of the defined encryption methods.
  393. *
  394. * @param string $type Type which can be one of 'sslv2', 'sslv3', 'sslv23' or 'tls'.
  395. * @param string $clientOrServer Can be one of 'client', 'server'. Default is 'client'.
  396. * @param bool $enable Enable or disable encryption. Default is true (enable)
  397. * @return bool True on success
  398. * @throws InvalidArgumentException When an invalid encryption scheme is chosen.
  399. * @throws SocketException When attempting to enable SSL/TLS fails.
  400. * @see stream_socket_enable_crypto
  401. */
  402. public function enableCrypto($type, $clientOrServer = 'client', $enable = true) {
  403. if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) {
  404. throw new InvalidArgumentException(__d('cake_dev', 'Invalid encryption scheme chosen'));
  405. }
  406. $enableCryptoResult = false;
  407. try {
  408. $enableCryptoResult = stream_socket_enable_crypto($this->connection, $enable,
  409. $this->_encryptMethods[$type . '_' . $clientOrServer]);
  410. } catch (Exception $e) {
  411. $this->setLastError(null, $e->getMessage());
  412. throw new SocketException($e->getMessage());
  413. }
  414. if ($enableCryptoResult === true) {
  415. $this->encrypted = $enable;
  416. return true;
  417. }
  418. $errorMessage = __d('cake_dev', 'Unable to perform enableCrypto operation on CakeSocket');
  419. $this->setLastError(null, $errorMessage);
  420. throw new SocketException($errorMessage);
  421. }
  422. }