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