PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/phpBB/includes/functions_jabber.php

http://github.com/phpbb/phpbb3
PHP | 904 lines | 573 code | 130 blank | 201 comment | 95 complexity | 0589b799b710bbdf0a0d993814082808 MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * This file is part of the phpBB Forum Software package.
  5. *
  6. * @copyright (c) phpBB Limited <https://www.phpbb.com>
  7. * @license GNU General Public License, version 2 (GPL-2.0)
  8. *
  9. * For full copyright and license information, please see
  10. * the docs/CREDITS.txt file.
  11. *
  12. */
  13. /**
  14. * @ignore
  15. */
  16. if (!defined('IN_PHPBB'))
  17. {
  18. exit;
  19. }
  20. /**
  21. *
  22. * Jabber class from Flyspray project
  23. *
  24. * @version class.jabber2.php 1595 2008-09-19 (0.9.9)
  25. * @copyright 2006 Flyspray.org
  26. * @author Florian Schmitz (floele)
  27. *
  28. * Only slightly modified by Acyd Burn
  29. */
  30. class jabber
  31. {
  32. var $connection = null;
  33. var $session = array();
  34. var $timeout = 10;
  35. var $server;
  36. var $connect_server;
  37. var $port;
  38. var $username;
  39. var $password;
  40. var $use_ssl;
  41. var $verify_peer;
  42. var $verify_peer_name;
  43. var $allow_self_signed;
  44. var $resource = 'functions_jabber.phpbb.php';
  45. var $enable_logging;
  46. var $log_array;
  47. var $features = array();
  48. /**
  49. * Constructor
  50. *
  51. * @param string $server Jabber server
  52. * @param int $port Jabber server port
  53. * @param string $username Jabber username or JID
  54. * @param string $password Jabber password
  55. * @param boold $use_ssl Use ssl
  56. * @param bool $verify_peer Verify SSL certificate
  57. * @param bool $verify_peer_name Verify Jabber peer name
  58. * @param bool $allow_self_signed Allow self signed certificates
  59. */
  60. function __construct($server, $port, $username, $password, $use_ssl = false, $verify_peer = true, $verify_peer_name = true, $allow_self_signed = false)
  61. {
  62. $this->connect_server = ($server) ? $server : 'localhost';
  63. $this->port = ($port) ? $port : 5222;
  64. // Get the server and the username
  65. if (strpos($username, '@') === false)
  66. {
  67. $this->server = $this->connect_server;
  68. $this->username = $username;
  69. }
  70. else
  71. {
  72. $jid = explode('@', $username, 2);
  73. $this->username = $jid[0];
  74. $this->server = $jid[1];
  75. }
  76. $this->password = $password;
  77. $this->use_ssl = ($use_ssl && self::can_use_ssl()) ? true : false;
  78. $this->verify_peer = $verify_peer;
  79. $this->verify_peer_name = $verify_peer_name;
  80. $this->allow_self_signed = $allow_self_signed;
  81. // Change port if we use SSL
  82. if ($this->port == 5222 && $this->use_ssl)
  83. {
  84. $this->port = 5223;
  85. }
  86. $this->enable_logging = true;
  87. $this->log_array = array();
  88. }
  89. /**
  90. * Able to use the SSL functionality?
  91. */
  92. static public function can_use_ssl()
  93. {
  94. return @extension_loaded('openssl');
  95. }
  96. /**
  97. * Able to use TLS?
  98. */
  99. static public function can_use_tls()
  100. {
  101. 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'))
  102. {
  103. return false;
  104. }
  105. /**
  106. * Make sure the encryption stream is supported
  107. * Also seem to work without the crypto stream if correctly compiled
  108. $streams = stream_get_wrappers();
  109. if (!in_array('streams.crypto', $streams))
  110. {
  111. return false;
  112. }
  113. */
  114. return true;
  115. }
  116. /**
  117. * Sets the resource which is used. No validation is done here, only escaping.
  118. * @param string $name
  119. * @access public
  120. */
  121. function set_resource($name)
  122. {
  123. $this->resource = $name;
  124. }
  125. /**
  126. * Connect
  127. */
  128. function connect()
  129. {
  130. /* if (!$this->check_jid($this->username . '@' . $this->server))
  131. {
  132. $this->add_to_log('Error: Jabber ID is not valid: ' . $this->username . '@' . $this->server);
  133. return false;
  134. }*/
  135. $this->session['ssl'] = $this->use_ssl;
  136. if ($this->open_socket($this->connect_server, $this->port, $this->use_ssl, $this->verify_peer, $this->verify_peer_name, $this->allow_self_signed))
  137. {
  138. $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
  139. $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
  140. }
  141. else
  142. {
  143. $this->add_to_log('Error: connect() #2');
  144. return false;
  145. }
  146. // Now we listen what the server has to say...and give appropriate responses
  147. $this->response($this->listen());
  148. return true;
  149. }
  150. /**
  151. * Disconnect
  152. */
  153. function disconnect()
  154. {
  155. if ($this->connected())
  156. {
  157. // disconnect gracefully
  158. if (isset($this->session['sent_presence']))
  159. {
  160. $this->send_presence('offline', '', true);
  161. }
  162. $this->send('</stream:stream>');
  163. $this->session = array();
  164. return fclose($this->connection);
  165. }
  166. return false;
  167. }
  168. /**
  169. * Connected?
  170. */
  171. function connected()
  172. {
  173. return (is_resource($this->connection) && !feof($this->connection)) ? true : false;
  174. }
  175. /**
  176. * Initiates login (using data from contructor, after calling connect())
  177. * @access public
  178. * @return bool
  179. */
  180. function login()
  181. {
  182. if (!count($this->features))
  183. {
  184. $this->add_to_log('Error: No feature information from server available.');
  185. return false;
  186. }
  187. return $this->response($this->features);
  188. }
  189. /**
  190. * Send data to the Jabber server
  191. * @param string $xml
  192. * @access public
  193. * @return bool
  194. */
  195. function send($xml)
  196. {
  197. if ($this->connected())
  198. {
  199. $xml = trim($xml);
  200. $this->add_to_log('SEND: '. $xml);
  201. return fwrite($this->connection, $xml);
  202. }
  203. else
  204. {
  205. $this->add_to_log('Error: Could not send, connection lost (flood?).');
  206. return false;
  207. }
  208. }
  209. /**
  210. * OpenSocket
  211. * @param string $server host to connect to
  212. * @param int $port port number
  213. * @param bool $use_ssl use ssl or not
  214. * @param bool $verify_peer verify ssl certificate
  215. * @param bool $verify_peer_name verify peer name
  216. * @param bool $allow_self_signed allow self-signed ssl certificates
  217. * @access public
  218. * @return bool
  219. */
  220. function open_socket($server, $port, $use_ssl, $verify_peer, $verify_peer_name, $allow_self_signed)
  221. {
  222. if (@function_exists('dns_get_record'))
  223. {
  224. $record = @dns_get_record("_xmpp-client._tcp.$server", DNS_SRV);
  225. if (!empty($record) && !empty($record[0]['target']))
  226. {
  227. $server = $record[0]['target'];
  228. }
  229. }
  230. $options = array();
  231. if ($use_ssl)
  232. {
  233. $remote_socket = 'ssl://' . $server . ':' . $port;
  234. // Set ssl context options, see http://php.net/manual/en/context.ssl.php
  235. $options['ssl'] = array('verify_peer' => $verify_peer, 'verify_peer_name' => $verify_peer_name, 'allow_self_signed' => $allow_self_signed);
  236. }
  237. else
  238. {
  239. $remote_socket = $server . ':' . $port;
  240. }
  241. $socket_context = stream_context_create($options);
  242. if ($this->connection = @stream_socket_client($remote_socket, $errorno, $errorstr, $this->timeout, STREAM_CLIENT_CONNECT, $socket_context))
  243. {
  244. stream_set_blocking($this->connection, 0);
  245. stream_set_timeout($this->connection, 60);
  246. return true;
  247. }
  248. // Apparently an error occurred...
  249. $this->add_to_log('Error: open_socket() - ' . $errorstr);
  250. return false;
  251. }
  252. /**
  253. * Return log
  254. */
  255. function get_log()
  256. {
  257. if ($this->enable_logging && count($this->log_array))
  258. {
  259. return implode("<br /><br />", $this->log_array);
  260. }
  261. return '';
  262. }
  263. /**
  264. * Add information to log
  265. */
  266. function add_to_log($string)
  267. {
  268. if ($this->enable_logging)
  269. {
  270. $this->log_array[] = utf8_htmlspecialchars($string);
  271. }
  272. }
  273. /**
  274. * Listens to the connection until it gets data or the timeout is reached.
  275. * Thus, it should only be called if data is expected to be received.
  276. * @access public
  277. * @return mixed either false for timeout or an array with the received data
  278. */
  279. function listen($timeout = 10, $wait = false)
  280. {
  281. if (!$this->connected())
  282. {
  283. return false;
  284. }
  285. // Wait for a response until timeout is reached
  286. $start = time();
  287. $data = '';
  288. do
  289. {
  290. $read = trim(fread($this->connection, 4096));
  291. $data .= $read;
  292. }
  293. while (time() <= $start + $timeout && !feof($this->connection) && ($wait || $data == '' || $read != '' || (substr(rtrim($data), -1) != '>')));
  294. if ($data != '')
  295. {
  296. $this->add_to_log('RECV: '. $data);
  297. return $this->xmlize($data);
  298. }
  299. else
  300. {
  301. $this->add_to_log('Timeout, no response from server.');
  302. return false;
  303. }
  304. }
  305. /**
  306. * Initiates account registration (based on data used for contructor)
  307. * @access public
  308. * @return bool
  309. */
  310. function register()
  311. {
  312. if (!isset($this->session['id']) || isset($this->session['jid']))
  313. {
  314. $this->add_to_log('Error: Cannot initiate registration.');
  315. return false;
  316. }
  317. $this->send("<iq type='get' id='reg_1'><query xmlns='jabber:iq:register'/></iq>");
  318. return $this->response($this->listen());
  319. }
  320. /**
  321. * Sets account presence. No additional info required (default is "online" status)
  322. * @param $message online, offline...
  323. * @param $type dnd, away, chat, xa or nothing
  324. * @param $unavailable set this to true if you want to become unavailable
  325. * @access public
  326. * @return bool
  327. */
  328. function send_presence($message = '', $type = '', $unavailable = false)
  329. {
  330. if (!isset($this->session['jid']))
  331. {
  332. $this->add_to_log('ERROR: send_presence() - Cannot set presence at this point, no jid given.');
  333. return false;
  334. }
  335. $type = strtolower($type);
  336. $type = (in_array($type, array('dnd', 'away', 'chat', 'xa'))) ? '<show>'. $type .'</show>' : '';
  337. $unavailable = ($unavailable) ? " type='unavailable'" : '';
  338. $message = ($message) ? '<status>' . utf8_htmlspecialchars($message) .'</status>' : '';
  339. $this->session['sent_presence'] = !$unavailable;
  340. return $this->send("<presence$unavailable>" . $type . $message . '</presence>');
  341. }
  342. /**
  343. * This handles all the different XML elements
  344. * @param array $xml
  345. * @access public
  346. * @return bool
  347. */
  348. function response($xml)
  349. {
  350. if (!is_array($xml) || !count($xml))
  351. {
  352. return false;
  353. }
  354. // did we get multiple elements? do one after another
  355. // array('message' => ..., 'presence' => ...)
  356. if (count($xml) > 1)
  357. {
  358. foreach ($xml as $key => $value)
  359. {
  360. $this->response(array($key => $value));
  361. }
  362. return;
  363. }
  364. else
  365. {
  366. // or even multiple elements of the same type?
  367. // array('message' => array(0 => ..., 1 => ...))
  368. if (count(reset($xml)) > 1)
  369. {
  370. foreach (reset($xml) as $value)
  371. {
  372. $this->response(array(key($xml) => array(0 => $value)));
  373. }
  374. return;
  375. }
  376. }
  377. switch (key($xml))
  378. {
  379. case 'stream:stream':
  380. // Connection initialised (or after authentication). Not much to do here...
  381. if (isset($xml['stream:stream'][0]['#']['stream:features']))
  382. {
  383. // we already got all info we need
  384. $this->features = $xml['stream:stream'][0]['#'];
  385. }
  386. else
  387. {
  388. $this->features = $this->listen();
  389. }
  390. $second_time = isset($this->session['id']);
  391. $this->session['id'] = $xml['stream:stream'][0]['@']['id'];
  392. if ($second_time)
  393. {
  394. // If we are here for the second time after TLS, we need to continue logging in
  395. return $this->login();
  396. }
  397. // go on with authentication?
  398. if (isset($this->features['stream:features'][0]['#']['bind']) || !empty($this->session['tls']))
  399. {
  400. return $this->response($this->features);
  401. }
  402. break;
  403. case 'stream:features':
  404. // Resource binding after successful authentication
  405. if (isset($this->session['authenticated']))
  406. {
  407. // session required?
  408. $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']);
  409. $this->send("<iq type='set' id='bind_1'>
  410. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
  411. <resource>" . utf8_htmlspecialchars($this->resource) . '</resource>
  412. </bind>
  413. </iq>');
  414. return $this->response($this->listen());
  415. }
  416. // Let's use TLS if SSL is not enabled and we can actually use it
  417. if (!$this->session['ssl'] && self::can_use_tls() && self::can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls']))
  418. {
  419. $this->add_to_log('Switching to TLS.');
  420. $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n");
  421. return $this->response($this->listen());
  422. }
  423. // Does the server support SASL authentication?
  424. // I hope so, because we do (and no other method).
  425. if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) && $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl')
  426. {
  427. // Now decide on method
  428. $methods = array();
  429. foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value)
  430. {
  431. $methods[] = $value['#'];
  432. }
  433. // we prefer DIGEST-MD5
  434. // we don't want to use plain authentication (neither does the server usually) if no encryption is in place
  435. // http://www.xmpp.org/extensions/attic/jep-0078-1.7.html
  436. // The plaintext mechanism SHOULD NOT be used unless the underlying stream is encrypted (using SSL or TLS)
  437. // and the client has verified that the server certificate is signed by a trusted certificate authority.
  438. if (in_array('DIGEST-MD5', $methods))
  439. {
  440. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
  441. }
  442. else if (in_array('PLAIN', $methods) && ($this->session['ssl'] || !empty($this->session['tls'])))
  443. {
  444. // http://www.ietf.org/rfc/rfc4616.txt (PLAIN SASL Mechanism)
  445. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"
  446. . base64_encode($this->username . '@' . $this->server . chr(0) . $this->username . chr(0) . $this->password) .
  447. '</auth>');
  448. }
  449. else if (in_array('ANONYMOUS', $methods))
  450. {
  451. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
  452. }
  453. else
  454. {
  455. // not good...
  456. $this->add_to_log('Error: No authentication method supported.');
  457. $this->disconnect();
  458. return false;
  459. }
  460. return $this->response($this->listen());
  461. }
  462. else
  463. {
  464. // ok, this is it. bye.
  465. $this->add_to_log('Error: Server does not offer SASL authentication.');
  466. $this->disconnect();
  467. return false;
  468. }
  469. break;
  470. case 'challenge':
  471. // continue with authentication...a challenge literally -_-
  472. $decoded = base64_decode($xml['challenge'][0]['#']);
  473. $decoded = $this->parse_data($decoded);
  474. if (!isset($decoded['digest-uri']))
  475. {
  476. $decoded['digest-uri'] = 'xmpp/'. $this->server;
  477. }
  478. // better generate a cnonce, maybe it's needed
  479. $decoded['cnonce'] = base64_encode(md5(uniqid(mt_rand(), true)));
  480. // second challenge?
  481. if (isset($decoded['rspauth']))
  482. {
  483. $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
  484. }
  485. else
  486. {
  487. // Make sure we only use 'auth' for qop (relevant for $this->encrypt_password())
  488. // If the <response> is choking up on the changed parameter we may need to adjust encrypt_password() directly
  489. if (isset($decoded['qop']) && $decoded['qop'] != 'auth' && strpos($decoded['qop'], 'auth') !== false)
  490. {
  491. $decoded['qop'] = 'auth';
  492. }
  493. $response = array(
  494. 'username' => $this->username,
  495. 'response' => $this->encrypt_password(array_merge($decoded, array('nc' => '00000001'))),
  496. 'charset' => 'utf-8',
  497. 'nc' => '00000001',
  498. 'qop' => 'auth', // only auth being supported
  499. );
  500. foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key)
  501. {
  502. if (isset($decoded[$key]))
  503. {
  504. $response[$key] = $decoded[$key];
  505. }
  506. }
  507. $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" . base64_encode($this->implode_data($response)) . '</response>');
  508. }
  509. return $this->response($this->listen());
  510. break;
  511. case 'failure':
  512. $this->add_to_log('Error: Server sent "failure".');
  513. $this->disconnect();
  514. return false;
  515. break;
  516. case 'proceed':
  517. // continue switching to TLS
  518. $meta = stream_get_meta_data($this->connection);
  519. stream_set_blocking($this->connection, 1);
  520. if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT))
  521. {
  522. $this->add_to_log('Error: TLS mode change failed.');
  523. return false;
  524. }
  525. stream_set_blocking($this->connection, $meta['blocked']);
  526. $this->session['tls'] = true;
  527. // new stream
  528. $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
  529. $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
  530. return $this->response($this->listen());
  531. break;
  532. case 'success':
  533. // Yay, authentication successful.
  534. $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
  535. $this->session['authenticated'] = true;
  536. // we have to wait for another response
  537. return $this->response($this->listen());
  538. break;
  539. case 'iq':
  540. // we are not interested in IQs we did not expect
  541. if (!isset($xml['iq'][0]['@']['id']))
  542. {
  543. return false;
  544. }
  545. // multiple possibilities here
  546. switch ($xml['iq'][0]['@']['id'])
  547. {
  548. case 'bind_1':
  549. $this->session['jid'] = $xml['iq'][0]['#']['bind'][0]['#']['jid'][0]['#'];
  550. // and (maybe) yet another request to be able to send messages *finally*
  551. if ($this->session['sess_required'])
  552. {
  553. $this->send("<iq to='{$this->server}' type='set' id='sess_1'>
  554. <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
  555. </iq>");
  556. return $this->response($this->listen());
  557. }
  558. return true;
  559. break;
  560. case 'sess_1':
  561. return true;
  562. break;
  563. case 'reg_1':
  564. $this->send("<iq type='set' id='reg_2'>
  565. <query xmlns='jabber:iq:register'>
  566. <username>" . utf8_htmlspecialchars($this->username) . "</username>
  567. <password>" . utf8_htmlspecialchars($this->password) . "</password>
  568. </query>
  569. </iq>");
  570. return $this->response($this->listen());
  571. break;
  572. case 'reg_2':
  573. // registration end
  574. if (isset($xml['iq'][0]['#']['error']))
  575. {
  576. $this->add_to_log('Warning: Registration failed.');
  577. return false;
  578. }
  579. return true;
  580. break;
  581. case 'unreg_1':
  582. return true;
  583. break;
  584. default:
  585. $this->add_to_log('Notice: Received unexpected IQ.');
  586. return false;
  587. break;
  588. }
  589. break;
  590. case 'message':
  591. // we are only interested in content...
  592. if (!isset($xml['message'][0]['#']['body']))
  593. {
  594. return false;
  595. }
  596. $message['body'] = $xml['message'][0]['#']['body'][0]['#'];
  597. $message['from'] = $xml['message'][0]['@']['from'];
  598. if (isset($xml['message'][0]['#']['subject']))
  599. {
  600. $message['subject'] = $xml['message'][0]['#']['subject'][0]['#'];
  601. }
  602. $this->session['messages'][] = $message;
  603. break;
  604. default:
  605. // hm...don't know this response
  606. $this->add_to_log('Notice: Unknown server response (' . key($xml) . ')');
  607. return false;
  608. break;
  609. }
  610. }
  611. function send_message($to, $text, $subject = '', $type = 'normal')
  612. {
  613. if (!isset($this->session['jid']))
  614. {
  615. return false;
  616. }
  617. if (!in_array($type, array('chat', 'normal', 'error', 'groupchat', 'headline')))
  618. {
  619. $type = 'normal';
  620. }
  621. return $this->send("<message from='" . utf8_htmlspecialchars($this->session['jid']) . "' to='" . utf8_htmlspecialchars($to) . "' type='$type' id='" . uniqid('msg') . "'>
  622. <subject>" . utf8_htmlspecialchars($subject) . "</subject>
  623. <body>" . utf8_htmlspecialchars($text) . "</body>
  624. </message>"
  625. );
  626. }
  627. /**
  628. * Encrypts a password as in RFC 2831
  629. * @param array $data Needs data from the client-server connection
  630. * @access public
  631. * @return string
  632. */
  633. function encrypt_password($data)
  634. {
  635. // let's me think about <challenge> again...
  636. foreach (array('realm', 'cnonce', 'digest-uri') as $key)
  637. {
  638. if (!isset($data[$key]))
  639. {
  640. $data[$key] = '';
  641. }
  642. }
  643. $pack = md5($this->username . ':' . $data['realm'] . ':' . $this->password);
  644. if (isset($data['authzid']))
  645. {
  646. $a1 = pack('H32', $pack) . sprintf(':%s:%s:%s', $data['nonce'], $data['cnonce'], $data['authzid']);
  647. }
  648. else
  649. {
  650. $a1 = pack('H32', $pack) . sprintf(':%s:%s', $data['nonce'], $data['cnonce']);
  651. }
  652. // should be: qop = auth
  653. $a2 = 'AUTHENTICATE:'. $data['digest-uri'];
  654. return md5(sprintf('%s:%s:%s:%s:%s:%s', md5($a1), $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], md5($a2)));
  655. }
  656. /**
  657. * parse_data like a="b",c="d",... or like a="a, b", c, d="e", f=g,...
  658. * @param string $data
  659. * @access public
  660. * @return array a => b ...
  661. */
  662. function parse_data($data)
  663. {
  664. $data = explode(',', $data);
  665. $pairs = array();
  666. $key = false;
  667. foreach ($data as $pair)
  668. {
  669. $dd = strpos($pair, '=');
  670. if ($dd)
  671. {
  672. $key = trim(substr($pair, 0, $dd));
  673. $pairs[$key] = trim(trim(substr($pair, $dd + 1)), '"');
  674. }
  675. else if (strpos(strrev(trim($pair)), '"') === 0 && $key)
  676. {
  677. // We are actually having something left from "a, b" values, add it to the last one we handled.
  678. $pairs[$key] .= ',' . trim(trim($pair), '"');
  679. continue;
  680. }
  681. }
  682. return $pairs;
  683. }
  684. /**
  685. * opposite of jabber::parse_data()
  686. * @param array $data
  687. * @access public
  688. * @return string
  689. */
  690. function implode_data($data)
  691. {
  692. $return = array();
  693. foreach ($data as $key => $value)
  694. {
  695. $return[] = $key . '="' . $value . '"';
  696. }
  697. return implode(',', $return);
  698. }
  699. /**
  700. * xmlize()
  701. * @author Hans Anderson
  702. * @copyright Hans Anderson / http://www.hansanderson.com/php/xml/
  703. */
  704. function xmlize($data, $skip_white = 1, $encoding = 'UTF-8')
  705. {
  706. $data = trim($data);
  707. if (substr($data, 0, 5) != '<?xml')
  708. {
  709. // mod
  710. $data = '<root>'. $data . '</root>';
  711. }
  712. $vals = $index = $array = array();
  713. $parser = xml_parser_create($encoding);
  714. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  715. xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $skip_white);
  716. xml_parse_into_struct($parser, $data, $vals, $index);
  717. xml_parser_free($parser);
  718. $i = 0;
  719. $tagname = $vals[$i]['tag'];
  720. $array[$tagname][0]['@'] = (isset($vals[$i]['attributes'])) ? $vals[$i]['attributes'] : array();
  721. $array[$tagname][0]['#'] = $this->_xml_depth($vals, $i);
  722. if (substr($data, 0, 5) != '<?xml')
  723. {
  724. $array = $array['root'][0]['#'];
  725. }
  726. return $array;
  727. }
  728. /**
  729. * _xml_depth()
  730. * @author Hans Anderson
  731. * @copyright Hans Anderson / http://www.hansanderson.com/php/xml/
  732. */
  733. function _xml_depth($vals, &$i)
  734. {
  735. $children = array();
  736. if (isset($vals[$i]['value']))
  737. {
  738. array_push($children, $vals[$i]['value']);
  739. }
  740. while (++$i < count($vals))
  741. {
  742. switch ($vals[$i]['type'])
  743. {
  744. case 'open':
  745. $tagname = (isset($vals[$i]['tag'])) ? $vals[$i]['tag'] : '';
  746. $size = (isset($children[$tagname])) ? count($children[$tagname]) : 0;
  747. if (isset($vals[$i]['attributes']))
  748. {
  749. $children[$tagname][$size]['@'] = $vals[$i]['attributes'];
  750. }
  751. $children[$tagname][$size]['#'] = $this->_xml_depth($vals, $i);
  752. break;
  753. case 'cdata':
  754. array_push($children, $vals[$i]['value']);
  755. break;
  756. case 'complete':
  757. $tagname = $vals[$i]['tag'];
  758. $size = (isset($children[$tagname])) ? count($children[$tagname]) : 0;
  759. $children[$tagname][$size]['#'] = (isset($vals[$i]['value'])) ? $vals[$i]['value'] : array();
  760. if (isset($vals[$i]['attributes']))
  761. {
  762. $children[$tagname][$size]['@'] = $vals[$i]['attributes'];
  763. }
  764. break;
  765. case 'close':
  766. return $children;
  767. break;
  768. }
  769. }
  770. return $children;
  771. }
  772. }