PageRenderTime 45ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/Network/Socket.php

https://github.com/MichalWadowski/cakephp
PHP | 383 lines | 191 code | 38 blank | 154 comment | 32 complexity | f2779e5460bc51acc1bfaa66319ff908 MD5 | raw file
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 1.2.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Network;
  16. use Cake\Core\InstanceConfigTrait;
  17. use Cake\Validation\Validation;
  18. /**
  19. * CakePHP network socket connection class.
  20. *
  21. * Core base class for network communication.
  22. *
  23. */
  24. class Socket {
  25. use InstanceConfigTrait;
  26. /**
  27. * Object description
  28. *
  29. * @var string
  30. */
  31. public $description = 'Remote DataSource Network Socket Interface';
  32. /**
  33. * Default configuration settings for the socket connection
  34. *
  35. * @var array
  36. */
  37. protected $_defaultConfig = array(
  38. 'persistent' => false,
  39. 'host' => 'localhost',
  40. 'protocol' => 'tcp',
  41. 'port' => 80,
  42. 'timeout' => 30
  43. );
  44. /**
  45. * Reference to socket connection resource
  46. *
  47. * @var resource
  48. */
  49. public $connection = null;
  50. /**
  51. * This boolean contains the current state of the Socket class
  52. *
  53. * @var bool
  54. */
  55. public $connected = false;
  56. /**
  57. * This variable contains an array with the last error number (num) and string (str)
  58. *
  59. * @var array
  60. */
  61. public $lastError = array();
  62. /**
  63. * True if the socket stream is encrypted after a Cake\Network\Socket::enableCrypto() call
  64. *
  65. * @var bool
  66. */
  67. public $encrypted = false;
  68. /**
  69. * Contains all the encryption methods available
  70. *
  71. * @var array
  72. */
  73. protected $_encryptMethods = array(
  74. // @codingStandardsIgnoreStart
  75. 'sslv2_client' => STREAM_CRYPTO_METHOD_SSLv2_CLIENT,
  76. 'sslv3_client' => STREAM_CRYPTO_METHOD_SSLv3_CLIENT,
  77. 'sslv23_client' => STREAM_CRYPTO_METHOD_SSLv23_CLIENT,
  78. 'tls_client' => STREAM_CRYPTO_METHOD_TLS_CLIENT,
  79. 'sslv2_server' => STREAM_CRYPTO_METHOD_SSLv2_SERVER,
  80. 'sslv3_server' => STREAM_CRYPTO_METHOD_SSLv3_SERVER,
  81. 'sslv23_server' => STREAM_CRYPTO_METHOD_SSLv23_SERVER,
  82. 'tls_server' => STREAM_CRYPTO_METHOD_TLS_SERVER
  83. // @codingStandardsIgnoreEnd
  84. );
  85. /**
  86. * Used to capture connection warnings which can happen when there are
  87. * SSL errors for example.
  88. *
  89. * @var array
  90. */
  91. protected $_connectionErrors = array();
  92. /**
  93. * Constructor.
  94. *
  95. * @param array $config Socket configuration, which will be merged with the base configuration
  96. * @see Socket::$_baseConfig
  97. */
  98. public function __construct(array $config = array()) {
  99. $this->config($config);
  100. if (!is_numeric($this->_config['protocol'])) {
  101. $this->_config['protocol'] = getprotobyname($this->_config['protocol']);
  102. }
  103. }
  104. /**
  105. * Connect the socket to the given host and port.
  106. *
  107. * @return bool Success
  108. * @throws \Cake\Network\Error\SocketException
  109. */
  110. public function connect() {
  111. if ($this->connection) {
  112. $this->disconnect();
  113. }
  114. $scheme = null;
  115. if (isset($this->_config['request']['uri']) && $this->_config['request']['uri']['scheme'] === 'https') {
  116. $scheme = 'ssl://';
  117. }
  118. if (!empty($this->_config['context'])) {
  119. $context = stream_context_create($this->_config['context']);
  120. } else {
  121. $context = stream_context_create();
  122. }
  123. $connectAs = STREAM_CLIENT_CONNECT;
  124. if ($this->_config['persistent']) {
  125. $connectAs |= STREAM_CLIENT_PERSISTENT;
  126. }
  127. set_error_handler(array($this, '_connectionErrorHandler'));
  128. $this->connection = stream_socket_client(
  129. $scheme . $this->_config['host'] . ':' . $this->_config['port'],
  130. $errNum,
  131. $errStr,
  132. $this->_config['timeout'],
  133. $connectAs,
  134. $context
  135. );
  136. restore_error_handler();
  137. if (!empty($errNum) || !empty($errStr)) {
  138. $this->setLastError($errNum, $errStr);
  139. throw new Error\SocketException($errStr, $errNum);
  140. }
  141. if (!$this->connection && $this->_connectionErrors) {
  142. $message = implode("\n", $this->_connectionErrors);
  143. throw new Error\SocketException($message, E_WARNING);
  144. }
  145. $this->connected = is_resource($this->connection);
  146. if ($this->connected) {
  147. stream_set_timeout($this->connection, $this->_config['timeout']);
  148. }
  149. return $this->connected;
  150. }
  151. /**
  152. * socket_stream_client() does not populate errNum, or $errStr when there are
  153. * connection errors, as in the case of SSL verification failure.
  154. *
  155. * Instead we need to handle those errors manually.
  156. *
  157. * @param int $code Code number.
  158. * @param string $message Message.
  159. * @return void
  160. */
  161. protected function _connectionErrorHandler($code, $message) {
  162. $this->_connectionErrors[] = $message;
  163. }
  164. /**
  165. * Get the connection context.
  166. *
  167. * @return null|array Null when there is no connection, an array when there is.
  168. */
  169. public function context() {
  170. if (!$this->connection) {
  171. return;
  172. }
  173. return stream_context_get_options($this->connection);
  174. }
  175. /**
  176. * Get the host name of the current connection.
  177. *
  178. * @return string Host name
  179. */
  180. public function host() {
  181. if (Validation::ip($this->_config['host'])) {
  182. return gethostbyaddr($this->_config['host']);
  183. }
  184. return gethostbyaddr($this->address());
  185. }
  186. /**
  187. * Get the IP address of the current connection.
  188. *
  189. * @return string IP address
  190. */
  191. public function address() {
  192. if (Validation::ip($this->_config['host'])) {
  193. return $this->_config['host'];
  194. }
  195. return gethostbyname($this->_config['host']);
  196. }
  197. /**
  198. * Get all IP addresses associated with the current connection.
  199. *
  200. * @return array IP addresses
  201. */
  202. public function addresses() {
  203. if (Validation::ip($this->_config['host'])) {
  204. return array($this->_config['host']);
  205. }
  206. return gethostbynamel($this->_config['host']);
  207. }
  208. /**
  209. * Get the last error as a string.
  210. *
  211. * @return string Last error
  212. */
  213. public function lastError() {
  214. if (!empty($this->lastError)) {
  215. return $this->lastError['num'] . ': ' . $this->lastError['str'];
  216. }
  217. return null;
  218. }
  219. /**
  220. * Set the last error.
  221. *
  222. * @param int $errNum Error code
  223. * @param string $errStr Error string
  224. * @return void
  225. */
  226. public function setLastError($errNum, $errStr) {
  227. $this->lastError = array('num' => $errNum, 'str' => $errStr);
  228. }
  229. /**
  230. * Write data to the socket.
  231. *
  232. * @param string $data The data to write to the socket
  233. * @return bool Success
  234. */
  235. public function write($data) {
  236. if (!$this->connected) {
  237. if (!$this->connect()) {
  238. return false;
  239. }
  240. }
  241. $totalBytes = strlen($data);
  242. for ($written = 0, $rv = 0; $written < $totalBytes; $written += $rv) {
  243. $rv = fwrite($this->connection, substr($data, $written));
  244. if ($rv === false || $rv === 0) {
  245. return $written;
  246. }
  247. }
  248. return $written;
  249. }
  250. /**
  251. * Read data from the socket. Returns false if no data is available or no connection could be
  252. * established.
  253. *
  254. * @param int $length Optional buffer length to read; defaults to 1024
  255. * @return mixed Socket data
  256. */
  257. public function read($length = 1024) {
  258. if (!$this->connected) {
  259. if (!$this->connect()) {
  260. return false;
  261. }
  262. }
  263. if (!feof($this->connection)) {
  264. $buffer = fread($this->connection, $length);
  265. $info = stream_get_meta_data($this->connection);
  266. if ($info['timed_out']) {
  267. $this->setLastError(E_WARNING, 'Connection timed out');
  268. return false;
  269. }
  270. return $buffer;
  271. }
  272. return false;
  273. }
  274. /**
  275. * Disconnect the socket from the current connection.
  276. *
  277. * @return bool Success
  278. */
  279. public function disconnect() {
  280. if (!is_resource($this->connection)) {
  281. $this->connected = false;
  282. return true;
  283. }
  284. $this->connected = !fclose($this->connection);
  285. if (!$this->connected) {
  286. $this->connection = null;
  287. }
  288. return !$this->connected;
  289. }
  290. /**
  291. * Destructor, used to disconnect from current connection.
  292. */
  293. public function __destruct() {
  294. $this->disconnect();
  295. }
  296. /**
  297. * Resets the state of this Socket instance to it's initial state (before Object::__construct got executed)
  298. *
  299. * @param array $state Array with key and values to reset
  300. * @return bool True on success
  301. */
  302. public function reset($state = null) {
  303. if (empty($state)) {
  304. static $initalState = array();
  305. if (empty($initalState)) {
  306. $initalState = get_class_vars(__CLASS__);
  307. }
  308. $state = $initalState;
  309. }
  310. foreach ($state as $property => $value) {
  311. $this->{$property} = $value;
  312. }
  313. return true;
  314. }
  315. /**
  316. * Encrypts current stream socket, using one of the defined encryption methods
  317. *
  318. * @param string $type can be one of 'ssl2', 'ssl3', 'ssl23' or 'tls'
  319. * @param string $clientOrServer can be one of 'client', 'server'. Default is 'client'
  320. * @param bool $enable enable or disable encryption. Default is true (enable)
  321. * @return bool True on success
  322. * @throws \InvalidArgumentException When an invalid encryption scheme is chosen.
  323. * @throws \Cake\Network\Error\SocketException When attempting to enable SSL/TLS fails
  324. * @see stream_socket_enable_crypto
  325. */
  326. public function enableCrypto($type, $clientOrServer = 'client', $enable = true) {
  327. if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) {
  328. throw new \InvalidArgumentException('Invalid encryption scheme chosen');
  329. }
  330. $enableCryptoResult = false;
  331. try {
  332. $enableCryptoResult = stream_socket_enable_crypto($this->connection, $enable, $this->_encryptMethods[$type . '_' . $clientOrServer]);
  333. } catch (\Exception $e) {
  334. $this->setLastError(null, $e->getMessage());
  335. throw new Error\SocketException($e->getMessage());
  336. }
  337. if ($enableCryptoResult === true) {
  338. $this->encrypted = $enable;
  339. return true;
  340. }
  341. $errorMessage = 'Unable to perform enableCrypto operation on the current socket';
  342. $this->setLastError(null, $errorMessage);
  343. throw new Error\SocketException($errorMessage);
  344. }
  345. }