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

<?php
/**
* Jaxl (Jabber XMPP Library)
*
* Copyright (c) 2009-2010, Abhinav Singh <me@abhinavsingh.com>.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Abhinav Singh nor the names of his
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRIC
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
* @package jaxl
* @subpackage xmpp
* @author Abhinav Singh <me@abhinavsingh.com>
* @copyright Abhinav Singh
* @link http://code.google.com/p/jaxl
*/
// include required classes
jaxl_require(array(
'JAXLPlugin',
'JAXLog',
'XMPPGet',
'XMPPSend'
));
/**
* Base XMPP class
*/
class XMPP {
/**
* Auth status of Jaxl instance
*
* @var bool
*/
var $auth = false;
/**
* Connected XMPP stream session requirement status
*
* @var bool
*/
var $sessionRequired = false;
/**
* SASL second auth challenge status
*
* @var bool
*/
var $secondChallenge = false;
/**
* Id of connected Jaxl instance
*
* @var integer
*/
var $lastid = 0;
/**
* Connected socket stream handler
*
* @var bool|object
*/
var $stream = false;
/**
* Connected socket stream id
*
* @var bool|integer
*/
var $streamId = false;
/**
* Socket stream connected host
*
* @var bool|string
*/
var $streamHost = false;
/**
* Socket stream version
*
* @var bool|integer
*/
var $streamVersion = false;
/**
* Last error number for connected socket stream
*
* @var bool|integer
*/
var $streamENum = false;
/**
* Last error string for connected socket stream
*
* @var bool|string
*/
var $streamEStr = false;
/**
* Timeout value for connecting socket stream
*
* @var bool|integer
*/
var $streamTimeout = false;
/**
* Blocking or Non-blocking socket stream
*
* @var bool
*/
var $streamBlocking = 1;
/**
* Input XMPP stream buffer
*/
var $buffer = '';
/**
* Output XMPP stream buffer
*/
var $obuffer = '';
/**
* Instance start time
*/
var $startTime = false;
/**
* Current value of instance clock
*/
var $clock = false;
/**
* When was instance clock last sync'd?
*/
var $clocked = false;
/**
* Enable/Disable rate limiting
*/
var $rateLimit = true;
/**
* Timestamp of last outbound XMPP packet
*/
var $lastSendTime = false;
/**
* Maximum rate at which XMPP stanza's can flow out
*/
var $sendRate = false;
/**
* Size of each packet to be read from the socket
*/
var $getPktSize = false;
/**
* Read select timeouts
*/
var $getSelectSecs = 1;
var $getSelectUsecs = 0;
/**
* Packet count and sizes
*/
var $totalRcvdPkt = 0;
var $totalRcvdSize = 0;
var $totalSentPkt = 0;
var $totalSentSize = 0;
/**
* Whether Jaxl core should return a SimpleXMLElement object of parsed string with callback payloads?
*/
var $getSXE = false;
/**
* XMPP constructor
*/
function __construct($config) {
$this->clock = 0;
$this->clocked = $this->startTime = time();
/* Parse configuration parameter */
$this->lastid = rand(1, 9);
$this->streamTimeout = isset($config['streamTimeout']) ? $config['streamTimeout'] : 20;
$this->rateLimit = isset($config['rateLimit']) ? $config['rateLimit'] : true;
$this->getPktSize = isset($config['getPktSize']) ? $config['getPktSize'] : 4096;
$this->sendRate = isset($config['sendRate']) ? $config['sendRate'] : .4;
$this->getSelectSecs = isset($config['getSelectSecs']) ? $config['getSelectSecs'] : 1;
$this->getSXE = isset($config['getSXE']) ? $config['getSXE'] : false;
}
/**
* Open socket stream to jabber server host:port for connecting Jaxl instance
*/
function connect() {
if(!$this->stream) {
if($this->stream = @stream_socket_client("tcp://{$this->host}:{$this->port}", $this->streamENum, $this->streamEStr, $this->streamTimeout)) {
$this->log("[[XMPP]] \nSocket opened to the jabber host ".$this->host.":".$this->port." ...");
stream_set_blocking($this->stream, $this->streamBlocking);
stream_set_timeout($this->stream, $this->streamTimeout);
}
else {
$this->log("[[XMPP]] \nUnable to open socket to the jabber host ".$this->host.":".$this->port." ...");
throw new JAXLException("[[XMPP]] Unable to open socket to the jabber host");
}
}
else {
$this->log("[[XMPP]] \nSocket already opened to the jabber host ".$this->host.":".$this->port." ...");
}
$ret = $this->stream ? true : false;
$this->executePlugin('jaxl_post_connect', $ret);
return $ret;
}
/**
* Send XMPP start stream
*/
function startStream() {
return XMPPSend::startStream($this);
}
/**
* Send XMPP end stream
*/
function endStream() {
return XMPPSend::endStream($this);
}
/**
* Send session start XMPP stanza
*/
function startSession() {
return XMPPSend::startSession($this, array('XMPPGet', 'postSession'));
}
/**
* Bind connected Jaxl instance to a resource
*/
function startBind() {
return XMPPSend::startBind($this, array('XMPPGet', 'postBind'));
}
/**
* Return back id for use with XMPP stanza's
*
* @return integer $id
*/
function getId() {
$id = $this->executePlugin('jaxl_get_id', ++$this->lastid);
if($id === $this->lastid) return dechex($this->uid + $this->lastid);
else return $id;
}
/**
* Read connected XMPP stream for new data
* $option = null (read until data is available)
* $option = integer (read for so many seconds)
*/
function getXML() {
// prepare select streams
$streams = array(); $jaxls = $this->instances['xmpp'];
foreach($jaxls as $cnt=>$jaxl) {
if($jaxl->stream) $streams[$cnt] = $jaxl->stream;
else unset($jaxls[$cnt]);
}
// get num changed streams
$read = $streams; $write = null; $except = null; $secs = $this->getSelectSecs; $usecs = $this->getSelectUsecs;
if(false === ($changed = @stream_select(&$read, &$write, &$except, $secs, $usecs))) {
$this->log("[[XMPPGet]] \nError while reading packet from stream", 1);
}
else {
if($changed == 0) $now = $this->clocked+$secs;
else $now = time();
foreach($read as $k=>$r) {
// get jaxl instance we are dealing with
$ret = $payload = '';
$key = array_search($r, $streams);
$jaxl = $jaxls[$key];
unset($jaxls[$key]);
// update clock
if($now > $jaxl->clocked) {
$jaxl->clock += $now-$jaxl->clocked;
$jaxl->clocked = $now;
}
// reload broken buffer
$payload = $jaxl->buffer;
$jaxl->buffer = '';
// read stream
$ret = @fread($jaxl->stream, $jaxl->getPktSize);
$bytes = strlen($ret);
$jaxl->totalRcvdSize += $bytes;
if(trim($ret) != '') $jaxl->log("[[XMPPGet]] ".$jaxl->getPktSize."\n".$ret, 4);
$ret = $jaxl->executePlugin('jaxl_get_xml', $ret);
$payload .= $ret;
// route packets
$jaxl->handler($payload);
// clear buffer
if($jaxl->obuffer != '') $jaxl->sendXML();
}
foreach($jaxls as $jaxl) {
// update clock
if($now > $jaxl->clocked) {
$jaxl->clock += $now-$jaxl->clocked;
$jaxl->clocked = $now;
$payload = $jaxl->executePlugin('jaxl_get_xml', '');
}
// clear buffer
if($jaxl->obuffer != '') $jaxl->sendXML();
}
}
unset($jaxls, $streams);
return $changed;
}
/**
* Send XMPP XML packet over connected stream
*/
function sendXML($xml='', $force=false) {
$xml = $this->executePlugin('jaxl_send_xml', $xml);
if($this->mode == "cgi") {
$this->executePlugin('jaxl_send_body', $xml);
}
else {
$currSendRate = ($this->totalSentSize/(JAXLUtil::getTime()-$this->lastSendTime))/1000000;
$this->obuffer .= $xml;
if($this->rateLimit
&& !$force
&& $this->lastSendTime
&& ($currSendRate > $this->sendRate)
) {
$this->log("[[XMPPSend]] rateLimit\nBufferSize:".strlen($this->obuffer).", maxSendRate:".$this->sendRate.", currSendRate:".$currSendRate, 5);
return 0;
}
$xml = $this->obuffer;
$this->obuffer = '';
$ret = ($xml == '') ? 0 : $this->_sendXML($xml);
return $ret;
}
}
/**
* Send XMPP XML packet over connected stream
*/
protected function _sendXML($xml) {
if($this->stream && $xml != '') {
$read = array(); $write = array($this->stream); $except = array();
$secs = 0; $usecs = 200000;
if(false === ($changed = @stream_select(&$read, &$write, &$except, $secs, $usecs))) {
$this->log("[[XMPPSend]] \nError while trying to send packet", 5);
throw new JAXLException("[[XMPPSend]] \nError while trying to send packet");
return 0;
}
else if($changed > 0) {
$this->lastSendTime = JAXLUtil::getTime();
// this can be resource consuming for large payloads rcvd
$xmls = JAXLUtil::splitXML($xml);
$pktCnt = count($xmls);
$this->totalSentPkt += $pktCnt;
$ret = @fwrite($this->stream, $xml);
$this->totalSentSize += $ret;
$this->log("[[XMPPSend]] $ret\n".$xml, 4);
return $ret;
}
else if($changed === 0) {
$this->obuffer .= $xml;
$this->log("[[XMPPSend]] Not ready for write, obuffer size:".strlen($this->obuffer), 1);
return 0;
}
else {
$this->obuffer .= $xml;
$this->log("[[XMPPSend]] Something horibly wrong here", 1);
return 0;
}
}
else if($xml == '') {
$this->log("[[XMPPSend]] Tried to send an empty stanza, not processing", 1);
return 0;
}
else {
$this->log("[[XMPPSend]] \nJaxl stream not connected to jabber host, unable to send xmpp payload...");
throw new JAXLException("[[XMPPSend]] Jaxl stream not connected to jabber host, unable to send xmpp payload...");
return 0;
}
}
/**
* Routes incoming XMPP data to appropriate handlers
*/
function handler($payload) {
if($payload == '' && $this->mode == 'cli') return '';
if($payload != '' && $this->mode == 'cgi') $this->log("[[XMPPGet]] \n".$payload, 4);
$payload = $this->executePlugin('jaxl_pre_handler', $payload);
$xmls = JAXLUtil::splitXML($payload);
$pktCnt = count($xmls);
$this->totalRcvdPkt += $pktCnt;
$buffer = array();
foreach($xmls as $pktNo => $xml) {
if($pktNo == $pktCnt-1) {
if(substr($xml, -1, 1) != '>') {
$this->buffer .= $xml;
break;
}
}
if(substr($xml, 0, 7) == '<stream') $arr = $this->xml->xmlize($xml);
else $arr = JAXLXml::parse($xml, $this->getSXE);
if($arr === false) { $this->buffer .= $xml; continue; }
switch(true) {
case isset($arr['stream:stream']):
XMPPGet::streamStream($arr['stream:stream'], $this);
break;
case isset($arr['stream:features']):
XMPPGet::streamFeatures($arr['stream:features'], $this);
break;
case isset($arr['stream:error']):
XMPPGet::streamError($arr['stream:error'], $this);
break;
case isset($arr['failure']);
XMPPGet::failure($arr['failure'], $this);
break;
case isset($arr['proceed']):
XMPPGet::proceed($arr['proceed'], $this);
break;
case isset($arr['challenge']):
XMPPGet::challenge($arr['challenge'], $this);
break;
case isset($arr['success']):
XMPPGet::success($arr['success'], $this);
break;
case isset($arr['presence']):
$buffer['presence'][] = $arr['presence'];
break;
case isset($arr['message']):
$buffer['message'][] = $arr['message'];
break;
case isset($arr['iq']):
XMPPGet::iq($arr['iq'], $this);
break;
default:
$jaxl->log("[[XMPPGet]] \nUnrecognized payload received from jabber server...");
throw new JAXLException("[[XMPPGet]] Unrecognized payload received from jabber server...");
break;
}
}
if(isset($buffer['presence'])) XMPPGet::presence($buffer['presence'], $this);
if(isset($buffer['message'])) XMPPGet::message($buffer['message'], $this);
unset($buffer);
$this->executePlugin('jaxl_post_handler', $payload);
return $payload;
}
}
?>