1<?php
2
3namespace BirknerAlex\XMPPHP;
4
5	/**
6	 * XMPPHP: The PHP XMPP Library
7	 * Copyright (C) 2008  Nathanael C. Fritz
8	 * This file is part of SleekXMPP.
9	 *
10	 * XMPPHP is free software; you can redistribute it and/or modify
11	 * it under the terms of the GNU General Public License as published by
12	 * the Free Software Foundation; either version 2 of the License, or
13	 * (at your option) any later version.
14	 *
15	 * XMPPHP is distributed in the hope that it will be useful,
16	 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17	 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18	 * GNU General Public License for more details.
19	 *
20	 * You should have received a copy of the GNU General Public License
21	 * along with XMPPHP; if not, write to the Free Software
22	 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
23	 *
24	 * @category   xmpphp
25	 * @package    XMPPHP
26	 * @author     Nathanael C. Fritz <JID: fritzy@netflint.net>
27	 * @author     Stephan Wentz <JID: stephan@jabber.wentz.it>
28	 * @author     Michael Garvin <JID: gar@netflint.net>
29	 * @author     Alexander Birkner (https://github.com/BirknerAlex)
30	 * @copyright  2008 Nathanael C. Fritz
31	 */
32
33/**
34 * XMPPHP Main Class
35 *
36 * @category   xmpphp
37 * @package    XMPPHP
38 * @author     Nathanael C. Fritz <JID: fritzy@netflint.net>
39 * @author     Stephan Wentz <JID: stephan@jabber.wentz.it>
40 * @author     Michael Garvin <JID: gar@netflint.net>
41 * @copyright  2008 Nathanael C. Fritz
42 * @version    $Id$
43 */
44class XMLStream {
45	/**
46	 * @var resource
47	 */
48	protected $socket;
49	/**
50	 * @var resource
51	 */
52	protected $parser;
53	/**
54	 * @var string
55	 */
56	protected $buffer;
57	/**
58	 * @var integer
59	 */
60	protected $xml_depth = 0;
61	/**
62	 * @var string
63	 */
64	protected $host;
65	/**
66	 * @var integer
67	 */
68	protected $port;
69	/**
70	 * @var string
71	 */
72	protected $stream_start = '<stream>';
73	/**
74	 * @var string
75	 */
76	protected $stream_end = '</stream>';
77	/**
78	 * @var boolean
79	 */
80	protected $disconnected = true;
81	/**
82	 * @var boolean
83	 */
84	protected $sent_disconnect = false;
85	/**
86	 * @var array
87	 */
88	protected $ns_map = array();
89	/**
90	 * @var array
91	 */
92	protected $current_ns = array();
93	/**
94	 * @var array
95	 */
96	protected $xmlobj = null;
97	/**
98	 * @var array
99	 */
100	protected $nshandlers = array();
101	/**
102	 * @var array
103	 */
104	protected $xpathhandlers = array();
105	/**
106	 * @var array
107	 */
108	protected $idhandlers = array();
109	/**
110	 * @var array
111	 */
112	protected $eventhandlers = array();
113	/**
114	 * @var integer
115	 */
116	protected $lastid = 0;
117	/**
118	 * @var string
119	 */
120	protected $default_ns;
121	/**
122	 * @var string[]
123	 */
124	protected $until = array();
125	/**
126	 * @var int[]
127	 */
128	protected $until_count = array();
129	/**
130	 * @var array
131	 */
132	protected $until_happened = false;
133	/**
134	 * @var array
135	 */
136	protected $until_payload = array();
137	/**
138	 * @var Log
139	 */
140	protected $log;
141	/**
142	 * @var boolean
143	 */
144	protected $reconnect = true;
145	/**
146	 * @var boolean
147	 */
148	protected $been_reset = false;
149	/**
150	 * @var boolean
151	 */
152	protected $is_server;
153	/**
154	 * @var float
155	 */
156	protected $last_send = 0;
157	/**
158	 * @var boolean
159	 */
160	protected $use_ssl = false;
161	/**
162	 * @var integer
163	 */
164	protected $reconnectTimeout = 30;
165
166	/**
167	 * Constructor
168	 *
169	 * @param string  $host
170	 * @param string  $port
171	 * @param boolean $printlog
172	 * @param string  $loglevel
173	 * @param boolean $is_server
174	 */
175	public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) {
176		$this->reconnect = !$is_server;
177		$this->is_server = $is_server;
178		$this->host = $host;
179		$this->port = $port;
180		$this->setupParser();
181		$this->log = new Log($printlog, $loglevel);
182	}
183
184	/**
185	 * Destructor
186	 * Cleanup connection
187	 */
188	public function __destruct() {
189		if(!$this->disconnected && $this->socket) {
190			$this->disconnect();
191		}
192	}
193
194	/**
195	 * Return the log instance
196	 *
197	 * @return Log
198	 */
199	public function getLog() {
200		return $this->log;
201	}
202
203	/**
204	 * Get next ID
205	 *
206	 * @return integer
207	 */
208	public function getId() {
209		$this->lastid++;
210		return $this->lastid;
211	}
212
213	/**
214	 * Set SSL
215	 *
216	 * @return integer
217	 */
218	public function useSSL($use=true) {
219		$this->use_ssl = $use;
220	}
221
222	/**
223	 * Add ID Handler
224	 *
225	 * @param integer $id
226	 * @param string  $pointer
227	 * @param string  $obj
228	 */
229	public function addIdHandler($id, $pointer, $obj = null) {
230		$this->idhandlers[$id] = array($pointer, $obj);
231	}
232
233	/**
234	 * Add Handler
235	 *
236	 * @param string $name
237	 * @param string  $ns
238	 * @param string  $pointer
239	 * @param string  $obj
240	 * @param integer $depth
241	 */
242	public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) {
243		#TODO deprication warning
244		$this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth);
245	}
246
247	/**
248	 * Add XPath Handler
249	 *
250	 * @param string $xpath
251	 * @param string $pointer
252	 * @param
253	 */
254	public function addXPathHandler($xpath, $pointer, $obj = null) {
255		if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) {
256			$ns_tags = $regs[0];
257		} else {
258			$ns_tags = array($xpath);
259		}
260		foreach($ns_tags as $ns_tag) {
261			list($l, $r) = explode('}', $ns_tag);
262			if ($r != null) {
263				$xpart = array(substr($l, 1), $r);
264			} else {
265				$xpart = array(null, $l);
266			}
267			$xpath_array[] = $xpart;
268		}
269		$this->xpathhandlers[] = array($xpath_array, $pointer, $obj);
270	}
271
272	/**
273	 * Add Event Handler
274	 *
275	 * @param integer $id
276	 * @param string  $pointer
277	 * @param string  $obj
278	 */
279	public function addEventHandler($name, $pointer, $obj) {
280		$this->eventhandlers[] = array($name, $pointer, $obj);
281	}
282
283	/**
284	 * Connect to XMPP Host
285	 *
286	 * @param integer $timeout    Timeout in seconds
287	 * @param boolean $persistent
288	 * @param boolean $sendinit   Send XMPP starting sequence after connect
289	 *                            automatically
290	 *
291	 * @throws Exception When the connection fails
292	 */
293	public function connect($timeout = 30, $persistent = false, $sendinit = true) {
294		$this->sent_disconnect = false;
295		$starttime = time();
296
297		do {
298			$this->disconnected = false;
299			$this->sent_disconnect = false;
300			if($persistent) {
301				$conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
302			} else {
303				$conflag = STREAM_CLIENT_CONNECT;
304			}
305			$conntype = 'tcp';
306			if($this->use_ssl) $conntype = 'ssl';
307			$this->log->log("Connecting to $conntype://{$this->host}:{$this->port}");
308			try {
309				$this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag);
310			} catch (Exception $e) {
311				throw new Exception($e->getMessage());
312			}
313			if(!$this->socket) {
314				$this->log->log("Could not connect.",  Log::LEVEL_ERROR);
315				$this->disconnected = true;
316				# Take it easy for a few seconds
317				sleep(min($timeout, 5));
318			}
319		} while (!$this->socket && (time() - $starttime) < $timeout);
320
321		if ($this->socket) {
322			stream_set_blocking($this->socket, 1);
323			if($sendinit) $this->send($this->stream_start);
324		} else {
325			throw new Exception("Could not connect before timeout.");
326		}
327	}
328
329	/**
330	 * Reconnect XMPP Host
331	 *
332	 * @throws Exception When the connection fails
333	 * @uses   $reconnectTimeout
334	 * @see    setReconnectTimeout()
335	 */
336	public function doReconnect() {
337		if(!$this->is_server) {
338			$this->log->log("Reconnecting ($this->reconnectTimeout)...",  Log::LEVEL_WARNING);
339			$this->connect($this->reconnectTimeout, false, false);
340			$this->reset();
341			$this->event('reconnect');
342		}
343	}
344
345	public function setReconnectTimeout($timeout) {
346		$this->reconnectTimeout = $timeout;
347	}
348
349	/**
350	 * Disconnect from XMPP Host
351	 */
352	public function disconnect() {
353		$this->log->log("Disconnecting...",  Log::LEVEL_VERBOSE);
354		if(false == (bool) $this->socket) {
355			return;
356		}
357		$this->reconnect = false;
358		$this->send($this->stream_end);
359		$this->sent_disconnect = true;
360		$this->processUntil('end_stream', 5);
361		$this->disconnected = true;
362	}
363
364	/**
365	 * Are we are disconnected?
366	 *
367	 * @return boolean
368	 */
369	public function isDisconnected() {
370		return $this->disconnected;
371	}
372
373	/**
374	 * Checks if the given string is closed with the same tag as it is
375	 * opened. We try to be as fast as possible here.
376	 *
377	 * @param string $buff Read buffer of __process()
378	 *
379	 * @return boolean true if the buffer seems to be complete
380	 */
381	protected function bufferComplete($buff)
382	{
383		if (substr($buff, -1) != '>') {
384			return false;
385		}
386		//we always have a space since the namespace needs to be
387		//declared. could be a tab, though
388		$start = substr(
389			$buff, 1,
390			min(strpos($buff, '>', 2), strpos($buff, ' ', 2)) - 1
391		);
392		$stop  = substr($buff, -strlen($start) - 3);
393
394		if ($start == '?xml') {
395			//starting with an xml tag. this means a stream is being
396			// opened, which is not much of data, so no fear it's
397			// not complete
398			return true;
399		}
400		if (substr($stop, -2) == '/>') {
401			//one tag, i.e. <success />
402			return true;
403		}
404		if ('</' . $start . '>' == $stop) {
405			return true;
406		}
407
408		return false;
409	}
410
411	/**
412	 * Core reading tool
413	 *
414	 * @param mixed   $maximum Limit when to return
415	 *                         - 0: only read if data is immediately ready
416	 *                         - NULL: wait forever and ever
417	 *                         - integer: process for this amount of microseconds
418	 * @param boolean $return_when_received Immediately return when data have been
419	 *                                      received
420	 *
421	 * @return boolean True when all goes well, false when something fails
422	 */
423	private function __process($maximum = 5, $return_when_received = false)
424	{
425		$remaining = $maximum;
426
427		do {
428			$starttime = (microtime(true) * 1000000);
429			$read = array($this->socket);
430			$write = array();
431			$except = array();
432			if (is_null($maximum)) {
433				$secs = NULL;
434				$usecs = NULL;
435			} else if ($maximum == 0) {
436				$secs = 0;
437				$usecs = 0;
438			} else {
439				$usecs = $remaining % 1000000;
440				$secs = floor(($remaining - $usecs) / 1000000);
441			}
442			$updated = @stream_select($read, $write, $except, $secs, $usecs);
443			if ($updated === false) {
444				$this->log->log("Error on stream_select()",  Log::LEVEL_VERBOSE);
445				if ($this->reconnect) {
446					$this->doReconnect();
447				} else {
448					fclose($this->socket);
449					$this->socket = NULL;
450					return false;
451				}
452			} else if ($updated > 0) {
453				$buff = '';
454				do {
455					if ($buff != '') {
456						//disable blocking for now because fread() will
457						// block until the 4k are full if we already
458						// read a part of the packet
459						stream_set_blocking($this->socket, 0);
460					}
461					$part = fread($this->socket, 4096);
462					stream_set_blocking($this->socket, 1);
463
464					if (!$part && feof($this->socket)) {
465						if($this->reconnect) {
466							$this->doReconnect();
467						} else {
468							fclose($this->socket);
469							$this->socket = NULL;
470							return false;
471						}
472					}
473					$this->log->log("RECV: $part",  Log::LEVEL_VERBOSE);
474					$buff .= $part;
475				} while (!$this->bufferComplete($buff));
476
477				xml_parse($this->parser, $buff, false);
478				if ($return_when_received) {
479					return true;
480				}
481			} else {
482				# $updated == 0 means no changes during timeout.
483			}
484			$endtime = (microtime(true)*1000000);
485			$time_past = $endtime - $starttime;
486			$remaining = $remaining - $time_past;
487		} while (is_null($maximum) || $remaining > 0);
488		return true;
489	}
490
491	/**
492	 * Process
493	 *
494	 * @return string
495	 */
496	public function process() {
497		$this->__process(NULL);
498	}
499
500	/**
501	 * Process until a timeout occurs
502	 *
503	 * @param integer $timeout Time in seconds
504	 *
505	 * @return string
506	 *
507	 * @see __process()
508	 */
509	public function processTime($timeout=NULL) {
510		if (is_null($timeout)) {
511			return $this->__process(NULL);
512		} else {
513			return $this->__process($timeout * 1000000);
514		}
515	}
516
517	/**
518	 * Process until a specified event or a timeout occurs
519	 *
520	 * @param string|array $event   Event name or array of event names
521	 * @param integer      $timeout Timeout in seconds
522	 *
523	 * @return array Payload
524	 */
525	public function processUntil($event, $timeout = -1)
526	{
527		if ($this->disconnected) {
528			throw new Exception('You need to connect first');
529		}
530
531		$start = time();
532		if (!is_array($event)) {
533			$event = array($event);
534		}
535
536		$this->until[] = $event;
537		end($this->until);
538		$event_key = key($this->until);
539		reset($this->until);
540
541		$this->until_count[$event_key] = 0;
542		$updated = '';
543		while (!$this->disconnected
544			&& $this->until_count[$event_key] < 1
545			&& ($timeout == -1 || time() - $start < $timeout)
546		) {
547			$maximum = $timeout == -1
548				? NULL
549				: ($timeout - (time() - $start)) * 1000000;
550			$ret = $this->__process($maximum, true);
551			if (!$ret) {
552				break;
553			}
554		}
555
556		if (array_key_exists($event_key, $this->until_payload)) {
557			$payload = $this->until_payload[$event_key];
558			unset($this->until_payload[$event_key]);
559			unset($this->until_count[$event_key]);
560			unset($this->until[$event_key]);
561		} else {
562			$payload = array();
563		}
564
565		return $payload;
566	}
567
568	/**
569	 * Obsolete?
570	 */
571	public function Xapply_socket($socket) {
572		$this->socket = $socket;
573	}
574
575	/**
576	 * XML start callback
577	 *
578	 * @see xml_set_element_handler
579	 *
580	 * @param resource $parser
581	 * @param string   $name
582	 */
583	public function startXML($parser, $name, $attr) {
584		if($this->been_reset) {
585			$this->been_reset = false;
586			$this->xml_depth = 0;
587		}
588		$this->xml_depth++;
589		if(array_key_exists('XMLNS', $attr)) {
590			$this->current_ns[$this->xml_depth] = $attr['XMLNS'];
591		} else {
592			$this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1];
593			if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns;
594		}
595		$ns = $this->current_ns[$this->xml_depth];
596		foreach($attr as $key => $value) {
597			if(strstr($key, ":")) {
598				$key = explode(':', $key);
599				$key = $key[1];
600				$this->ns_map[$key] = $value;
601			}
602		}
603		if(!strstr($name, ":") === false)
604		{
605			$name = explode(':', $name);
606			$ns = $this->ns_map[$name[0]];
607			$name = $name[1];
608		}
609		$obj = new XMLObj($name, $ns, $attr);
610		if($this->xml_depth > 1) {
611			$this->xmlobj[$this->xml_depth - 1]->subs[] = $obj;
612		}
613		$this->xmlobj[$this->xml_depth] = $obj;
614	}
615
616	/**
617	 * XML end callback
618	 *
619	 * @see xml_set_element_handler
620	 *
621	 * @param resource $parser
622	 * @param string   $name
623	 */
624	public function endXML($parser, $name) {
625		#$this->log->log("Ending $name",  Log::LEVEL_DEBUG);
626		#print "$name\n";
627		if($this->been_reset) {
628			$this->been_reset = false;
629			$this->xml_depth = 0;
630		}
631		$this->xml_depth--;
632		if($this->xml_depth == 1) {
633			#clean-up old objects
634			#$found = false; #FIXME This didn't appear to be in use --Gar
635			foreach($this->xpathhandlers as $handler) {
636				if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) {
637					$searchxml = $this->xmlobj[2];
638					$nstag = array_shift($handler[0]);
639					if (($nstag[0] == null or $searchxml->ns == $nstag[0]) and ($nstag[1] == "*" or $nstag[1] == $searchxml->name)) {
640						foreach($handler[0] as $nstag) {
641							if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns=$nstag[0])) {
642								$searchxml = $searchxml->sub($nstag[1], $ns=$nstag[0]);
643							} else {
644								$searchxml = null;
645								break;
646							}
647						}
648						if ($searchxml !== null) {
649							if($handler[2] === null) $handler[2] = $this;
650							$this->log->log("Calling {$handler[1]}",  Log::LEVEL_DEBUG);
651							$handler[2]->{$handler[1]}($this->xmlobj[2]);
652						}
653					}
654				}
655			}
656			foreach($this->nshandlers as $handler) {
657				if($handler[4] != 1 and array_key_exists(2, $this->xmlobj) and  $this->xmlobj[2]->hasSub($handler[0])) {
658					$searchxml = $this->xmlobj[2]->sub($handler[0]);
659				} elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) {
660					$searchxml = $this->xmlobj[2];
661				}
662				if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) {
663					if($handler[3] === null) $handler[3] = $this;
664					$this->log->log("Calling {$handler[2]}",  Log::LEVEL_DEBUG);
665					$handler[3]->{$handler[2]}($this->xmlobj[2]);
666				}
667			}
668			foreach($this->idhandlers as $id => $handler) {
669				if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) {
670					if($handler[1] === null) $handler[1] = $this;
671					$handler[1]->{$handler[0]}($this->xmlobj[2]);
672					#id handlers are only used once
673					unset($this->idhandlers[$id]);
674					break;
675				}
676			}
677			if(is_array($this->xmlobj)) {
678				$this->xmlobj = array_slice($this->xmlobj, 0, 1);
679				if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMLObj) {
680					$this->xmlobj[0]->subs = null;
681				}
682			}
683			unset($this->xmlobj[2]);
684		}
685		if($this->xml_depth == 0 and !$this->been_reset) {
686			if(!$this->disconnected) {
687				if(!$this->sent_disconnect) {
688					$this->send($this->stream_end);
689				}
690				$this->disconnected = true;
691				$this->sent_disconnect = true;
692				fclose($this->socket);
693				if($this->reconnect) {
694					$this->doReconnect();
695				}
696			}
697			$this->event('end_stream');
698		}
699	}
700
701	/**
702	 * XML character callback
703	 * @see xml_set_character_data_handler
704	 *
705	 * @param resource $parser
706	 * @param string   $data
707	 */
708	public function charXML($parser, $data) {
709		if(array_key_exists($this->xml_depth, $this->xmlobj)) {
710			$this->xmlobj[$this->xml_depth]->data .= $data;
711		}
712	}
713
714	/**
715	 * Event?
716	 *
717	 * @param string $name
718	 * @param string $payload
719	 */
720	public function event($name, $payload = null) {
721		$this->log->log("EVENT: $name",  Log::LEVEL_DEBUG);
722		foreach($this->eventhandlers as $handler) {
723			if($name == $handler[0]) {
724				if($handler[2] === null) {
725					$handler[2] = $this;
726				}
727				$handler[2]->{$handler[1]}($payload);
728			}
729		}
730
731		foreach($this->until as $key => $until) {
732			if(is_array($until)) {
733				if(in_array($name, $until)) {
734					$this->until_payload[$key][] = array($name, $payload);
735					if(!isset($this->until_count[$key])) {
736						$this->until_count[$key] = 0;
737					}
738					$this->until_count[$key] += 1;
739					#$this->until[$key] = false;
740				}
741			}
742		}
743	}
744
745	/**
746	 * Read from socket
747	 */
748	public function read() {
749		$buff = @fread($this->socket, 1024);
750		if(!$buff) {
751			if($this->reconnect) {
752				$this->doReconnect();
753			} else {
754				fclose($this->socket);
755				return false;
756			}
757		}
758		$this->log->log("RECV: $buff",  Log::LEVEL_VERBOSE);
759		xml_parse($this->parser, $buff, false);
760	}
761
762	/**
763	 * Send to socket
764	 *
765	 * @param string $msg
766	 */
767	public function send($msg, $timeout=NULL) {
768
769		if (is_null($timeout)) {
770			$secs = NULL;
771			$usecs = NULL;
772		} else if ($timeout == 0) {
773			$secs = 0;
774			$usecs = 0;
775		} else {
776			$maximum = $timeout * 1000000;
777			$usecs = $maximum % 1000000;
778			$secs = floor(($maximum - $usecs) / 1000000);
779		}
780
781		$read = array();
782		$write = array($this->socket);
783		$except = array();
784
785		$select = @stream_select($read, $write, $except, $secs, $usecs);
786
787		if($select === False) {
788			$this->log->log("ERROR sending message; reconnecting.");
789			$this->doReconnect();
790			# TODO: retry send here
791			return false;
792		} elseif ($select > 0) {
793			$this->log->log("Socket is ready; send it.", Log::LEVEL_VERBOSE);
794		} else {
795			$this->log->log("Socket is not ready; break.", Log::LEVEL_ERROR);
796			return false;
797		}
798
799		$sentbytes = @fwrite($this->socket, $msg);
800		$this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), Log::LEVEL_VERBOSE);
801		if($sentbytes === FALSE) {
802			$this->log->log("ERROR sending message; reconnecting.", Log::LEVEL_ERROR);
803			$this->doReconnect();
804			return false;
805		}
806		$this->log->log("Successfully sent $sentbytes bytes.", Log::LEVEL_VERBOSE);
807		return $sentbytes;
808	}
809
810	public function time() {
811		list($usec, $sec) = explode(" ", microtime());
812		return (float)$sec + (float)$usec;
813	}
814
815	/**
816	 * Reset connection
817	 */
818	public function reset() {
819		$this->xml_depth = 0;
820		unset($this->xmlobj);
821		$this->xmlobj = array();
822		$this->setupParser();
823		if(!$this->is_server) {
824			$this->send($this->stream_start);
825		}
826		$this->been_reset = true;
827	}
828
829	/**
830	 * Setup the XML parser
831	 */
832	public function setupParser() {
833		$this->parser = xml_parser_create('UTF-8');
834		xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
835		xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
836		xml_set_object($this->parser, $this);
837		xml_set_element_handler($this->parser, 'startXML', 'endXML');
838		xml_set_character_data_handler($this->parser, 'charXML');
839	}
840
841	public function readyToProcess() {
842		$read = array($this->socket);
843		$write = array();
844		$except = array();
845		$updated = @stream_select($read, $write, $except, 0);
846		return (($updated !== false) && ($updated > 0));
847	}
848}
849