~ubuntu-branches/ubuntu/maverick/libxmpp-php/maverick

« back to all changes in this revision

Viewing changes to XMPPHP/XMLStream.php

  • Committer: Bazaar Package Importer
  • Author(s): Daniel Watkins
  • Date: 2008-08-31 21:23:10 UTC
  • Revision ID: james.westby@ubuntu.com-20080831212310-rkqpt30dzzq4wxr6
Tags: upstream-0~svn53
ImportĀ upstreamĀ versionĀ 0~svn53

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * XMPPHP: The PHP XMPP Library
 
4
 * Copyright (C) 2008  Nathanael C. Fritz
 
5
 * This file is part of SleekXMPP.
 
6
 * 
 
7
 * XMPPHP is free software; you can redistribute it and/or modify
 
8
 * it under the terms of the GNU General Public License as published by
 
9
 * the Free Software Foundation; either version 2 of the License, or
 
10
 * (at your option) any later version.
 
11
 * 
 
12
 * XMPPHP is distributed in the hope that it will be useful,
 
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
 * GNU General Public License for more details.
 
16
 * 
 
17
 * You should have received a copy of the GNU General Public License
 
18
 * along with XMPPHP; if not, write to the Free Software
 
19
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
20
 *
 
21
 * @category   xmpphp 
 
22
 * @package     XMPPHP
 
23
 * @author       Nathanael C. Fritz <JID: fritzy@netflint.net>
 
24
 * @author       Stephan Wentz <JID: stephan@jabber.wentz.it>
 
25
 * @copyright  2008 Nathanael C. Fritz
 
26
 */
 
27
 
 
28
/** XMPPHP_Exception */
 
29
require_once 'Exception.php';
 
30
 
 
31
/** XMPPHP_XMLObj */
 
32
require_once 'XMLObj.php';
 
33
 
 
34
/** XMPPHP_Log */
 
35
require_once 'Log.php';
 
36
 
 
37
/**
 
38
 * XMPPHP XML Stream
 
39
 * 
 
40
 * @category   xmpphp 
 
41
 * @package     XMPPHP
 
42
 * @author       Nathanael C. Fritz <JID: fritzy@netflint.net>
 
43
 * @author       Stephan Wentz <JID: stephan@jabber.wentz.it>
 
44
 * @copyright  2008 Nathanael C. Fritz
 
45
 * @version     $Id$
 
46
 */
 
47
class XMPPHP_XMLStream {
 
48
        /**
 
49
         * @var resource
 
50
         */
 
51
        protected $socket;
 
52
        /**
 
53
         * @var resource
 
54
         */
 
55
        protected $parser;
 
56
        /**
 
57
         * @var string
 
58
         */
 
59
        protected $buffer;
 
60
        /**
 
61
         * @var integer
 
62
         */
 
63
        protected $xml_depth = 0;
 
64
        /**
 
65
         * @var string
 
66
         */
 
67
        protected $host;
 
68
        /**
 
69
         * @var integer
 
70
         */
 
71
        protected $port;
 
72
        /**
 
73
         * @var string
 
74
         */
 
75
        protected $stream_start = '<stream>';
 
76
        /**
 
77
         * @var string
 
78
         */
 
79
        protected $stream_end = '</stream>';
 
80
        /**
 
81
         * @var boolean
 
82
         */
 
83
        protected $disconnected = false;
 
84
        /**
 
85
         * @var boolean
 
86
         */
 
87
        protected $sent_disconnect = false;
 
88
        /**
 
89
         * @var array
 
90
         */
 
91
        protected $ns_map = array();
 
92
        /**
 
93
         * @var array
 
94
         */
 
95
        protected $current_ns = array();
 
96
        /**
 
97
         * @var array
 
98
         */
 
99
        protected $xmlobj = null;
 
100
        /**
 
101
         * @var array
 
102
         */
 
103
        protected $nshandlers = array();
 
104
        /**
 
105
         * @var array
 
106
         */
 
107
        protected $idhandlers = array();
 
108
        /**
 
109
         * @var array
 
110
         */
 
111
        protected $eventhandlers = array();
 
112
        /**
 
113
         * @var integer
 
114
         */
 
115
        protected $lastid = 0;
 
116
        /**
 
117
         * @var string
 
118
         */
 
119
        protected $default_ns;
 
120
        /**
 
121
         * @var string
 
122
         */
 
123
        protected $until = '';
 
124
        /**
 
125
         * @var array
 
126
         */
 
127
        protected $until_happened = false;
 
128
        /**
 
129
         * @var array
 
130
         */
 
131
        protected $until_payload = array();
 
132
        /**
 
133
         * @var XMPPHP_Log
 
134
         */
 
135
        protected $log;
 
136
        /**
 
137
         * @var boolean
 
138
         */
 
139
        protected $reconnect = true;
 
140
        /**
 
141
         * @var boolean
 
142
         */
 
143
        protected $been_reset = false;
 
144
        /**
 
145
         * @var boolean
 
146
         */
 
147
        protected $is_server;
 
148
        /**
 
149
         * @var float
 
150
         */
 
151
        protected $last_send = 0;
 
152
        /**
 
153
         * @var boolean
 
154
         */
 
155
        protected $use_ssl = false;
 
156
 
 
157
        /**
 
158
         * Constructor
 
159
         *
 
160
         * @param string  $host
 
161
         * @param string  $port
 
162
         * @param boolean $printlog
 
163
         * @param string  $loglevel
 
164
         * @param boolean $is_server
 
165
         */
 
166
        public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) {
 
167
                $this->reconnect = !$is_server;
 
168
                $this->is_server = $is_server;
 
169
                $this->host = $host;
 
170
                $this->port = $port;
 
171
                $this->setupParser();
 
172
                $this->log = new XMPPHP_Log($printlog, $loglevel);
 
173
        }
 
174
 
 
175
        /**
 
176
         * Destructor
 
177
         * Cleanup connection
 
178
         */
 
179
        public function __destruct() {
 
180
                if(!$this->disconnected && $this->socket) {
 
181
                        $this->disconnect();
 
182
                }
 
183
        }
 
184
        
 
185
        /**
 
186
         * Return the log instance
 
187
         *
 
188
         * @return XMPPHP_Log
 
189
         */
 
190
        public function getLog() {
 
191
                return $this->log;
 
192
        }
 
193
        
 
194
        /**
 
195
         * Get next ID
 
196
         *
 
197
         * @return integer
 
198
         */
 
199
        public function getId() {
 
200
                $this->lastid++;
 
201
                return $this->lastid;
 
202
        }
 
203
 
 
204
        /**
 
205
         * Set SSL
 
206
         *
 
207
         * @return integer
 
208
         */
 
209
        public function useSSL($use=true) {
 
210
                $this->use_ssl = $use;
 
211
        }
 
212
 
 
213
        /**
 
214
         * Add ID Handler
 
215
         *
 
216
         * @param integer $id
 
217
         * @param string  $pointer
 
218
         * @param string  $obj
 
219
         */
 
220
        public function addIdHandler($id, $pointer, $obj = null) {
 
221
                $this->idhandlers[$id] = array($pointer, $obj);
 
222
        }
 
223
 
 
224
        /**
 
225
         * Add Handler
 
226
         *
 
227
         * @param integer $id
 
228
         * @param string  $ns
 
229
         * @param string  $pointer
 
230
         * @param string  $obj
 
231
         * @param integer $depth
 
232
         */
 
233
        public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) {
 
234
                $this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth);
 
235
        }
 
236
 
 
237
        /**
 
238
         * Add Evemt Handler
 
239
         *
 
240
         * @param integer $id
 
241
         * @param string  $pointer
 
242
         * @param string  $obj
 
243
         */
 
244
        public function addEventHandler($name, $pointer, $obj) {
 
245
                $this->eventhandlers[] = array($name, $pointer, $obj);
 
246
        }
 
247
 
 
248
        /**
 
249
         * Connect to XMPP Host
 
250
         *
 
251
         * @param integer $timeout
 
252
         * @param boolean $persistent
 
253
         * @param boolean $sendinit
 
254
         */
 
255
        public function connect($timeout = 30, $persistent = false, $sendinit = true) {
 
256
                $this->disconnected = false;
 
257
                $this->sent_disconnect = false;
 
258
                if($persistent) {
 
259
                        $conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
 
260
                } else {
 
261
                        $conflag = STREAM_CLIENT_CONNECT;
 
262
                }
 
263
                $conntype = 'tcp';
 
264
                if($this->use_ssl) $conntype = 'ssl';
 
265
                $this->log->log("Connecting to $conntype://{$this->host}:{$this->port}");
 
266
                try {
 
267
                        $this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag);
 
268
                } catch (Exception $e) {
 
269
                        throw new XMPPHP_Exception($e->getMessage());
 
270
                }
 
271
                if(!$this->socket) {
 
272
                        $this->log->log("Could not connect.",  XMPPHP_Log::LEVEL_ERROR);
 
273
                        $this->disconnected = true;
 
274
                        
 
275
                        throw new XMPPHP_Exception('Could not connect.');
 
276
                }
 
277
                stream_set_blocking($this->socket, 1);
 
278
                if($sendinit) $this->send($this->stream_start);
 
279
        }
 
280
 
 
281
        /**
 
282
         * Reconnect XMPP Host
 
283
         */
 
284
        public function doReconnect() {
 
285
                if(!$this->is_server) {
 
286
                        $this->log->log("Reconnecting...",  XMPPHP_Log::LEVEL_WARNING);
 
287
                        $this->connect(30, false, false);
 
288
                        $this->reset();
 
289
                }
 
290
        }
 
291
 
 
292
        /**
 
293
         * Disconnect from XMPP Host
 
294
         */
 
295
        public function disconnect() {
 
296
                $this->log->log("Disconnecting...",  XMPPHP_Log::LEVEL_VERBOSE);
 
297
                $this->reconnect = false;
 
298
                $this->send($this->stream_end);
 
299
                $this->sent_disconnect = true;
 
300
                $this->processUntil('end_stream', 5);
 
301
                $this->disconnected = true;
 
302
        }
 
303
 
 
304
        /**
 
305
         * Are we are disconnected?
 
306
         *
 
307
         * @return boolean
 
308
         */
 
309
        public function isDisconnected() {
 
310
                return $this->disconnected;
 
311
        }
 
312
 
 
313
        private function __process() {
 
314
                $read = array($this->socket);
 
315
                $write = null;
 
316
                $except = null;
 
317
                $updated = @stream_select($read, $write, $except, 1);
 
318
                if ($updated > 0) {
 
319
                        $buff = @fread($this->socket, 1024);
 
320
                        if(!$buff) { 
 
321
                                if($this->reconnect) {
 
322
                                        $this->doReconnect();
 
323
                                } else {
 
324
                                        fclose($this->socket);
 
325
                                        return false;
 
326
                                }
 
327
                        }
 
328
                        $this->log->log("RECV: $buff",  XMPPHP_Log::LEVEL_VERBOSE);
 
329
                        xml_parse($this->parser, $buff, false);
 
330
                }
 
331
        }
 
332
        
 
333
        /**
 
334
         * Process
 
335
         *
 
336
         * @return string
 
337
         */
 
338
        public function process() {
 
339
                $updated = '';
 
340
                while(!$this->disconnect) {
 
341
                        $this->__process();
 
342
                }
 
343
        }
 
344
 
 
345
        /**
 
346
         * Process until a timeout occurs
 
347
         *
 
348
         * @param integer $timeout
 
349
         * @return string
 
350
         */
 
351
        public function processTime($timeout = -1) {
 
352
                $start = time();
 
353
                $updated = '';
 
354
                while(!$this->disconnected and ($timeout == -1 or time() - $start < $timeout)) {
 
355
                        $this->__process();
 
356
                }
 
357
        }
 
358
 
 
359
        /**
 
360
         * Process until a specified event or a timeout occurs
 
361
         *
 
362
         * @param string|array $event
 
363
         * @param integer $timeout
 
364
         * @return string
 
365
         */
 
366
        public function processUntil($event, $timeout=-1) {
 
367
                $start = time();
 
368
                if(!is_array($event)) $event = array($event);
 
369
                $this->until[] = $event;
 
370
                end($this->until);
 
371
                $event_key = key($this->until);
 
372
                reset($this->until);
 
373
                $updated = '';
 
374
                while(!$this->disconnected and $this->until[$event_key] and (time() - $start < $timeout or $timeout == -1)) {
 
375
                        $this->__process();
 
376
                }
 
377
                if(array_key_exists($event_key, $this->until_payload)) {
 
378
                        $payload = $this->until_payload[$event_key];
 
379
                } else {
 
380
                        $payload = array();
 
381
                }
 
382
                unset($this->until_payload[$event_key]);
 
383
                return $payload;
 
384
        }
 
385
 
 
386
        /**
 
387
         * Obsolete?
 
388
         */
 
389
        public function Xapply_socket($socket) {
 
390
                $this->socket = $socket;
 
391
        }
 
392
 
 
393
        /**
 
394
         * XML start callback
 
395
         * 
 
396
         * @see xml_set_element_handler
 
397
         *
 
398
         * @param resource $parser
 
399
         * @param string   $name
 
400
         */
 
401
        public function startXML($parser, $name, $attr) {
 
402
                if($this->been_reset) {
 
403
                        $this->been_reset = false;
 
404
                        $this->xml_depth = 0;
 
405
                }
 
406
                $this->xml_depth++;
 
407
                if(array_key_exists('XMLNS', $attr)) {
 
408
                        $this->current_ns[$this->xml_depth] = $attr['XMLNS'];
 
409
                } else {
 
410
                        $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1];
 
411
                        if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns;
 
412
                }
 
413
                $ns = $this->current_ns[$this->xml_depth];
 
414
                foreach($attr as $key => $value) {
 
415
                        if(strstr($key, ":")) {
 
416
                                $key = explode(':', $key);
 
417
                                $key = $key[1];
 
418
                                $this->ns_map[$key] = $value;
 
419
                        }
 
420
                }
 
421
                if(!strstr($name, ":") === false)
 
422
                {
 
423
                        $name = explode(':', $name);
 
424
                        $ns = $this->ns_map[$name[0]];
 
425
                        $name = $name[1];
 
426
                }
 
427
                $obj = new XMPPHP_XMLObj($name, $ns, $attr);
 
428
                if($this->xml_depth > 1) {
 
429
                        $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj;
 
430
                }
 
431
                $this->xmlobj[$this->xml_depth] = $obj;
 
432
        }
 
433
 
 
434
        /**
 
435
         * XML end callback
 
436
         * 
 
437
         * @see xml_set_element_handler
 
438
         *
 
439
         * @param resource $parser
 
440
         * @param string   $name
 
441
         */
 
442
        public function endXML($parser, $name) {
 
443
                #$this->log->log("Ending $name",  XMPPHP_Log::LEVEL_DEBUG);
 
444
                #print "$name\n";
 
445
                if($this->been_reset) {
 
446
                        $this->been_reset = false;
 
447
                        $this->xml_depth = 0;
 
448
                }
 
449
                $this->xml_depth--;
 
450
                if($this->xml_depth == 1) {
 
451
                        #clean-up old objects
 
452
                        $found = false;
 
453
                        foreach($this->nshandlers as $handler) {
 
454
                                if($handler[4] != 1 and $this->xmlobj[2]->hasSub($handler[0])) {
 
455
                                        $searchxml = $this->xmlobj[2]->sub($handler[0]);
 
456
                                } elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) {
 
457
                                        $searchxml = $this->xmlobj[2];
 
458
                                }
 
459
                                if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) {
 
460
                                        if($handler[3] === null) $handler[3] = $this;
 
461
                                        $this->log->log("Calling {$handler[2]}",  XMPPHP_Log::LEVEL_DEBUG);
 
462
                                        $handler[3]->$handler[2]($this->xmlobj[2]);
 
463
                                }
 
464
                        }
 
465
                        foreach($this->idhandlers as $id => $handler) {
 
466
                                if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) {
 
467
                                        if($handler[1] === null) $handler[1] = $this;
 
468
                                        $handler[1]->$handler[0]($this->xmlobj[2]);
 
469
                                        #id handlers are only used once
 
470
                                        unset($this->idhandlers[$id]);
 
471
                                        break;
 
472
                                }
 
473
                        }
 
474
                        if(is_array($this->xmlobj)) {
 
475
                                $this->xmlobj = array_slice($this->xmlobj, 0, 1);
 
476
                                if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMPPHP_XMLObj) {
 
477
                                        $this->xmlobj[0]->subs = null;
 
478
                                }
 
479
                        }
 
480
                        unset($this->xmlobj[2]);
 
481
                }
 
482
                if($this->xml_depth == 0 and !$this->been_reset) {
 
483
                        if(!$this->disconnected) {
 
484
                                if(!$this->sent_disconnect) {
 
485
                                        $this->send($this->stream_end);
 
486
                                }
 
487
                                $this->disconnected = true;
 
488
                                $this->sent_disconnect = true;
 
489
                                fclose($this->socket);
 
490
                                if($this->reconnect) {
 
491
                                        $this->doReconnect();
 
492
                                }
 
493
                        }
 
494
                        $this->event('end_stream');
 
495
                }
 
496
        }
 
497
 
 
498
        /**
 
499
         * XML character callback
 
500
         * @see xml_set_character_data_handler
 
501
         *
 
502
         * @param resource $parser
 
503
         * @param string   $data
 
504
         */
 
505
        public function charXML($parser, $data) {
 
506
                if(array_key_exists($this->xml_depth, $this->xmlobj)) {
 
507
                        $this->xmlobj[$this->xml_depth]->data .= $data;
 
508
                }
 
509
        }
 
510
 
 
511
        /**
 
512
         * Event?
 
513
         *
 
514
         * @param string $name
 
515
         * @param string $payload
 
516
         */
 
517
        public function event($name, $payload = null) {
 
518
                $this->log->log("EVENT: $name",  XMPPHP_Log::LEVEL_DEBUG);
 
519
                foreach($this->eventhandlers as $handler) {
 
520
                        if($name == $handler[0]) {
 
521
                                if($handler[2] === null) {
 
522
                                        $handler[2] = $this;
 
523
                                }
 
524
                                $handler[2]->$handler[1]($payload);
 
525
                        }
 
526
                }
 
527
                foreach($this->until as $key => $until) {
 
528
                        if(is_array($until)) {
 
529
                                if(in_array($name, $until)) {
 
530
                                        $this->until_payload[$key][] = array($name, $payload);
 
531
                                        $this->until[$key] = false;
 
532
                                }
 
533
                        }
 
534
                }
 
535
        }
 
536
 
 
537
        /**
 
538
         * Read from socket
 
539
         */
 
540
        public function read() {
 
541
                $buff = @fread($this->socket, 1024);
 
542
                if(!$buff) { 
 
543
                        if($this->reconnect) {
 
544
                                $this->doReconnect();
 
545
                        } else {
 
546
                                fclose($this->socket);
 
547
                                return false;
 
548
                        }
 
549
                }
 
550
                $this->log->log("RECV: $buff",  XMPPHP_Log::LEVEL_VERBOSE);
 
551
                xml_parse($this->parser, $buff, false);
 
552
        }
 
553
 
 
554
        /**
 
555
         * Send to socket
 
556
         *
 
557
         * @param string $msg
 
558
         */
 
559
        public function send($msg, $rec=false) {
 
560
                if($this->time() - $this->last_send < .1) {
 
561
                        usleep(100000);
 
562
                }
 
563
                $wait = true;
 
564
                while($wait) {
 
565
                        $read = null;
 
566
                        $write = array($this->socket);
 
567
                        $except = null;
 
568
                        $select = @stream_select($read, $write, $except, 0, 0);
 
569
                        if($select === False) {
 
570
                                $this->doReconnect();
 
571
                                return false;
 
572
                        } elseif ($select > 0) {
 
573
                                $wait = false;
 
574
                        } else {
 
575
                                usleep(100000);
 
576
                                //$this->processTime(.25);
 
577
                        }
 
578
                }
 
579
                $sentbytes = @fwrite($this->socket, $msg, 1024);
 
580
                $this->last_send = $this->time();
 
581
                $this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'),  XMPPHP_Log::LEVEL_VERBOSE);
 
582
                if($sentbytes === FALSE) {
 
583
                        $this->doReconnect();
 
584
                } elseif ($sentbytes != mb_strlen($msg, '8bit')) {
 
585
                        $this->send(mb_substr($msg, $sentbytes, mb_strlen($msg, '8bit'), '8bit'), true);
 
586
                }
 
587
        }
 
588
 
 
589
        public function time() {
 
590
                list($usec, $sec) = explode(" ", microtime());
 
591
                return (float)$sec + (float)$usec;
 
592
        }
 
593
 
 
594
        /**
 
595
         * Reset connection
 
596
         */
 
597
        public function reset() {
 
598
                $this->xml_depth = 0;
 
599
                unset($this->xmlobj);
 
600
                $this->xmlobj = array();
 
601
                $this->setupParser();
 
602
                if(!$this->is_server) {
 
603
                        $this->send($this->stream_start);
 
604
                }
 
605
                $this->been_reset = true;
 
606
        }
 
607
 
 
608
        /**
 
609
         * Setup the XML parser
 
610
         */
 
611
        public function setupParser() {
 
612
                $this->parser = xml_parser_create('UTF-8');
 
613
                xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
 
614
                xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
 
615
                xml_set_object($this->parser, $this);
 
616
                xml_set_element_handler($this->parser, 'startXML', 'endXML');
 
617
                xml_set_character_data_handler($this->parser, 'charXML');
 
618
        }
 
619
}