Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
13532 anikendra 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 boolean
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 boolean
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
		if (!is_numeric($this->config['protocol'])) {
120
			$this->config['protocol'] = getprotobyname($this->config['protocol']);
121
		}
122
	}
123
 
124
/**
125
 * Connect the socket to the given host and port.
126
 *
127
 * @return boolean Success
128
 * @throws SocketException
129
 */
130
	public function connect() {
131
		if ($this->connection) {
132
			$this->disconnect();
133
		}
134
 
135
		$scheme = null;
136
		if (isset($this->config['request']['uri']) && $this->config['request']['uri']['scheme'] === 'https') {
137
			$scheme = 'ssl://';
138
		}
139
 
140
		if (!empty($this->config['context'])) {
141
			$context = stream_context_create($this->config['context']);
142
		} else {
143
			$context = stream_context_create();
144
		}
145
 
146
		$connectAs = STREAM_CLIENT_CONNECT;
147
		if ($this->config['persistent']) {
148
			$connectAs |= STREAM_CLIENT_PERSISTENT;
149
		}
150
 
151
		set_error_handler(array($this, '_connectionErrorHandler'));
152
		$this->connection = stream_socket_client(
153
			$scheme . $this->config['host'] . ':' . $this->config['port'],
154
			$errNum,
155
			$errStr,
156
			$this->config['timeout'],
157
			$connectAs,
158
			$context
159
		);
160
		restore_error_handler();
161
 
162
		if (!empty($errNum) || !empty($errStr)) {
163
			$this->setLastError($errNum, $errStr);
164
			throw new SocketException($errStr, $errNum);
165
		}
166
 
167
		if (!$this->connection && $this->_connectionErrors) {
168
			$message = implode("\n", $this->_connectionErrors);
169
			throw new SocketException($message, E_WARNING);
170
		}
171
 
172
		$this->connected = is_resource($this->connection);
173
		if ($this->connected) {
174
			stream_set_timeout($this->connection, $this->config['timeout']);
175
		}
176
		return $this->connected;
177
	}
178
 
179
/**
180
 * socket_stream_client() does not populate errNum, or $errStr when there are
181
 * connection errors, as in the case of SSL verification failure.
182
 *
183
 * Instead we need to handle those errors manually.
184
 *
185
 * @param integer $code
186
 * @param string $message
187
 * @return void
188
 */
189
	protected function _connectionErrorHandler($code, $message) {
190
		$this->_connectionErrors[] = $message;
191
	}
192
 
193
/**
194
 * Get the connection context.
195
 *
196
 * @return null|array Null when there is no connection, an array when there is.
197
 */
198
	public function context() {
199
		if (!$this->connection) {
200
			return;
201
		}
202
		return stream_context_get_options($this->connection);
203
	}
204
 
205
/**
206
 * Get the host name of the current connection.
207
 *
208
 * @return string Host name
209
 */
210
	public function host() {
211
		if (Validation::ip($this->config['host'])) {
212
			return gethostbyaddr($this->config['host']);
213
		}
214
		return gethostbyaddr($this->address());
215
	}
216
 
217
/**
218
 * Get the IP address of the current connection.
219
 *
220
 * @return string IP address
221
 */
222
	public function address() {
223
		if (Validation::ip($this->config['host'])) {
224
			return $this->config['host'];
225
		}
226
		return gethostbyname($this->config['host']);
227
	}
228
 
229
/**
230
 * Get all IP addresses associated with the current connection.
231
 *
232
 * @return array IP addresses
233
 */
234
	public function addresses() {
235
		if (Validation::ip($this->config['host'])) {
236
			return array($this->config['host']);
237
		}
238
		return gethostbynamel($this->config['host']);
239
	}
240
 
241
/**
242
 * Get the last error as a string.
243
 *
244
 * @return string Last error
245
 */
246
	public function lastError() {
247
		if (!empty($this->lastError)) {
248
			return $this->lastError['num'] . ': ' . $this->lastError['str'];
249
		}
250
		return null;
251
	}
252
 
253
/**
254
 * Set the last error.
255
 *
256
 * @param integer $errNum Error code
257
 * @param string $errStr Error string
258
 * @return void
259
 */
260
	public function setLastError($errNum, $errStr) {
261
		$this->lastError = array('num' => $errNum, 'str' => $errStr);
262
	}
263
 
264
/**
265
 * Write data to the socket.
266
 *
267
 * @param string $data The data to write to the socket
268
 * @return boolean Success
269
 */
270
	public function write($data) {
271
		if (!$this->connected) {
272
			if (!$this->connect()) {
273
				return false;
274
			}
275
		}
276
		$totalBytes = strlen($data);
277
		for ($written = 0, $rv = 0; $written < $totalBytes; $written += $rv) {
278
			$rv = fwrite($this->connection, substr($data, $written));
279
			if ($rv === false || $rv === 0) {
280
				return $written;
281
			}
282
		}
283
		return $written;
284
	}
285
 
286
/**
287
 * Read data from the socket. Returns false if no data is available or no connection could be
288
 * established.
289
 *
290
 * @param integer $length Optional buffer length to read; defaults to 1024
291
 * @return mixed Socket data
292
 */
293
	public function read($length = 1024) {
294
		if (!$this->connected) {
295
			if (!$this->connect()) {
296
				return false;
297
			}
298
		}
299
 
300
		if (!feof($this->connection)) {
301
			$buffer = fread($this->connection, $length);
302
			$info = stream_get_meta_data($this->connection);
303
			if ($info['timed_out']) {
304
				$this->setLastError(E_WARNING, __d('cake_dev', 'Connection timed out'));
305
				return false;
306
			}
307
			return $buffer;
308
		}
309
		return false;
310
	}
311
 
312
/**
313
 * Disconnect the socket from the current connection.
314
 *
315
 * @return boolean Success
316
 */
317
	public function disconnect() {
318
		if (!is_resource($this->connection)) {
319
			$this->connected = false;
320
			return true;
321
		}
322
		$this->connected = !fclose($this->connection);
323
 
324
		if (!$this->connected) {
325
			$this->connection = null;
326
		}
327
		return !$this->connected;
328
	}
329
 
330
/**
331
 * Destructor, used to disconnect from current connection.
332
 */
333
	public function __destruct() {
334
		$this->disconnect();
335
	}
336
 
337
/**
338
 * Resets the state of this Socket instance to it's initial state (before Object::__construct got executed)
339
 *
340
 * @param array $state Array with key and values to reset
341
 * @return boolean True on success
342
 */
343
	public function reset($state = null) {
344
		if (empty($state)) {
345
			static $initalState = array();
346
			if (empty($initalState)) {
347
				$initalState = get_class_vars(__CLASS__);
348
			}
349
			$state = $initalState;
350
		}
351
 
352
		foreach ($state as $property => $value) {
353
			$this->{$property} = $value;
354
		}
355
		return true;
356
	}
357
 
358
/**
359
 * Encrypts current stream socket, using one of the defined encryption methods
360
 *
361
 * @param string $type can be one of 'ssl2', 'ssl3', 'ssl23' or 'tls'
362
 * @param string $clientOrServer can be one of 'client', 'server'. Default is 'client'
363
 * @param boolean $enable enable or disable encryption. Default is true (enable)
364
 * @return boolean True on success
365
 * @throws InvalidArgumentException When an invalid encryption scheme is chosen.
366
 * @throws SocketException When attempting to enable SSL/TLS fails
367
 * @see stream_socket_enable_crypto
368
 */
369
	public function enableCrypto($type, $clientOrServer = 'client', $enable = true) {
370
		if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) {
371
			throw new InvalidArgumentException(__d('cake_dev', 'Invalid encryption scheme chosen'));
372
		}
373
		$enableCryptoResult = false;
374
		try {
375
			$enableCryptoResult = stream_socket_enable_crypto($this->connection, $enable, $this->_encryptMethods[$type . '_' . $clientOrServer]);
376
		} catch (Exception $e) {
377
			$this->setLastError(null, $e->getMessage());
378
			throw new SocketException($e->getMessage());
379
		}
380
		if ($enableCryptoResult === true) {
381
			$this->encrypted = $enable;
382
			return true;
383
		}
384
		$errorMessage = __d('cake_dev', 'Unable to perform enableCrypto operation on CakeSocket');
385
		$this->setLastError(null, $errorMessage);
386
		throw new SocketException($errorMessage);
387
	}
388
 
389
}