You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

505 lines
17 KiB

15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
15 years ago
  1. <?php
  2. /**
  3. * Jaxl (Jabber XMPP Library)
  4. *
  5. * Copyright (c) 2009-2010, Abhinav Singh <me@abhinavsingh.com>.
  6. * All rights reserved.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. *
  12. * * Redistributions of source code must retain the above copyright
  13. * notice, this list of conditions and the following disclaimer.
  14. *
  15. * * Redistributions in binary form must reproduce the above copyright
  16. * notice, this list of conditions and the following disclaimer in
  17. * the documentation and/or other materials provided with the
  18. * distribution.
  19. *
  20. * * Neither the name of Abhinav Singh nor the names of his
  21. * contributors may be used to endorse or promote products derived
  22. * from this software without specific prior written permission.
  23. *
  24. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  25. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  26. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  27. * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  28. * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  29. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  30. * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  31. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  32. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC
  33. * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  34. * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  35. * POSSIBILITY OF SUCH DAMAGE.
  36. *
  37. * @package jaxl
  38. * @subpackage xmpp
  39. * @author Abhinav Singh <me@abhinavsingh.com>
  40. * @copyright Abhinav Singh
  41. * @link http://code.google.com/p/jaxl
  42. */
  43. // include required classes
  44. jaxl_require(array(
  45. 'JAXLPlugin',
  46. 'JAXLog',
  47. 'XMPPGet',
  48. 'XMPPSend'
  49. ));
  50. /**
  51. * Base XMPP class
  52. */
  53. class XMPP {
  54. /**
  55. * Auth status of Jaxl instance
  56. *
  57. * @var bool
  58. */
  59. var $auth = false;
  60. /**
  61. * Connected XMPP stream session requirement status
  62. *
  63. * @var bool
  64. */
  65. var $sessionRequired = false;
  66. /**
  67. * SASL second auth challenge status
  68. *
  69. * @var bool
  70. */
  71. var $secondChallenge = false;
  72. /**
  73. * Id of connected Jaxl instance
  74. *
  75. * @var integer
  76. */
  77. var $lastid = 0;
  78. /**
  79. * Connected socket stream handler
  80. *
  81. * @var bool|object
  82. */
  83. var $stream = false;
  84. /**
  85. * Connected socket stream id
  86. *
  87. * @var bool|integer
  88. */
  89. var $streamId = false;
  90. /**
  91. * Socket stream connected host
  92. *
  93. * @var bool|string
  94. */
  95. var $streamHost = false;
  96. /**
  97. * Socket stream version
  98. *
  99. * @var bool|integer
  100. */
  101. var $streamVersion = false;
  102. /**
  103. * Last error number for connected socket stream
  104. *
  105. * @var bool|integer
  106. */
  107. var $streamENum = false;
  108. /**
  109. * Last error string for connected socket stream
  110. *
  111. * @var bool|string
  112. */
  113. var $streamEStr = false;
  114. /**
  115. * Timeout value for connecting socket stream
  116. *
  117. * @var bool|integer
  118. */
  119. var $streamTimeout = false;
  120. /**
  121. * Blocking or Non-blocking socket stream
  122. *
  123. * @var bool
  124. */
  125. var $streamBlocking = 1;
  126. /**
  127. * Input XMPP stream buffer
  128. */
  129. var $buffer = '';
  130. /**
  131. * Output XMPP stream buffer
  132. */
  133. var $obuffer = '';
  134. /**
  135. * Instance start time
  136. */
  137. var $startTime = false;
  138. /**
  139. * Current value of instance clock
  140. */
  141. var $clock = false;
  142. /**
  143. * When was instance clock last sync'd?
  144. */
  145. var $clocked = false;
  146. /**
  147. * Enable/Disable rate limiting
  148. */
  149. var $rateLimit = true;
  150. /**
  151. * Timestamp of last outbound XMPP packet
  152. */
  153. var $lastSendTime = false;
  154. /**
  155. * Maximum rate at which XMPP stanza's can flow out
  156. */
  157. var $sendRate = false;
  158. /**
  159. * Size of each packet to be read from the socket
  160. */
  161. var $getPktSize = false;
  162. /**
  163. * Read select timeouts
  164. */
  165. var $getSelectSecs = 1;
  166. var $getSelectUsecs = 0;
  167. /**
  168. * Packet count and sizes
  169. */
  170. var $totalRcvdPkt = 0;
  171. var $totalRcvdSize = 0;
  172. var $totalSentPkt = 0;
  173. var $totalSentSize = 0;
  174. /**
  175. * Whether Jaxl core should return a SimpleXMLElement object of parsed string with callback payloads?
  176. */
  177. var $getSXE = false;
  178. /**
  179. * XMPP constructor
  180. */
  181. function __construct($config) {
  182. $this->clock = 0;
  183. $this->clocked = $this->startTime = time();
  184. /* Parse configuration parameter */
  185. $this->lastid = rand(1, 9);
  186. $this->streamTimeout = isset($config['streamTimeout']) ? $config['streamTimeout'] : 20;
  187. $this->rateLimit = isset($config['rateLimit']) ? $config['rateLimit'] : true;
  188. $this->getPktSize = isset($config['getPktSize']) ? $config['getPktSize'] : 4096;
  189. $this->sendRate = isset($config['sendRate']) ? $config['sendRate'] : .4;
  190. $this->getSelectSecs = isset($config['getSelectSecs']) ? $config['getSelectSecs'] : 1;
  191. $this->getSXE = isset($config['getSXE']) ? $config['getSXE'] : false;
  192. }
  193. /**
  194. * Open socket stream to jabber server host:port for connecting Jaxl instance
  195. */
  196. function connect() {
  197. if(!$this->stream) {
  198. if($this->stream = @stream_socket_client("tcp://{$this->host}:{$this->port}", $this->streamENum, $this->streamEStr, $this->streamTimeout)) {
  199. $this->log("[[XMPP]] \nSocket opened to the jabber host ".$this->host.":".$this->port." ...");
  200. stream_set_blocking($this->stream, $this->streamBlocking);
  201. stream_set_timeout($this->stream, $this->streamTimeout);
  202. }
  203. else {
  204. $this->log("[[XMPP]] \nUnable to open socket to the jabber host ".$this->host.":".$this->port." ...");
  205. throw new JAXLException("[[XMPP]] Unable to open socket to the jabber host");
  206. }
  207. }
  208. else {
  209. $this->log("[[XMPP]] \nSocket already opened to the jabber host ".$this->host.":".$this->port." ...");
  210. }
  211. $ret = $this->stream ? true : false;
  212. $this->executePlugin('jaxl_post_connect', $ret);
  213. return $ret;
  214. }
  215. /**
  216. * Send XMPP start stream
  217. */
  218. function startStream() {
  219. return XMPPSend::startStream($this);
  220. }
  221. /**
  222. * Send XMPP end stream
  223. */
  224. function endStream() {
  225. return XMPPSend::endStream($this);
  226. }
  227. /**
  228. * Send session start XMPP stanza
  229. */
  230. function startSession() {
  231. return XMPPSend::startSession($this, array('XMPPGet', 'postSession'));
  232. }
  233. /**
  234. * Bind connected Jaxl instance to a resource
  235. */
  236. function startBind() {
  237. return XMPPSend::startBind($this, array('XMPPGet', 'postBind'));
  238. }
  239. /**
  240. * Return back id for use with XMPP stanza's
  241. *
  242. * @return integer $id
  243. */
  244. function getId() {
  245. $id = $this->executePlugin('jaxl_get_id', ++$this->lastid);
  246. if($id === $this->lastid) return dechex($this->uid + $this->lastid);
  247. else return $id;
  248. }
  249. /**
  250. * Read connected XMPP stream for new data
  251. * $option = null (read until data is available)
  252. * $option = integer (read for so many seconds)
  253. */
  254. function getXML() {
  255. // prepare select streams
  256. $streams = array(); $jaxls = $this->instances['xmpp'];
  257. foreach($jaxls as $cnt=>$jaxl) {
  258. if($jaxl->stream) $streams[$cnt] = $jaxl->stream;
  259. else unset($jaxls[$cnt]);
  260. }
  261. // get num changed streams
  262. $read = $streams; $write = null; $except = null; $secs = $this->getSelectSecs; $usecs = $this->getSelectUsecs;
  263. if(false === ($changed = @stream_select(&$read, &$write, &$except, $secs, $usecs))) {
  264. $this->log("[[XMPPGet]] \nError while reading packet from stream", 1);
  265. }
  266. else {
  267. if($changed == 0) $now = $this->clocked+$secs;
  268. else $now = time();
  269. foreach($read as $k=>$r) {
  270. // get jaxl instance we are dealing with
  271. $ret = $payload = '';
  272. $key = array_search($r, $streams);
  273. $jaxl = $jaxls[$key];
  274. unset($jaxls[$key]);
  275. // update clock
  276. if($now > $jaxl->clocked) {
  277. $jaxl->clock += $now-$jaxl->clocked;
  278. $jaxl->clocked = $now;
  279. }
  280. // reload broken buffer
  281. $payload = $jaxl->buffer;
  282. $jaxl->buffer = '';
  283. // read stream
  284. $ret = @fread($jaxl->stream, $jaxl->getPktSize);
  285. $bytes = strlen($ret);
  286. $jaxl->totalRcvdSize += $bytes;
  287. if(trim($ret) != '') $jaxl->log("[[XMPPGet]] ".$jaxl->getPktSize."\n".$ret, 4);
  288. $ret = $jaxl->executePlugin('jaxl_get_xml', $ret);
  289. $payload .= $ret;
  290. // route packets
  291. $jaxl->handler($payload);
  292. // clear buffer
  293. if($jaxl->obuffer != '') $jaxl->sendXML();
  294. }
  295. foreach($jaxls as $jaxl) {
  296. // update clock
  297. if($now > $jaxl->clocked) {
  298. $jaxl->clock += $now-$jaxl->clocked;
  299. $jaxl->clocked = $now;
  300. $payload = $jaxl->executePlugin('jaxl_get_xml', '');
  301. }
  302. // clear buffer
  303. if($jaxl->obuffer != '') $jaxl->sendXML();
  304. }
  305. }
  306. unset($jaxls, $streams);
  307. return $changed;
  308. }
  309. /**
  310. * Send XMPP XML packet over connected stream
  311. */
  312. function sendXML($xml='', $force=false) {
  313. $xml = $this->executePlugin('jaxl_send_xml', $xml);
  314. if($this->mode == "cgi") {
  315. $this->executePlugin('jaxl_send_body', $xml);
  316. }
  317. else {
  318. $currSendRate = ($this->totalSentSize/(JAXLUtil::getTime()-$this->lastSendTime))/1000000;
  319. $this->obuffer .= $xml;
  320. if($this->rateLimit
  321. && !$force
  322. && $this->lastSendTime
  323. && ($currSendRate > $this->sendRate)
  324. ) {
  325. $this->log("[[XMPPSend]] rateLimit\nBufferSize:".strlen($this->obuffer).", maxSendRate:".$this->sendRate.", currSendRate:".$currSendRate, 5);
  326. return 0;
  327. }
  328. $xml = $this->obuffer;
  329. $this->obuffer = '';
  330. $ret = ($xml == '') ? 0 : $this->_sendXML($xml);
  331. return $ret;
  332. }
  333. }
  334. /**
  335. * Send XMPP XML packet over connected stream
  336. */
  337. protected function _sendXML($xml) {
  338. if($this->stream && $xml != '') {
  339. $read = array(); $write = array($this->stream); $except = array();
  340. $secs = 0; $usecs = 200000;
  341. if(false === ($changed = @stream_select(&$read, &$write, &$except, $secs, $usecs))) {
  342. $this->log("[[XMPPSend]] \nError while trying to send packet", 5);
  343. throw new JAXLException("[[XMPPSend]] \nError while trying to send packet");
  344. return 0;
  345. }
  346. else if($changed > 0) {
  347. $this->lastSendTime = JAXLUtil::getTime();
  348. // this can be resource consuming for large payloads rcvd
  349. $xmls = JAXLUtil::splitXML($xml);
  350. $pktCnt = count($xmls);
  351. $this->totalSentPkt += $pktCnt;
  352. $ret = @fwrite($this->stream, $xml);
  353. $this->totalSentSize += $ret;
  354. $this->log("[[XMPPSend]] $ret\n".$xml, 4);
  355. return $ret;
  356. }
  357. else if($changed === 0) {
  358. $this->obuffer .= $xml;
  359. $this->log("[[XMPPSend]] Not ready for write, obuffer size:".strlen($this->obuffer), 1);
  360. return 0;
  361. }
  362. else {
  363. $this->obuffer .= $xml;
  364. $this->log("[[XMPPSend]] Something horibly wrong here", 1);
  365. return 0;
  366. }
  367. }
  368. else if($xml == '') {
  369. $this->log("[[XMPPSend]] Tried to send an empty stanza, not processing", 1);
  370. return 0;
  371. }
  372. else {
  373. $this->log("[[XMPPSend]] \nJaxl stream not connected to jabber host, unable to send xmpp payload...");
  374. throw new JAXLException("[[XMPPSend]] Jaxl stream not connected to jabber host, unable to send xmpp payload...");
  375. return 0;
  376. }
  377. }
  378. /**
  379. * Routes incoming XMPP data to appropriate handlers
  380. */
  381. function handler($payload) {
  382. if($payload == '' && $this->mode == 'cli') return '';
  383. if($payload != '' && $this->mode == 'cgi') $this->log("[[XMPPGet]] \n".$payload, 4);
  384. $payload = $this->executePlugin('jaxl_pre_handler', $payload);
  385. $xmls = JAXLUtil::splitXML($payload);
  386. $pktCnt = count($xmls);
  387. $this->totalRcvdPkt += $pktCnt;
  388. $buffer = array();
  389. foreach($xmls as $pktNo => $xml) {
  390. if($pktNo == $pktCnt-1) {
  391. if(substr($xml, -1, 1) != '>') {
  392. $this->buffer .= $xml;
  393. break;
  394. }
  395. }
  396. if(substr($xml, 0, 7) == '<stream') $arr = $this->xml->xmlize($xml);
  397. else $arr = JAXLXml::parse($xml, $this->getSXE);
  398. if($arr === false) { $this->buffer .= $xml; continue; }
  399. switch(true) {
  400. case isset($arr['stream:stream']):
  401. XMPPGet::streamStream($arr['stream:stream'], $this);
  402. break;
  403. case isset($arr['stream:features']):
  404. XMPPGet::streamFeatures($arr['stream:features'], $this);
  405. break;
  406. case isset($arr['stream:error']):
  407. XMPPGet::streamError($arr['stream:error'], $this);
  408. break;
  409. case isset($arr['failure']);
  410. XMPPGet::failure($arr['failure'], $this);
  411. break;
  412. case isset($arr['proceed']):
  413. XMPPGet::proceed($arr['proceed'], $this);
  414. break;
  415. case isset($arr['challenge']):
  416. XMPPGet::challenge($arr['challenge'], $this);
  417. break;
  418. case isset($arr['success']):
  419. XMPPGet::success($arr['success'], $this);
  420. break;
  421. case isset($arr['presence']):
  422. $buffer['presence'][] = $arr['presence'];
  423. break;
  424. case isset($arr['message']):
  425. $buffer['message'][] = $arr['message'];
  426. break;
  427. case isset($arr['iq']):
  428. XMPPGet::iq($arr['iq'], $this);
  429. break;
  430. default:
  431. $jaxl->log("[[XMPPGet]] \nUnrecognized payload received from jabber server...");
  432. throw new JAXLException("[[XMPPGet]] Unrecognized payload received from jabber server...");
  433. break;
  434. }
  435. }
  436. if(isset($buffer['presence'])) XMPPGet::presence($buffer['presence'], $this);
  437. if(isset($buffer['message'])) XMPPGet::message($buffer['message'], $this);
  438. unset($buffer);
  439. $this->executePlugin('jaxl_post_handler', $payload);
  440. return $payload;
  441. }
  442. }
  443. ?>