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: $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: 200: 201: 202: 203: 204: 205: 206: 207:
208: protected function _connectionErrorHandler($code, $message) {
209: $this->_connectionErrors[] = $message;
210: }
211:
212: 213: 214: 215: 216:
217: public function context() {
218: if (!$this->connection) {
219: return;
220: }
221: return stream_context_get_options($this->connection);
222: }
223:
224: 225: 226: 227: 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: 238: 239: 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: 250: 251: 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: 262: 263: 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: 274: 275: 276: 277: 278:
279: public function setLastError($errNum, $errStr) {
280: $this->lastError = array('num' => $errNum, 'str' => $errStr);
281: }
282:
283: 284: 285: 286: 287: 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: 307: 308: 309: 310: 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: 333: 334: 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: 351:
352: public function __destruct() {
353: $this->disconnect();
354: }
355:
356: 357: 358: 359: 360: 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: 379: 380: 381: 382: 383: 384: 385: 386: 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:
411: