PageRenderTime 56ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/jabber/XMPP/XMLStream.php

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