Verzeichnisstruktur phpBB-3.3.15


Veröffentlicht
28.08.2024

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