PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/horde/framework/Horde/Socket/Client.php

http://github.com/moodle/moodle
PHP | 336 lines | 166 code | 33 blank | 137 comment | 22 complexity | 6377af59323aff484eea80d6ac1fa939 MD5 | raw file
Possible License(s): MIT, AGPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-3.0, GPL-3.0, Apache-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. namespace Horde\Socket;
  3. /**
  4. * Copyright 2013-2017 Horde LLC (http://www.horde.org/)
  5. *
  6. * See the enclosed file LICENSE for license information (LGPL). If you
  7. * did not receive this file, see http://www.horde.org/licenses/lgpl21.
  8. *
  9. * @category Horde
  10. * @copyright 2013-2017 Horde LLC
  11. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  12. * @package Socket_Client
  13. */
  14. /**
  15. * Utility interface for establishing a stream socket client.
  16. *
  17. * @author Michael Slusarz <slusarz@horde.org>
  18. * @author Jan Schneider <jan@horde.org>
  19. * @category Horde
  20. * @copyright 2013-2017 Horde LLC
  21. * @license http://www.horde.org/licenses/lgpl21 LGPL 2.1
  22. * @package Socket_Client
  23. *
  24. * @property-read boolean $connected Is there an active connection?
  25. * @property-read boolean $secure Is the active connection secure?
  26. */
  27. class Client
  28. {
  29. /**
  30. * Is there an active connection?
  31. *
  32. * @var boolean
  33. */
  34. protected $_connected = false;
  35. /**
  36. * Configuration parameters.
  37. *
  38. * @var array
  39. */
  40. protected $_params;
  41. /**
  42. * Is the connection secure?
  43. *
  44. * @var boolean
  45. */
  46. protected $_secure = false;
  47. /**
  48. * The actual socket.
  49. *
  50. * @var resource
  51. */
  52. protected $_stream;
  53. /**
  54. * Constructor.
  55. *
  56. * @param string $host Hostname of remote server (can contain
  57. * protocol prefx).
  58. * @param integer $port Port number of remote server.
  59. * @param integer $timeout Connection timeout (in seconds).
  60. * @param mixed $secure Security layer requested. One of:
  61. * - false: (No encryption) [DEFAULT]
  62. * - 'ssl': (Auto-detect SSL version)
  63. * - 'sslv2': (Force SSL version 3)
  64. * - 'sslv3': (Force SSL version 2)
  65. * - 'tls': (TLS; started via protocol-level negotation over unencrypted
  66. * channel)
  67. * - 'tlsv1': (TLS version 1.x connection)
  68. * - true: (TLS if available/necessary)
  69. * @param array $context Any context parameters passed to
  70. * stream_create_context().
  71. * @param array $params Additional options.
  72. *
  73. * @throws Horde\Socket\Client\Exception
  74. */
  75. public function __construct(
  76. $host, $port = null, $timeout = 30, $secure = false,
  77. $context = array(), array $params = array()
  78. )
  79. {
  80. if ($secure && !extension_loaded('openssl')) {
  81. if ($secure !== true) {
  82. throw new \InvalidArgumentException('Secure connections require the PHP openssl extension.');
  83. }
  84. $secure = false;
  85. }
  86. $context = array_merge_recursive(
  87. array(
  88. 'ssl' => array(
  89. 'verify_peer' => false,
  90. 'verify_peer_name' => false
  91. )
  92. ),
  93. $context
  94. );
  95. $this->_params = $params;
  96. $this->_connect($host, $port, $timeout, $secure, $context);
  97. }
  98. /**
  99. */
  100. public function __get($name)
  101. {
  102. switch ($name) {
  103. case 'connected':
  104. return $this->_connected;
  105. case 'secure':
  106. return $this->_secure;
  107. }
  108. }
  109. /**
  110. * This object can not be cloned.
  111. */
  112. public function __clone()
  113. {
  114. throw new \LogicException('Object cannot be cloned.');
  115. }
  116. /**
  117. * This object can not be serialized.
  118. */
  119. public function __sleep()
  120. {
  121. throw new \LogicException('Object can not be serialized.');
  122. }
  123. /**
  124. * Start a TLS connection.
  125. *
  126. * @return boolean Whether TLS was successfully started.
  127. */
  128. public function startTls()
  129. {
  130. if ($this->connected && !$this->secure) {
  131. if (defined('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT')) {
  132. $mode = STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT
  133. | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
  134. | STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
  135. } else {
  136. $mode = STREAM_CRYPTO_METHOD_TLS_CLIENT;
  137. }
  138. if (@stream_socket_enable_crypto($this->_stream, true, $mode) === true) {
  139. $this->_secure = true;
  140. return true;
  141. }
  142. }
  143. return false;
  144. }
  145. /**
  146. * Close the connection.
  147. */
  148. public function close()
  149. {
  150. if ($this->connected) {
  151. @fclose($this->_stream);
  152. $this->_connected = $this->_secure = false;
  153. $this->_stream = null;
  154. }
  155. }
  156. /**
  157. * Returns information about the connection.
  158. *
  159. * Currently returns four entries in the result array:
  160. * - timed_out (bool): The socket timed out waiting for data
  161. * - blocked (bool): The socket was blocked
  162. * - eof (bool): Indicates EOF event
  163. * - unread_bytes (int): Number of bytes left in the socket buffer
  164. *
  165. * @throws Horde\Socket\Client\Exception
  166. * @return array Information about existing socket resource.
  167. */
  168. public function getStatus()
  169. {
  170. $this->_checkStream();
  171. return stream_get_meta_data($this->_stream);
  172. }
  173. /**
  174. * Returns a line of data.
  175. *
  176. * @param int $size Reading ends when $size - 1 bytes have been read,
  177. * or a newline or an EOF (whichever comes first).
  178. *
  179. * @throws Horde\Socket\Client\Exception
  180. * @return string $size bytes of data from the socket
  181. */
  182. public function gets($size)
  183. {
  184. $this->_checkStream();
  185. $data = @fgets($this->_stream, $size);
  186. if ($data === false) {
  187. throw new Client\Exception('Error reading data from socket');
  188. }
  189. return $data;
  190. }
  191. /**
  192. * Returns a specified amount of data.
  193. *
  194. * @param integer $size The number of bytes to read from the socket.
  195. *
  196. * @throws Horde\Socket\Client\Exception
  197. * @return string $size bytes of data from the socket.
  198. */
  199. public function read($size)
  200. {
  201. $this->_checkStream();
  202. $data = @fread($this->_stream, $size);
  203. if ($data === false) {
  204. throw new Client\Exception('Error reading data from socket');
  205. }
  206. return $data;
  207. }
  208. /**
  209. * Writes data to the stream.
  210. *
  211. * @param string $data Data to write.
  212. *
  213. * @throws Horde\Socket\Client\Exception
  214. */
  215. public function write($data)
  216. {
  217. $this->_checkStream();
  218. if (!@fwrite($this->_stream, $data)) {
  219. $meta_data = $this->getStatus();
  220. if (!empty($meta_data['timed_out'])) {
  221. throw new Client\Exception('Timed out writing data to socket');
  222. }
  223. throw new Client\Exception('Error writing data to socket');
  224. }
  225. }
  226. /* Internal methods. */
  227. /**
  228. * Connect to the remote server.
  229. *
  230. * @see __construct()
  231. *
  232. * @throws Horde\Socket\Client\Exception
  233. */
  234. protected function _connect(
  235. $host, $port, $timeout, $secure, $context, $retries = 0
  236. )
  237. {
  238. $conn = '';
  239. if (!strpos($host, '://')) {
  240. switch (strval($secure)) {
  241. case 'ssl':
  242. case 'sslv2':
  243. case 'sslv3':
  244. $conn = $secure . '://';
  245. $this->_secure = true;
  246. break;
  247. case 'tlsv1':
  248. $conn = 'tls://';
  249. $this->_secure = true;
  250. break;
  251. case 'tls':
  252. default:
  253. $conn = 'tcp://';
  254. break;
  255. }
  256. }
  257. $conn .= $host;
  258. if ($port) {
  259. $conn .= ':' . $port;
  260. }
  261. $this->_stream = @stream_socket_client(
  262. $conn,
  263. $error_number,
  264. $error_string,
  265. $timeout,
  266. STREAM_CLIENT_CONNECT,
  267. stream_context_create($context)
  268. );
  269. if ($this->_stream === false) {
  270. /* From stream_socket_client() page: a function return of false,
  271. * with an error code of 0, indicates a "problem initializing the
  272. * socket". These kind of issues are seen on the same server
  273. * (and even the same user account) as sucessful connections, so
  274. * these are likely transient issues. Retry up to 3 times in these
  275. * instances. */
  276. if (!$error_number && ($retries < 3)) {
  277. return $this->_connect($host, $port, $timeout, $secure, $context, ++$retries);
  278. }
  279. $e = new Client\Exception(
  280. 'Error connecting to server.'
  281. );
  282. $e->details = sprintf("[%u] %s", $error_number, $error_string);
  283. throw $e;
  284. }
  285. stream_set_timeout($this->_stream, $timeout);
  286. if (function_exists('stream_set_read_buffer')) {
  287. stream_set_read_buffer($this->_stream, 0);
  288. }
  289. stream_set_write_buffer($this->_stream, 0);
  290. $this->_connected = true;
  291. }
  292. /**
  293. * Throws an exception is the stream is not a resource.
  294. *
  295. * @throws Horde\Socket\Client\Exception
  296. */
  297. protected function _checkStream()
  298. {
  299. if (!is_resource($this->_stream)) {
  300. throw new Client\Exception('Not connected');
  301. }
  302. }
  303. }