Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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