Verzeichnisstruktur phpBB-3.2.0


Veröffentlicht
06.01.2017

So funktioniert es


Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück

Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis.
Auf den Verzeichnisnamen klicken, dies zeigt nur das Verzeichnis mit Inhalt an

(Beispiel Datei-Icons)

Auf das Icon klicken um den Quellcode anzuzeigen

functions_jabber.php

Zuletzt modifiziert: 09.10.2024, 12:51 - Dateigröße: 21.24 KiB


001  <?php
002  /**
003  *
004  * This file is part of the phpBB Forum Software package.
005  *
006  * @copyright (c) phpBB Limited <https://www.phpbb.com>
007  * @license GNU General Public License, version 2 (GPL-2.0)
008  *
009  * For full copyright and license information, please see
010  * the docs/CREDITS.txt file.
011  *
012  */
013   
014  /**
015  * @ignore
016  */
017  if (!defined('IN_PHPBB'))
018  {
019      exit;
020  }
021   
022  /**
023  *
024  * Jabber class from Flyspray project
025  *
026  * @version class.jabber2.php 1595 2008-09-19 (0.9.9)
027  * @copyright 2006 Flyspray.org
028  * @author Florian Schmitz (floele)
029  *
030  * Only slightly modified by Acyd Burn
031  */
032  class jabber
033  {
034      var $connection = null;
035      var $session = array();
036      var $timeout = 10;
037   
038      var $server;
039      var $connect_server;
040      var $port;
041      var $username;
042      var $password;
043      var $use_ssl;
044      var $resource = 'functions_jabber.phpbb.php';
045   
046      var $enable_logging;
047      var $log_array;
048   
049      var $features = array();
050   
051      /**
052      */
053      function jabber($server, $port, $username, $password, $use_ssl = false)
054      {
055          $this->connect_server        = ($server) ? $server : 'localhost';
056          $this->port                    = ($port) ? $port : 5222;
057   
058          // Get the server and the username
059          if (strpos($username, '@') === false)
060          {
061              $this->server = $this->connect_server;
062              $this->username = $username;
063          }
064          else
065          {
066              $jid = explode('@', $username, 2);
067   
068              $this->username = $jid[0];
069              $this->server = $jid[1];
070          }
071   
072          $this->password                = $password;
073          $this->use_ssl                = ($use_ssl && self::can_use_ssl()) ? true : false;
074   
075          // Change port if we use SSL
076          if ($this->port == 5222 && $this->use_ssl)
077          {
078              $this->port = 5223;
079          }
080   
081          $this->enable_logging        = true;
082          $this->log_array            = array();
083      }
084   
085      /**
086      * Able to use the SSL functionality?
087      */
088      static public function can_use_ssl()
089      {
090          return @extension_loaded('openssl');
091      }
092   
093      /**
094      * Able to use TLS?
095      */
096      static public function can_use_tls()
097      {
098          if (!@extension_loaded('openssl') || !function_exists('stream_socket_enable_crypto') || !function_exists('stream_get_meta_data') || !function_exists('socket_set_blocking') || !function_exists('stream_get_wrappers'))
099          {
100              return false;
101          }
102   
103          /**
104          * Make sure the encryption stream is supported
105          * Also seem to work without the crypto stream if correctly compiled
106   
107          $streams = stream_get_wrappers();
108   
109          if (!in_array('streams.crypto', $streams))
110          {
111              return false;
112          }
113          */
114   
115          return true;
116      }
117   
118      /**
119      * Sets the resource which is used. No validation is done here, only escaping.
120      * @param string $name
121      * @access public
122      */
123      function set_resource($name)
124      {
125          $this->resource = $name;
126      }
127   
128      /**
129      * Connect
130      */
131      function connect()
132      {
133  /*        if (!$this->check_jid($this->username . '@' . $this->server))
134          {
135              $this->add_to_log('Error: Jabber ID is not valid: ' . $this->username . '@' . $this->server);
136              return false;
137          }*/
138   
139          $this->session['ssl'] = $this->use_ssl;
140   
141          if ($this->open_socket($this->connect_server, $this->port, $this->use_ssl))
142          {
143              $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
144              $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
145          }
146          else
147          {
148              $this->add_to_log('Error: connect() #2');
149              return false;
150          }
151   
152          // Now we listen what the server has to say...and give appropriate responses
153          $this->response($this->listen());
154          return true;
155      }
156   
157      /**
158      * Disconnect
159      */
160      function disconnect()
161      {
162          if ($this->connected())
163          {
164              // disconnect gracefully
165              if (isset($this->session['sent_presence']))
166              {
167                  $this->send_presence('offline', '', true);
168              }
169   
170              $this->send('</stream:stream>');
171              $this->session = array();
172              return fclose($this->connection);
173          }
174   
175          return false;
176      }
177   
178      /**
179      * Connected?
180      */
181      function connected()
182      {
183          return (is_resource($this->connection) && !feof($this->connection)) ? true : false;
184      }
185   
186   
187      /**
188      * Initiates login (using data from contructor, after calling connect())
189      * @access public
190      * @return bool
191      */
192      function login()
193      {
194          if (!sizeof($this->features))
195          {
196              $this->add_to_log('Error: No feature information from server available.');
197              return false;
198          }
199   
200          return $this->response($this->features);
201      }
202   
203      /**
204      * Send data to the Jabber server
205      * @param string $xml
206      * @access public
207      * @return bool
208      */
209      function send($xml)
210      {
211          if ($this->connected())
212          {
213              $xml = trim($xml);
214              $this->add_to_log('SEND: '. $xml);
215              return fwrite($this->connection, $xml);
216          }
217          else
218          {
219              $this->add_to_log('Error: Could not send, connection lost (flood?).');
220              return false;
221          }
222      }
223   
224      /**
225      * OpenSocket
226      * @param string $server host to connect to
227      * @param int $port port number
228      * @param bool $use_ssl use ssl or not
229      * @access public
230      * @return bool
231      */
232      function open_socket($server, $port, $use_ssl = false)
233      {
234          if (@function_exists('dns_get_record'))
235          {
236              $record = @dns_get_record("_xmpp-client._tcp.$server", DNS_SRV);
237              if (!empty($record) && !empty($record[0]['target']))
238              {
239                  $server = $record[0]['target'];
240              }
241          }
242   
243          $server = $use_ssl ? 'ssl://' . $server : $server;
244   
245          if ($this->connection = @fsockopen($server, $port, $errorno, $errorstr, $this->timeout))
246          {
247              socket_set_blocking($this->connection, 0);
248              socket_set_timeout($this->connection, 60);
249   
250              return true;
251          }
252   
253          // Apparently an error occurred...
254          $this->add_to_log('Error: open_socket() - ' . $errorstr);
255          return false;
256      }
257   
258      /**
259      * Return log
260      */
261      function get_log()
262      {
263          if ($this->enable_logging && sizeof($this->log_array))
264          {
265              return implode("<br /><br />", $this->log_array);
266          }
267   
268          return '';
269      }
270   
271      /**
272      * Add information to log
273      */
274      function add_to_log($string)
275      {
276          if ($this->enable_logging)
277          {
278              $this->log_array[] = utf8_htmlspecialchars($string);
279          }
280      }
281   
282      /**
283      * Listens to the connection until it gets data or the timeout is reached.
284      * Thus, it should only be called if data is expected to be received.
285      * @access public
286      * @return mixed either false for timeout or an array with the received data
287      */
288      function listen($timeout = 10, $wait = false)
289      {
290          if (!$this->connected())
291          {
292              return false;
293          }
294   
295          // Wait for a response until timeout is reached
296          $start = time();
297          $data = '';
298   
299          do
300          {
301              $read = trim(fread($this->connection, 4096));
302              $data .= $read;
303          }
304          while (time() <= $start + $timeout && !feof($this->connection) && ($wait || $data == '' || $read != '' || (substr(rtrim($data), -1) != '>')));
305   
306          if ($data != '')
307          {
308              $this->add_to_log('RECV: '. $data);
309              return $this->xmlize($data);
310          }
311          else
312          {
313              $this->add_to_log('Timeout, no response from server.');
314              return false;
315          }
316      }
317   
318      /**
319      * Initiates account registration (based on data used for contructor)
320      * @access public
321      * @return bool
322      */
323      function register()
324      {
325          if (!isset($this->session['id']) || isset($this->session['jid']))
326          {
327              $this->add_to_log('Error: Cannot initiate registration.');
328              return false;
329          }
330   
331          $this->send("<iq type='get' id='reg_1'><query xmlns='jabber:iq:register'/></iq>");
332          return $this->response($this->listen());
333      }
334   
335      /**
336      * Sets account presence. No additional info required (default is "online" status)
337      * @param $message online, offline...
338      * @param $type dnd, away, chat, xa or nothing
339      * @param $unavailable set this to true if you want to become unavailable
340      * @access public
341      * @return bool
342      */
343      function send_presence($message = '', $type = '', $unavailable = false)
344      {
345          if (!isset($this->session['jid']))
346          {
347              $this->add_to_log('ERROR: send_presence() - Cannot set presence at this point, no jid given.');
348              return false;
349          }
350   
351          $type = strtolower($type);
352          $type = (in_array($type, array('dnd', 'away', 'chat', 'xa'))) ? '<show>'. $type .'</show>' : '';
353   
354          $unavailable = ($unavailable) ? " type='unavailable'" : '';
355          $message = ($message) ? '<status>' . utf8_htmlspecialchars($message) .'</status>' : '';
356   
357          $this->session['sent_presence'] = !$unavailable;
358   
359          return $this->send("<presence$unavailable>" . $type . $message . '</presence>');
360      }
361   
362      /**
363      * This handles all the different XML elements
364      * @param array $xml
365      * @access public
366      * @return bool
367      */
368      function response($xml)
369      {
370          if (!is_array($xml) || !sizeof($xml))
371          {
372              return false;
373          }
374   
375          // did we get multiple elements? do one after another
376          // array('message' => ..., 'presence' => ...)
377          if (sizeof($xml) > 1)
378          {
379              foreach ($xml as $key => $value)
380              {
381                  $this->response(array($key => $value));
382              }
383              return;
384          }
385          else
386          {
387              // or even multiple elements of the same type?
388              // array('message' => array(0 => ..., 1 => ...))
389              if (sizeof(reset($xml)) > 1)
390              {
391                  foreach (reset($xml) as $value)
392                  {
393                      $this->response(array(key($xml) => array(0 => $value)));
394                  }
395                  return;
396              }
397          }
398   
399          switch (key($xml))
400          {
401              case 'stream:stream':
402                  // Connection initialised (or after authentication). Not much to do here...
403   
404                  if (isset($xml['stream:stream'][0]['#']['stream:features']))
405                  {
406                      // we already got all info we need
407                      $this->features = $xml['stream:stream'][0]['#'];
408                  }
409                  else
410                  {
411                      $this->features = $this->listen();
412                  }
413   
414                  $second_time = isset($this->session['id']);
415                  $this->session['id'] = $xml['stream:stream'][0]['@']['id'];
416   
417                  if ($second_time)
418                  {
419                      // If we are here for the second time after TLS, we need to continue logging in
420                      return $this->login();
421                  }
422   
423                  // go on with authentication?
424                  if (isset($this->features['stream:features'][0]['#']['bind']) || !empty($this->session['tls']))
425                  {
426                      return $this->response($this->features);
427                  }
428              break;
429   
430              case 'stream:features':
431                  // Resource binding after successful authentication
432                  if (isset($this->session['authenticated']))
433                  {
434                      // session required?
435                      $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']);
436   
437                      $this->send("<iq type='set' id='bind_1'>
438                          <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
439                              <resource>" . utf8_htmlspecialchars($this->resource) . '</resource>
440                          </bind>
441                      </iq>');
442                      return $this->response($this->listen());
443                  }
444   
445                  // Let's use TLS if SSL is not enabled and we can actually use it
446                  if (!$this->session['ssl'] && self::can_use_tls() && self::can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls']))
447                  {
448                      $this->add_to_log('Switching to TLS.');
449                      $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n");
450                      return $this->response($this->listen());
451                  }
452   
453                  // Does the server support SASL authentication?
454   
455                  // I hope so, because we do (and no other method).
456                  if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) && $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl')
457                  {
458                      // Now decide on method
459                      $methods = array();
460   
461                      foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value)
462                      {
463                          $methods[] = $value['#'];
464                      }
465   
466                      // we prefer DIGEST-MD5
467                      // we don't want to use plain authentication (neither does the server usually) if no encryption is in place
468   
469                      // http://www.xmpp.org/extensions/attic/jep-0078-1.7.html
470                      // The plaintext mechanism SHOULD NOT be used unless the underlying stream is encrypted (using SSL or TLS)
471                      // and the client has verified that the server certificate is signed by a trusted certificate authority.
472   
473                      if (in_array('DIGEST-MD5', $methods))
474                      {
475                          $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
476                      }
477                      else if (in_array('PLAIN', $methods) && ($this->session['ssl'] || !empty($this->session['tls'])))
478                      {
479                          // http://www.ietf.org/rfc/rfc4616.txt (PLAIN SASL Mechanism)
480                          $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"
481                              . base64_encode($this->username . '@' . $this->server . chr(0) . $this->username . chr(0) . $this->password) .
482                              '</auth>');
483                      }
484                      else if (in_array('ANONYMOUS', $methods))
485                      {
486                          $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
487                      }
488                      else
489                      {
490                          // not good...
491                          $this->add_to_log('Error: No authentication method supported.');
492                          $this->disconnect();
493                          return false;
494                      }
495   
496                      return $this->response($this->listen());
497                  }
498                  else
499                  {
500                      // ok, this is it. bye.
501                      $this->add_to_log('Error: Server does not offer SASL authentication.');
502                      $this->disconnect();
503                      return false;
504                  }
505              break;
506   
507              case 'challenge':
508                  // continue with authentication...a challenge literally -_-
509                  $decoded = base64_decode($xml['challenge'][0]['#']);
510                  $decoded = $this->parse_data($decoded);
511   
512                  if (!isset($decoded['digest-uri']))
513                  {
514                      $decoded['digest-uri'] = 'xmpp/'. $this->server;
515                  }
516   
517                  // better generate a cnonce, maybe it's needed
518                  $decoded['cnonce'] = base64_encode(md5(uniqid(mt_rand(), true)));
519   
520                  // second challenge?
521                  if (isset($decoded['rspauth']))
522                  {
523                      $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
524                  }
525                  else
526                  {
527                      // Make sure we only use 'auth' for qop (relevant for $this->encrypt_password())
528                      // If the <response> is choking up on the changed parameter we may need to adjust encrypt_password() directly
529                      if (isset($decoded['qop']) && $decoded['qop'] != 'auth' && strpos($decoded['qop'], 'auth') !== false)
530                      {
531                          $decoded['qop'] = 'auth';
532                      }
533   
534                      $response = array(
535                          'username'    => $this->username,
536                          'response'    => $this->encrypt_password(array_merge($decoded, array('nc' => '00000001'))),
537                          'charset'    => 'utf-8',
538                          'nc'        => '00000001',
539                          'qop'        => 'auth',            // only auth being supported
540                      );
541   
542                      foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key)
543                      {
544                          if (isset($decoded[$key]))
545                          {
546                              $response[$key] = $decoded[$key];
547                          }
548                      }
549   
550                      $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" . base64_encode($this->implode_data($response)) . '</response>');
551                  }
552   
553                  return $this->response($this->listen());
554              break;
555   
556              case 'failure':
557                  $this->add_to_log('Error: Server sent "failure".');
558                  $this->disconnect();
559                  return false;
560              break;
561   
562              case 'proceed':
563                  // continue switching to TLS
564                  $meta = stream_get_meta_data($this->connection);
565                  socket_set_blocking($this->connection, 1);
566   
567                  if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT))
568                  {
569                      $this->add_to_log('Error: TLS mode change failed.');
570                      return false;
571                  }
572   
573                  socket_set_blocking($this->connection, $meta['blocked']);
574                  $this->session['tls'] = true;
575   
576                  // new stream
577                  $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
578                  $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
579   
580                  return $this->response($this->listen());
581              break;
582   
583              case 'success':
584                  // Yay, authentication successful.
585                  $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
586                  $this->session['authenticated'] = true;
587   
588                  // we have to wait for another response
589                  return $this->response($this->listen());
590              break;
591   
592              case 'iq':
593                  // we are not interested in IQs we did not expect
594                  if (!isset($xml['iq'][0]['@']['id']))
595                  {
596                      return false;
597                  }
598   
599                  // multiple possibilities here
600                  switch ($xml['iq'][0]['@']['id'])
601                  {
602                      case 'bind_1':
603                          $this->session['jid'] = $xml['iq'][0]['#']['bind'][0]['#']['jid'][0]['#'];
604   
605                          // and (maybe) yet another request to be able to send messages *finally*
606                          if ($this->session['sess_required'])
607                          {
608                              $this->send("<iq to='{$this->server}' type='set' id='sess_1'>
609                                  <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
610                                  </iq>");
611                              return $this->response($this->listen());
612                          }
613   
614                          return true;
615                      break;
616   
617                      case 'sess_1':
618                          return true;
619                      break;
620   
621                      case 'reg_1':
622                          $this->send("<iq type='set' id='reg_2'>
623                                  <query xmlns='jabber:iq:register'>
624                                      <username>" . utf8_htmlspecialchars($this->username) . "</username>
625                                      <password>" . utf8_htmlspecialchars($this->password) . "</password>
626                                  </query>
627                              </iq>");
628                          return $this->response($this->listen());
629                      break;
630   
631                      case 'reg_2':
632                          // registration end
633                          if (isset($xml['iq'][0]['#']['error']))
634                          {
635                              $this->add_to_log('Warning: Registration failed.');
636                              return false;
637                          }
638                          return true;
639                      break;
640   
641                      case 'unreg_1':
642                          return true;
643                      break;
644   
645                      default:
646                          $this->add_to_log('Notice: Received unexpected IQ.');
647                          return false;
648                      break;
649                  }
650              break;
651   
652              case 'message':
653                  // we are only interested in content...
654                  if (!isset($xml['message'][0]['#']['body']))
655                  {
656                      return false;
657                  }
658   
659                  $message['body'] = $xml['message'][0]['#']['body'][0]['#'];
660                  $message['from'] = $xml['message'][0]['@']['from'];
661   
662                  if (isset($xml['message'][0]['#']['subject']))
663                  {
664                      $message['subject'] = $xml['message'][0]['#']['subject'][0]['#'];
665                  }
666                  $this->session['messages'][] = $message;
667              break;
668   
669              default:
670                  // hm...don't know this response
671                  $this->add_to_log('Notice: Unknown server response (' . key($xml) . ')');
672                  return false;
673              break;
674          }
675      }
676   
677      function send_message($to, $text, $subject = '', $type = 'normal')
678      {
679          if (!isset($this->session['jid']))
680          {
681              return false;
682          }
683   
684          if (!in_array($type, array('chat', 'normal', 'error', 'groupchat', 'headline')))
685          {
686              $type = 'normal';
687          }
688   
689          return $this->send("<message from='" . utf8_htmlspecialchars($this->session['jid']) . "' to='" . utf8_htmlspecialchars($to) . "' type='$type' id='" . uniqid('msg') . "'>
690              <subject>" . utf8_htmlspecialchars($subject) . "</subject>
691              <body>" . utf8_htmlspecialchars($text) . "</body>
692              </message>"
693          );
694      }
695   
696      /**
697      * Encrypts a password as in RFC 2831
698      * @param array $data Needs data from the client-server connection
699      * @access public
700      * @return string
701      */
702      function encrypt_password($data)
703      {
704          // let's me think about <challenge> again...
705          foreach (array('realm', 'cnonce', 'digest-uri') as $key)
706          {
707              if (!isset($data[$key]))
708              {
709                  $data[$key] = '';
710              }
711          }
712   
713          $pack = md5($this->username . ':' . $data['realm'] . ':' . $this->password);
714   
715          if (isset($data['authzid']))
716          {
717              $a1 = pack('H32', $pack)  . sprintf(':%s:%s:%s', $data['nonce'], $data['cnonce'], $data['authzid']);
718          }
719          else
720          {
721              $a1 = pack('H32', $pack)  . sprintf(':%s:%s', $data['nonce'], $data['cnonce']);
722          }
723   
724          // should be: qop = auth
725          $a2 = 'AUTHENTICATE:'. $data['digest-uri'];
726   
727          return md5(sprintf('%s:%s:%s:%s:%s:%s', md5($a1), $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], md5($a2)));
728      }
729   
730      /**
731      * parse_data like a="b",c="d",... or like a="a, b", c, d="e", f=g,...
732      * @param string $data
733      * @access public
734      * @return array a => b ...
735      */
736      function parse_data($data)
737      {
738          $data = explode(',', $data);
739          $pairs = array();
740          $key = false;
741   
742          foreach ($data as $pair)
743          {
744              $dd = strpos($pair, '=');
745   
746              if ($dd)
747              {
748                  $key = trim(substr($pair, 0, $dd));
749                  $pairs[$key] = trim(trim(substr($pair, $dd + 1)), '"');
750              }
751              else if (strpos(strrev(trim($pair)), '"') === 0 && $key)
752              {
753                  // We are actually having something left from "a, b" values, add it to the last one we handled.
754                  $pairs[$key] .= ',' . trim(trim($pair), '"');
755                  continue;
756              }
757          }
758   
759          return $pairs;
760      }
761   
762      /**
763      * opposite of jabber::parse_data()
764      * @param array $data
765      * @access public
766      * @return string
767      */
768      function implode_data($data)
769      {
770          $return = array();
771          foreach ($data as $key => $value)
772          {
773              $return[] = $key . '="' . $value . '"';
774          }
775          return implode(',', $return);
776      }
777   
778      /**
779      * xmlize()
780      * @author Hans Anderson
781      * @copyright Hans Anderson / http://www.hansanderson.com/php/xml/
782      */
783      function xmlize($data, $skip_white = 1, $encoding = 'UTF-8')
784      {
785          $data = trim($data);
786   
787          if (substr($data, 0, 5) != '<?xml')
788          {
789              // mod
790              $data = '<root>'. $data . '</root>';
791          }
792   
793          $vals = $index = $array = array();
794          $parser = xml_parser_create($encoding);
795          xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
796          xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $skip_white);
797          xml_parse_into_struct($parser, $data, $vals, $index);
798          xml_parser_free($parser);
799   
800          $i = 0;
801          $tagname = $vals[$i]['tag'];
802   
803          $array[$tagname][0]['@'] = (isset($vals[$i]['attributes'])) ? $vals[$i]['attributes'] : array();
804          $array[$tagname][0]['#'] = $this->_xml_depth($vals, $i);
805   
806          if (substr($data, 0, 5) != '<?xml')
807          {
808              $array = $array['root'][0]['#'];
809          }
810   
811          return $array;
812      }
813   
814      /**
815      * _xml_depth()
816      * @author Hans Anderson
817      * @copyright Hans Anderson / http://www.hansanderson.com/php/xml/
818      */
819      function _xml_depth($vals, &$i)
820      {
821          $children = array();
822   
823          if (isset($vals[$i]['value']))
824          {
825              array_push($children, $vals[$i]['value']);
826          }
827   
828          while (++$i < sizeof($vals))
829          {
830              switch ($vals[$i]['type'])
831              {
832                  case 'open':
833   
834                      $tagname = (isset($vals[$i]['tag'])) ? $vals[$i]['tag'] : '';
835                      $size = (isset($children[$tagname])) ? sizeof($children[$tagname]) : 0;
836   
837                      if (isset($vals[$i]['attributes']))
838                      {
839                          $children[$tagname][$size]['@'] = $vals[$i]['attributes'];
840                      }
841   
842                      $children[$tagname][$size]['#'] = $this->_xml_depth($vals, $i);
843   
844                  break;
845   
846                  case 'cdata':
847                      array_push($children, $vals[$i]['value']);
848                  break;
849   
850                  case 'complete':
851   
852                      $tagname = $vals[$i]['tag'];
853                      $size = (isset($children[$tagname])) ? sizeof($children[$tagname]) : 0;
854                      $children[$tagname][$size]['#'] = (isset($vals[$i]['value'])) ? $vals[$i]['value'] : array();
855   
856                      if (isset($vals[$i]['attributes']))
857                      {
858                          $children[$tagname][$size]['@'] = $vals[$i]['attributes'];
859                      }
860   
861                  break;
862   
863                  case 'close':
864                      return $children;
865                  break;
866              }
867          }
868   
869          return $children;
870      }
871  }
872