1: <?php
2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17:
18:
19: App::uses('Validation', 'Utility');
20:
21: 22: 23: 24: 25: 26: 27:
28: class CakeSocket {
29:
30: 31: 32: 33: 34:
35: public $description = 'Remote DataSource Network Socket Interface';
36:
37: 38: 39: 40: 41:
42: protected $_baseConfig = array(
43: 'persistent' => false,
44: 'host' => 'localhost',
45: 'protocol' => 'tcp',
46: 'port' => 80,
47: 'timeout' => 30
48: );
49:
50: 51: 52: 53: 54:
55: public $config = array();
56:
57: 58: 59: 60: 61:
62: public $connection = null;
63:
64: 65: 66: 67: 68:
69: public $connected = false;
70:
71: 72: 73: 74: 75:
76: public $lastError = array();
77:
78: 79: 80: 81: 82:
83: public $encrypted = false;
84:
85: 86: 87: 88: 89:
90: protected $_encryptMethods = array(
91:
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:
101: );
102:
103: 104: 105: 106: 107: 108:
109: protected $_connectionErrors = array();
110:
111: 112: 113: 114: 115: 116:
117: public function __construct($config = array()) {
118: $this->config = array_merge($this->_baseConfig, $config);
119: }
120:
121: 122: 123: 124: 125: 126:
127: public function connect() {
128: if ($this->connection) {
129: $this->disconnect();
130: }
131:
132: $hasProtocol = strpos($this->config['host'], '://') !== false;
133: if ($hasProtocol) {
134: list($this->config['protocol'], $this->config['host']) = explode('://', $this->config['host']);
135: }
136: $scheme = null;
137: if (!empty($this->config['protocol'])) {
138: $scheme = $this->config['protocol'] . '://';
139: }
140: if (!empty($this->config['proxy'])) {
141: $scheme = 'tcp://';
142: }
143:
144: $host = $this->config['host'];
145: if (isset($this->config['request']['uri']['host'])) {
146: $host = $this->config['request']['uri']['host'];
147: }
148: $this->_setSslContext($host);
149:
150: if (!empty($this->config['context'])) {
151: $context = stream_context_create($this->config['context']);
152: } else {
153: $context = stream_context_create();
154: }
155:
156: $connectAs = STREAM_CLIENT_CONNECT;
157: if ($this->config['persistent']) {
158: $connectAs |= STREAM_CLIENT_PERSISTENT;
159: }
160:
161: set_error_handler(array($this, '_connectionErrorHandler'));
162: $this->connection = stream_socket_client(
163: $scheme . $this->config['host'] . ':' . $this->config['port'],
164: $errNum,
165: $errStr,
166: $this->config['timeout'],
167: $connectAs,
168: $context
169: );
170: restore_error_handler();
171:
172: if (!empty($errNum) || !empty($errStr)) {
173: $this->setLastError($errNum, $errStr);
174: throw new SocketException($errStr, $errNum);
175: }
176:
177: if (!$this->connection && $this->_connectionErrors) {
178: $message = implode("\n", $this->_connectionErrors);
179: throw new SocketException($message, E_WARNING);
180: }
181:
182: $this->connected = is_resource($this->connection);
183: if ($this->connected) {
184: stream_set_timeout($this->connection, $this->config['timeout']);
185:
186: if (!empty($this->config['request']) &&
187: $this->config['request']['uri']['scheme'] === 'https' &&
188: !empty($this->config['proxy'])
189: ) {
190: $req = array();
191: $req[] = 'CONNECT ' . $this->config['request']['uri']['host'] . ':' .
192: $this->config['request']['uri']['port'] . ' HTTP/1.1';
193: $req[] = 'Host: ' . $this->config['host'];
194: $req[] = 'User-Agent: php proxy';
195: if (!empty($this->config['proxyauth'])) {
196: $req[] = 'Proxy-Authorization: ' . $this->config['proxyauth'];
197: }
198:
199: fwrite($this->connection, implode("\r\n", $req) . "\r\n\r\n");
200:
201: while (!feof($this->connection)) {
202: $s = rtrim(fgets($this->connection, 4096));
203: if (preg_match('/^$/', $s)) {
204: break;
205: }
206: }
207:
208: $this->enableCrypto('tls', 'client');
209: }
210: }
211: return $this->connected;
212: }
213:
214: 215: 216: 217: 218: 219:
220: protected function _setSslContext($host) {
221: foreach ($this->config as $key => $value) {
222: if (substr($key, 0, 4) !== 'ssl_') {
223: continue;
224: }
225: $contextKey = substr($key, 4);
226: if (empty($this->config['context']['ssl'][$contextKey])) {
227: $this->config['context']['ssl'][$contextKey] = $value;
228: }
229: unset($this->config[$key]);
230: }
231: if (version_compare(PHP_VERSION, '5.3.2', '>=')) {
232: if (!isset($this->config['context']['ssl']['SNI_enabled'])) {
233: $this->config['context']['ssl']['SNI_enabled'] = true;
234: }
235: if (version_compare(PHP_VERSION, '5.6.0', '>=')) {
236: if (empty($this->config['context']['ssl']['peer_name'])) {
237: $this->config['context']['ssl']['peer_name'] = $host;
238: }
239: } else {
240: if (empty($this->config['context']['ssl']['SNI_server_name'])) {
241: $this->config['context']['ssl']['SNI_server_name'] = $host;
242: }
243: }
244: }
245: if (empty($this->config['context']['ssl']['cafile'])) {
246: $this->config['context']['ssl']['cafile'] = CAKE . 'Config' . DS . 'cacert.pem';
247: }
248: if (!empty($this->config['context']['ssl']['verify_host'])) {
249: $this->config['context']['ssl']['CN_match'] = $host;
250: }
251: unset($this->config['context']['ssl']['verify_host']);
252: }
253:
254: 255: 256: 257: 258: 259: 260: 261: 262: 263:
264: protected function _connectionErrorHandler($code, $message) {
265: $this->_connectionErrors[] = $message;
266: }
267:
268: 269: 270: 271: 272:
273: public function context() {
274: if (!$this->connection) {
275: return null;
276: }
277: return stream_context_get_options($this->connection);
278: }
279:
280: 281: 282: 283: 284:
285: public function host() {
286: if (Validation::ip($this->config['host'])) {
287: return gethostbyaddr($this->config['host']);
288: }
289: return gethostbyaddr($this->address());
290: }
291:
292: 293: 294: 295: 296:
297: public function address() {
298: if (Validation::ip($this->config['host'])) {
299: return $this->config['host'];
300: }
301: return gethostbyname($this->config['host']);
302: }
303:
304: 305: 306: 307: 308:
309: public function addresses() {
310: if (Validation::ip($this->config['host'])) {
311: return array($this->config['host']);
312: }
313: return gethostbynamel($this->config['host']);
314: }
315:
316: 317: 318: 319: 320:
321: public function lastError() {
322: if (!empty($this->lastError)) {
323: return $this->lastError['num'] . ': ' . $this->lastError['str'];
324: }
325: return null;
326: }
327:
328: 329: 330: 331: 332: 333: 334:
335: public function setLastError($errNum, $errStr) {
336: $this->lastError = array('num' => $errNum, 'str' => $errStr);
337: }
338:
339: 340: 341: 342: 343: 344:
345: public function write($data) {
346: if (!$this->connected) {
347: if (!$this->connect()) {
348: return false;
349: }
350: }
351: $totalBytes = strlen($data);
352: for ($written = 0, $rv = 0; $written < $totalBytes; $written += $rv) {
353: $rv = fwrite($this->connection, substr($data, $written));
354: if ($rv === false || $rv === 0) {
355: return $written;
356: }
357: }
358: return $written;
359: }
360:
361: 362: 363: 364: 365: 366: 367:
368: public function read($length = 1024) {
369: if (!$this->connected) {
370: if (!$this->connect()) {
371: return false;
372: }
373: }
374:
375: if (!feof($this->connection)) {
376: $buffer = fread($this->connection, $length);
377: $info = stream_get_meta_data($this->connection);
378: if ($info['timed_out']) {
379: $this->setLastError(E_WARNING, __d('cake_dev', 'Connection timed out'));
380: return false;
381: }
382: return $buffer;
383: }
384: return false;
385: }
386:
387: 388: 389: 390: 391:
392: public function disconnect() {
393: if (!is_resource($this->connection)) {
394: $this->connected = false;
395: return true;
396: }
397: $this->connected = !fclose($this->connection);
398:
399: if (!$this->connected) {
400: $this->connection = null;
401: }
402: return !$this->connected;
403: }
404:
405: 406: 407:
408: public function __destruct() {
409: $this->disconnect();
410: }
411:
412: 413: 414: 415: 416: 417:
418: public function reset($state = null) {
419: if (empty($state)) {
420: static $initalState = array();
421: if (empty($initalState)) {
422: $initalState = get_class_vars(__CLASS__);
423: }
424: $state = $initalState;
425: }
426:
427: foreach ($state as $property => $value) {
428: $this->{$property} = $value;
429: }
430: return true;
431: }
432:
433: 434: 435: 436: 437: 438: 439: 440: 441: 442: 443:
444: public function enableCrypto($type, $clientOrServer = 'client', $enable = true) {
445: if (!array_key_exists($type . '_' . $clientOrServer, $this->_encryptMethods)) {
446: throw new InvalidArgumentException(__d('cake_dev', 'Invalid encryption scheme chosen'));
447: }
448: $enableCryptoResult = false;
449: try {
450: $enableCryptoResult = stream_socket_enable_crypto($this->connection, $enable,
451: $this->_encryptMethods[$type . '_' . $clientOrServer]);
452: } catch (Exception $e) {
453: $this->setLastError(null, $e->getMessage());
454: throw new SocketException($e->getMessage());
455: }
456: if ($enableCryptoResult === true) {
457: $this->encrypted = $enable;
458: return true;
459: }
460: $errorMessage = __d('cake_dev', 'Unable to perform enableCrypto operation on CakeSocket');
461: $this->setLastError(null, $errorMessage);
462: throw new SocketException($errorMessage);
463: }
464: }
465: