683 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			683 lines
		
	
	
		
			19 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
/**
 | 
						|
 * XMPP Prebind for PHP
 | 
						|
 *
 | 
						|
 * @copyright 2011 Amiado Group AG
 | 
						|
 * @author Michael Weibel <michael.weibel@amiadogroup.com>
 | 
						|
 */
 | 
						|
 | 
						|
/**
 | 
						|
 * FirePHP for debugging
 | 
						|
 */
 | 
						|
include 'FirePHP/fb.php';
 | 
						|
 | 
						|
/**
 | 
						|
 * PEAR Auth_SASL
 | 
						|
 */
 | 
						|
require 'Auth/SASL.php';
 | 
						|
 | 
						|
/**
 | 
						|
 * XMPP Library for connecting to jabber server & receiving sid and rid
 | 
						|
 */
 | 
						|
class XmppPrebind {
 | 
						|
 | 
						|
	const XMLNS_BODY    = 'http://jabber.org/protocol/httpbind';
 | 
						|
	const XMLNS_BOSH    = 'urn:xmpp:xbosh';
 | 
						|
	const XMLNS_CLIENT  = 'jabber:client';
 | 
						|
	const XMLNS_SESSION = 'urn:ietf:params:xml:ns:xmpp-session';
 | 
						|
	const XMLNS_BIND    = 'urn:ietf:params:xml:ns:xmpp-bind';
 | 
						|
	const XMLNS_SASL    = 'urn:ietf:params:xml:ns:xmpp-sasl';
 | 
						|
	const XMLNS_VCARD   = 'vcard-temp';
 | 
						|
 | 
						|
	const XML_LANG      = 'en';
 | 
						|
	const CONTENT_TYPE  = 'text/xml charset=utf-8';
 | 
						|
 | 
						|
	const ENCRYPTION_PLAIN      = 'PLAIN';
 | 
						|
	const ENCRYPTION_DIGEST_MD5 = 'DIGEST-MD5';
 | 
						|
	const ENCRYPTION_CRAM_MD5 = 'CRAM-MD5';
 | 
						|
 | 
						|
	const SERVICE_NAME = 'xmpp';
 | 
						|
 | 
						|
	protected $jabberHost = '';
 | 
						|
	protected $boshUri    = '';
 | 
						|
	protected $resource   = '';
 | 
						|
 | 
						|
	protected $debug = false;
 | 
						|
	/**
 | 
						|
	 * FirePHP Instance
 | 
						|
	 *
 | 
						|
	 * @var FirePHP
 | 
						|
	 */
 | 
						|
	protected $firePhp = null;
 | 
						|
 | 
						|
	protected $useGzip = false;
 | 
						|
	protected $useSsl = false;
 | 
						|
	protected $encryption = self::ENCRYPTION_PLAIN;
 | 
						|
 | 
						|
	protected $jid = '';
 | 
						|
	protected $password = '';
 | 
						|
 | 
						|
	protected $rid = '';
 | 
						|
	protected $sid = '';
 | 
						|
 | 
						|
	protected $doSession = false;
 | 
						|
	protected $doBind    = false;
 | 
						|
 | 
						|
	protected $mechanisms = array();
 | 
						|
 | 
						|
	// the Bosh attributes for use in a client using this prebound session
 | 
						|
	protected $wait;
 | 
						|
	protected $requests;
 | 
						|
	protected $ver;
 | 
						|
	protected $polling;
 | 
						|
	protected $inactivity;
 | 
						|
	protected $hold;
 | 
						|
	protected $to;
 | 
						|
	protected $ack;
 | 
						|
	protected $accept;
 | 
						|
	protected $maxpause;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Session creation response
 | 
						|
	 *
 | 
						|
	 * @var DOMDocument
 | 
						|
	 */
 | 
						|
	public $response;
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Create a new XmppPrebind Object with the required params
 | 
						|
	 *
 | 
						|
	 * @param string $jabberHost Jabber Server Host
 | 
						|
	 * @param string $boshUri    Full URI to the http-bind
 | 
						|
	 * @param string $resource   Resource identifier
 | 
						|
	 * @param bool   $useSsl     Use SSL (not working yet, TODO)
 | 
						|
	 * @param bool   $debug      Enable debug
 | 
						|
	 */
 | 
						|
	public function __construct($jabberHost, $boshUri, $resource, $useSsl = false, $debug = false) {
 | 
						|
		$this->jabberHost = $jabberHost;
 | 
						|
		$this->boshUri    = $boshUri;
 | 
						|
		$this->resource   = $resource;
 | 
						|
 | 
						|
		$this->useSsl = $useSsl;
 | 
						|
 | 
						|
		$this->debug = $debug;
 | 
						|
		if ($this->debug === true) {
 | 
						|
			$this->firePhp = FirePHP::getInstance(true);
 | 
						|
			$this->firePhp->setEnabled(true);
 | 
						|
		}
 | 
						|
 | 
						|
		/* TODO: Not working
 | 
						|
		 if (function_exists('gzinflate')) {
 | 
						|
			$this->useGzip = true;
 | 
						|
		}*/
 | 
						|
 | 
						|
		/*
 | 
						|
		 * The client MUST generate a large, random, positive integer for the initial 'rid' (see Security Considerations)
 | 
						|
		 * and then increment that value by one for each subsequent request. The client MUST take care to choose an
 | 
						|
		 * initial 'rid' that will never be incremented above 9007199254740991 [21] within the session.
 | 
						|
		 * In practice, a session would have to be extraordinarily long (or involve the exchange of an extraordinary
 | 
						|
		 * number of packets) to exceed the defined limit.
 | 
						|
		 *
 | 
						|
		 * @link http://xmpp.org/extensions/xep-0124.html#rids
 | 
						|
		 */
 | 
						|
		if (function_exists('mt_rand')) {
 | 
						|
			$this->rid = mt_rand(1000000000, 10000000000);
 | 
						|
		} else {
 | 
						|
			$this->rid = rand(1000000000, 10000000000);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * connect to the jabber server with the supplied username & password
 | 
						|
	 *
 | 
						|
	 * @param string $username Username without jabber host
 | 
						|
	 * @param string $password Password
 | 
						|
	 * @param string $route Route
 | 
						|
	 */
 | 
						|
	public function connect($username, $password, $route = false) {
 | 
						|
		$this->jid      = $username . '@' . $this->jabberHost;
 | 
						|
 | 
						|
		if($this->resource) {
 | 
						|
			$this->jid .= '/' . $this->resource;
 | 
						|
		}
 | 
						|
 | 
						|
		$this->password = $password;
 | 
						|
 | 
						|
		$response = $this->sendInitialConnection($route);
 | 
						|
        if(empty($response)) {
 | 
						|
			throw new XmppPrebindConnectionException("No response from server.");
 | 
						|
        }
 | 
						|
 | 
						|
		$body = self::getBodyFromXml($response);
 | 
						|
        if ( empty( $body ) )
 | 
						|
			throw new XmppPrebindConnectionException("No body could be found in response from server.");
 | 
						|
		$this->sid = $body->getAttribute('sid');
 | 
						|
 | 
						|
		// set the Bosh Attributes
 | 
						|
		$this->wait = $body->getAttribute('wait');
 | 
						|
		$this->requests = $body->getAttribute('requests');
 | 
						|
		$this->ver = $body->getAttribute('ver');
 | 
						|
		$this->polling = $body->getAttribute('polling');
 | 
						|
		$this->inactivity = $body->getAttribute('inactivity');
 | 
						|
		$this->hold = $body->getAttribute('hold');
 | 
						|
		$this->to = $body->getAttribute('to');
 | 
						|
		$this->accept = $body->getAttribute('accept');
 | 
						|
		$this->maxpause = $body->getAttribute('maxpause');
 | 
						|
 | 
						|
		$this->debug($this->sid, 'sid');
 | 
						|
 | 
						|
        if(empty($body->firstChild) || empty($body->firstChild->firstChild)) {
 | 
						|
			throw new XmppPrebindConnectionException("Child not found in response from server.");
 | 
						|
        }
 | 
						|
		$mechanisms = $body->getElementsByTagName('mechanism');
 | 
						|
 | 
						|
		foreach ($mechanisms as $value) {
 | 
						|
			$this->mechanisms[] = $value->nodeValue;
 | 
						|
		}
 | 
						|
 | 
						|
		if (in_array(self::ENCRYPTION_DIGEST_MD5, $this->mechanisms)) {
 | 
						|
			$this->encryption = self::ENCRYPTION_DIGEST_MD5;
 | 
						|
		} elseif (in_array(self::ENCRYPTION_CRAM_MD5, $this->mechanisms)) {
 | 
						|
			$this->encryption = self::ENCRYPTION_CRAM_MD5;
 | 
						|
		} elseif (in_array(self::ENCRYPTION_PLAIN, $this->mechanisms)) {
 | 
						|
			$this->encryption = self::ENCRYPTION_PLAIN;
 | 
						|
		} else {
 | 
						|
			throw new XmppPrebindConnectionException("No encryption supported by the server is supported by this library.");
 | 
						|
		}
 | 
						|
 | 
						|
		$this->debug($this->encryption, 'encryption used');
 | 
						|
 | 
						|
		// Assign session creation response
 | 
						|
		$this->response = $body;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Try to authenticate
 | 
						|
	 *
 | 
						|
	 * @throws XmppPrebindException if invalid login
 | 
						|
	 * @return bool
 | 
						|
	 */
 | 
						|
	public function auth() {
 | 
						|
		$auth = Auth_SASL::factory($this->encryption);
 | 
						|
 | 
						|
		switch ($this->encryption) {
 | 
						|
			case self::ENCRYPTION_PLAIN:
 | 
						|
				$authXml = $this->buildPlainAuth($auth);
 | 
						|
				break;
 | 
						|
			case self::ENCRYPTION_DIGEST_MD5:
 | 
						|
				$authXml = $this->sendChallengeAndBuildDigestMd5Auth($auth);
 | 
						|
				break;
 | 
						|
			case self::ENCRYPTION_CRAM_MD5:
 | 
						|
				$authXml = $this->sendChallengeAndBuildCramMd5Auth($auth);
 | 
						|
				break;
 | 
						|
		}
 | 
						|
		$response = $this->send($authXml);
 | 
						|
 | 
						|
		$body = self::getBodyFromXml($response);
 | 
						|
 | 
						|
		if (!$body->hasChildNodes() || $body->firstChild->nodeName !== 'success') {
 | 
						|
			throw new XmppPrebindException("Invalid login");
 | 
						|
		}
 | 
						|
 | 
						|
		$this->sendRestart();
 | 
						|
		$this->sendBindIfRequired();
 | 
						|
		$this->sendSessionIfRequired();
 | 
						|
 | 
						|
		return true;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get BOSH parameters to properly setup the BOSH client
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function getBoshInfo()
 | 
						|
	{
 | 
						|
		return array(
 | 
						|
			'wait' => $this->wait,
 | 
						|
			'requests' => $this->requests,
 | 
						|
			'ver' => $this->ver,
 | 
						|
			'polling' => $this->polling,
 | 
						|
			'inactivity' => $this->inactivity,
 | 
						|
			'hold' => $this->hold,
 | 
						|
			'to' => $this->to,
 | 
						|
			'ack' => $this->ack,
 | 
						|
			'accept' => $this->accept,
 | 
						|
			'maxpause' => $this->maxpause,
 | 
						|
		);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get jid, sid and rid for attaching
 | 
						|
	 *
 | 
						|
	 * @return array
 | 
						|
	 */
 | 
						|
	public function getSessionInfo() {
 | 
						|
		return array('jid' => $this->jid, 'sid' => $this->sid, 'rid' => $this->rid);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Debug if debug enabled
 | 
						|
	 *
 | 
						|
	 * @param string $msg
 | 
						|
	 * @param string $label
 | 
						|
	 */
 | 
						|
	protected function debug($msg, $label = null) {
 | 
						|
		if ($this->firePhp) {
 | 
						|
			$this->firePhp->log($msg, $label);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Send xmpp restart message after successful auth
 | 
						|
	 *
 | 
						|
	 * @return string Response
 | 
						|
	 */
 | 
						|
	protected function sendRestart() {
 | 
						|
		$domDocument = $this->buildBody();
 | 
						|
		$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
		$body->appendChild(self::getNewTextAttribute($domDocument, 'to', $this->jabberHost));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($domDocument, 'xmlns:xmpp', self::XMLNS_BOSH));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($domDocument, 'xmpp:restart', 'true'));
 | 
						|
 | 
						|
		$restartResponse = $this->send($domDocument->saveXML());
 | 
						|
 | 
						|
		$restartBody = self::getBodyFromXml($restartResponse);
 | 
						|
		foreach ($restartBody->childNodes as $bodyChildNodes) {
 | 
						|
			if ($bodyChildNodes->nodeName === 'stream:features') {
 | 
						|
				foreach ($bodyChildNodes->childNodes as $streamFeatures) {
 | 
						|
					if ($streamFeatures->nodeName === 'bind') {
 | 
						|
						$this->doBind = true;
 | 
						|
					} elseif ($streamFeatures->nodeName === 'session') {
 | 
						|
						$this->doSession = true;
 | 
						|
					}
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
 | 
						|
		return $restartResponse;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Send xmpp bind message after restart
 | 
						|
	 *
 | 
						|
	 * @return string Response
 | 
						|
	 */
 | 
						|
	protected function sendBindIfRequired() {
 | 
						|
		if ($this->doBind) {
 | 
						|
			$domDocument = $this->buildBody();
 | 
						|
			$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
 | 
						|
			$iq = $domDocument->createElement('iq');
 | 
						|
			$iq->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_CLIENT));
 | 
						|
			$iq->appendChild(self::getNewTextAttribute($domDocument, 'type', 'set'));
 | 
						|
			$iq->appendChild(self::getNewTextAttribute($domDocument, 'id', 'bind_' . rand()));
 | 
						|
 | 
						|
			$bind = $domDocument->createElement('bind');
 | 
						|
			$bind->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_BIND));
 | 
						|
 | 
						|
			$resource = $domDocument->createElement('resource');
 | 
						|
			$resource->appendChild($domDocument->createTextNode($this->resource));
 | 
						|
 | 
						|
			$bind->appendChild($resource);
 | 
						|
			$iq->appendChild($bind);
 | 
						|
			$body->appendChild($iq);
 | 
						|
 | 
						|
			return $this->send($domDocument->saveXML());
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Send session if there's a session node in the restart response (within stream:features)
 | 
						|
	 */
 | 
						|
	protected function sendSessionIfRequired() {
 | 
						|
		if ($this->doSession) {
 | 
						|
			$domDocument = $this->buildBody();
 | 
						|
			$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
 | 
						|
			$iq = $domDocument->createElement('iq');
 | 
						|
			$iq->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_CLIENT));
 | 
						|
			$iq->appendChild(self::getNewTextAttribute($domDocument, 'type', 'set'));
 | 
						|
			$iq->appendChild(self::getNewTextAttribute($domDocument, 'id', 'session_auth_' . rand()));
 | 
						|
 | 
						|
			$session = $domDocument->createElement('session');
 | 
						|
			$session->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SESSION));
 | 
						|
 | 
						|
			$iq->appendChild($session);
 | 
						|
			$body->appendChild($iq);
 | 
						|
 | 
						|
			return $this->send($domDocument->saveXML());
 | 
						|
		}
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Send initial connection string
 | 
						|
	 *
 | 
						|
	 * @param string $route
 | 
						|
	 * @return string Response
 | 
						|
	 */
 | 
						|
	protected function sendInitialConnection($route = false) {
 | 
						|
		$domDocument = $this->buildBody();
 | 
						|
		$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
 | 
						|
		$waitTime = 60;
 | 
						|
 | 
						|
		$body->appendChild(self::getNewTextAttribute($domDocument, 'hold', '1'));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($domDocument, 'to', $this->jabberHost));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($domDocument, 'xmlns:xmpp', self::XMLNS_BOSH));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($domDocument, 'xmpp:version', '1.0'));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($domDocument, 'wait', $waitTime));
 | 
						|
 | 
						|
		if ($route)
 | 
						|
		{
 | 
						|
			$body->appendChild(self::getNewTextAttribute($domDocument, 'route', $route));
 | 
						|
		}
 | 
						|
 | 
						|
		return $this->send($domDocument->saveXML());
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Send challenge request
 | 
						|
	 *
 | 
						|
	 * @return string Challenge
 | 
						|
	 */
 | 
						|
	protected function sendChallenge() {
 | 
						|
		$domDocument = $this->buildBody();
 | 
						|
		$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
 | 
						|
		$auth = $domDocument->createElement('auth');
 | 
						|
		$auth->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL));
 | 
						|
		$auth->appendChild(self::getNewTextAttribute($domDocument, 'mechanism', $this->encryption));
 | 
						|
		$body->appendChild($auth);
 | 
						|
 | 
						|
		$response = $this->send($domDocument->saveXML());
 | 
						|
 | 
						|
		$body = $this->getBodyFromXml($response);
 | 
						|
		$challenge = base64_decode($body->firstChild->nodeValue);
 | 
						|
 | 
						|
		return $challenge;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Build PLAIN auth string
 | 
						|
	 *
 | 
						|
	 * @param Auth_SASL_Common $auth
 | 
						|
	 * @return string Auth XML to send
 | 
						|
	 */
 | 
						|
	protected function buildPlainAuth(Auth_SASL_Common $auth) {
 | 
						|
		$authString = $auth->getResponse(self::getNodeFromJid($this->jid), $this->password, self::getBareJidFromJid($this->jid));
 | 
						|
		$authString = base64_encode($authString);
 | 
						|
		$this->debug($authString, 'PLAIN Auth String');
 | 
						|
 | 
						|
		$domDocument = $this->buildBody();
 | 
						|
		$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
 | 
						|
		$auth = $domDocument->createElement('auth');
 | 
						|
		$auth->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL));
 | 
						|
		$auth->appendChild(self::getNewTextAttribute($domDocument, 'mechanism', $this->encryption));
 | 
						|
		$auth->appendChild($domDocument->createTextNode($authString));
 | 
						|
		$body->appendChild($auth);
 | 
						|
 | 
						|
		return $domDocument->saveXML();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Send challenge request and build DIGEST-MD5 auth string
 | 
						|
	 *
 | 
						|
	 * @param Auth_SASL_Common $auth
 | 
						|
	 * @return string Auth XML to send
 | 
						|
	 */
 | 
						|
	protected function sendChallengeAndBuildDigestMd5Auth(Auth_SASL_Common $auth) {
 | 
						|
		$challenge = $this->sendChallenge();
 | 
						|
 | 
						|
		$authString = $auth->getResponse(self::getNodeFromJid($this->jid), $this->password, $challenge, $this->jabberHost, self::SERVICE_NAME);
 | 
						|
		$this->debug($authString, 'DIGEST-MD5 Auth String');
 | 
						|
 | 
						|
		$authString = base64_encode($authString);
 | 
						|
 | 
						|
		$domDocument = $this->buildBody();
 | 
						|
		$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
 | 
						|
		$response = $domDocument->createElement('response');
 | 
						|
		$response->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL));
 | 
						|
		$response->appendChild($domDocument->createTextNode($authString));
 | 
						|
 | 
						|
		$body->appendChild($response);
 | 
						|
 | 
						|
 | 
						|
		$challengeResponse = $this->send($domDocument->saveXML());
 | 
						|
 | 
						|
		return $this->replyToChallengeResponse($challengeResponse);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Send challenge request and build CRAM-MD5 auth string
 | 
						|
	 *
 | 
						|
	 * @param Auth_SASL_Common $auth
 | 
						|
	 * @return string Auth XML to send
 | 
						|
	 */
 | 
						|
	protected function sendChallengeAndBuildCramMd5Auth(Auth_SASL_Common $auth) {
 | 
						|
		$challenge = $this->sendChallenge();
 | 
						|
 | 
						|
		$authString = $auth->getResponse(self::getNodeFromJid($this->jid), $this->password, $challenge);
 | 
						|
		$this->debug($authString, 'CRAM-MD5 Auth String');
 | 
						|
 | 
						|
		$authString = base64_encode($authString);
 | 
						|
 | 
						|
		$domDocument = $this->buildBody();
 | 
						|
		$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
 | 
						|
		$response = $domDocument->createElement('response');
 | 
						|
		$response->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL));
 | 
						|
		$response->appendChild($domDocument->createTextNode($authString));
 | 
						|
 | 
						|
		$body->appendChild($response);
 | 
						|
 | 
						|
		$challengeResponse = $this->send($domDocument->saveXML());
 | 
						|
 | 
						|
		return $this->replyToChallengeResponse($challengeResponse);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * CRAM-MD5 and DIGEST-MD5 reply with an additional challenge response which must be replied to.
 | 
						|
	 * After this additional reply, the server should reply with "success".
 | 
						|
	 */
 | 
						|
	protected function replyToChallengeResponse($challengeResponse) {
 | 
						|
		$body = self::getBodyFromXml($challengeResponse);
 | 
						|
		$challenge = base64_decode((string)$body->firstChild->nodeValue);
 | 
						|
		if (strpos($challenge, 'rspauth') === false) {
 | 
						|
			throw new XmppPrebindConnectionException('Invalid challenge response received');
 | 
						|
		}
 | 
						|
 | 
						|
		$domDocument = $this->buildBody();
 | 
						|
		$body = self::getBodyFromDomDocument($domDocument);
 | 
						|
		$response = $domDocument->createElement('response');
 | 
						|
		$response->appendChild(self::getNewTextAttribute($domDocument, 'xmlns', self::XMLNS_SASL));
 | 
						|
 | 
						|
		$body->appendChild($response);
 | 
						|
 | 
						|
		return $domDocument->saveXML();
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Send XML via CURL
 | 
						|
	 *
 | 
						|
	 * @param string $xml
 | 
						|
	 * @return string Response
 | 
						|
	 */
 | 
						|
	protected function send($xml) {
 | 
						|
		$ch = curl_init($this->boshUri);
 | 
						|
		curl_setopt($ch, CURLOPT_HEADER, 0);
 | 
						|
		curl_setopt($ch, CURLOPT_POST, 1);
 | 
						|
		curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
 | 
						|
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
 | 
						|
 | 
						|
		$header = array('Content-Type: ' . self::CONTENT_TYPE);
 | 
						|
		if ($this->useGzip) {
 | 
						|
			$header[] = 'Accept-Encoding: gzip, deflate';
 | 
						|
		}
 | 
						|
		curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
 | 
						|
 | 
						|
		curl_setopt($ch, CURLOPT_VERBOSE, 0);
 | 
						|
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
 | 
						|
 | 
						|
		$response = curl_exec($ch);
 | 
						|
 | 
						|
		// Check if curl failed to get response
 | 
						|
		if ($response === false) {
 | 
						|
			throw new XmppPrebindConnectionException("Cannot connect to service");
 | 
						|
		}
 | 
						|
 | 
						|
		curl_close($ch);
 | 
						|
 | 
						|
		if ($this->useGzip) {
 | 
						|
			$response = self::compatibleGzInflate($response);
 | 
						|
		}
 | 
						|
 | 
						|
		$this->debug($xml, 'SENT');
 | 
						|
		$this->debug($response, 'RECV:');
 | 
						|
 | 
						|
		return $response;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Fix gzdecompress/gzinflate data error warning.
 | 
						|
	 *
 | 
						|
	 * @link http://www.mydigitallife.info/2010/01/17/workaround-to-fix-php-warning-gzuncompress-or-gzinflate-data-error-in-wordpress-http-php/
 | 
						|
	 *
 | 
						|
	 * @param string $gzData
 | 
						|
	 * @return string|bool
 | 
						|
	 */
 | 
						|
	public static function compatibleGzInflate($gzData) {
 | 
						|
		if ( substr($gzData, 0, 3) == "\x1f\x8b\x08" ) {
 | 
						|
			$i = 10;
 | 
						|
			$flg = ord( substr($gzData, 3, 1) );
 | 
						|
			if ( $flg > 0 ) {
 | 
						|
				if ( $flg & 4 ) {
 | 
						|
					list($xlen) = unpack('v', substr($gzData, $i, 2) );
 | 
						|
					$i = $i + 2 + $xlen;
 | 
						|
				}
 | 
						|
				if ( $flg & 8 )
 | 
						|
					$i = strpos($gzData, "\0", $i) + 1;
 | 
						|
				if ( $flg & 16 )
 | 
						|
					$i = strpos($gzData, "\0", $i) + 1;
 | 
						|
				if ( $flg & 2 )
 | 
						|
					$i = $i + 2;
 | 
						|
			}
 | 
						|
			return gzinflate( substr($gzData, $i, -8) );
 | 
						|
		} else {
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Build DOMDocument with standard xmpp body child node.
 | 
						|
	 *
 | 
						|
	 * @return DOMDocument
 | 
						|
	 */
 | 
						|
	protected function buildBody() {
 | 
						|
		$xml = new DOMDocument('1.0', 'UTF-8');
 | 
						|
 | 
						|
		$body = $xml->createElement('body');
 | 
						|
		$xml->appendChild($body);
 | 
						|
 | 
						|
		$body->appendChild(self::getNewTextAttribute($xml, 'xmlns', self::XMLNS_BODY));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($xml, 'content', self::CONTENT_TYPE));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($xml, 'rid', $this->getAndIncrementRid()));
 | 
						|
		$body->appendChild(self::getNewTextAttribute($xml, 'xml:lang', self::XML_LANG));
 | 
						|
 | 
						|
		if ($this->sid != '') {
 | 
						|
			$body->appendChild(self::getNewTextAttribute($xml, 'sid', $this->sid));
 | 
						|
		}
 | 
						|
 | 
						|
		return $xml;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get jid in form of username@jabberHost
 | 
						|
	 *
 | 
						|
	 * @param string $jid Jid in form username@jabberHost/Resource
 | 
						|
	 * @return string JID
 | 
						|
	 */
 | 
						|
	public static function getBareJidFromJid($jid) {
 | 
						|
		if ($jid == '') {
 | 
						|
			return '';
 | 
						|
		}
 | 
						|
		$splittedJid = explode('/', $jid, 1);
 | 
						|
		return $splittedJid[0];
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get node (username) from jid
 | 
						|
	 *
 | 
						|
	 * @param string $jid
 | 
						|
	 * @return string Node
 | 
						|
	 */
 | 
						|
	public static function getNodeFromJid($jid) {
 | 
						|
		$atPos = strpos($jid, '@');
 | 
						|
		if ($atPos === false) {
 | 
						|
			return '';
 | 
						|
		}
 | 
						|
		return substr($jid, 0, $atPos);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Append new attribute to existing DOMDocument.
 | 
						|
	 *
 | 
						|
	 * @param DOMDocument $domDocument
 | 
						|
	 * @param string $attributeName
 | 
						|
	 * @param string $value
 | 
						|
	 * @return DOMNode
 | 
						|
	 */
 | 
						|
	protected static function getNewTextAttribute($domDocument, $attributeName, $value) {
 | 
						|
		$attribute = $domDocument->createAttribute($attributeName);
 | 
						|
		$attribute->appendChild($domDocument->createTextNode($value));
 | 
						|
 | 
						|
		return $attribute;
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get body node from DOMDocument
 | 
						|
	 *
 | 
						|
	 * @param DOMDocument $domDocument
 | 
						|
	 * @return DOMNode
 | 
						|
	 */
 | 
						|
	protected static function getBodyFromDomDocument($domDocument) {
 | 
						|
		$body = $domDocument->getElementsByTagName('body');
 | 
						|
		return $body->item(0);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Parse XML and return DOMNode of the body
 | 
						|
	 *
 | 
						|
	 * @uses XmppPrebind::getBodyFromDomDocument()
 | 
						|
	 * @param string $xml
 | 
						|
	 * @return DOMNode
 | 
						|
	 */
 | 
						|
	protected static function getBodyFromXml($xml) {
 | 
						|
		$domDocument = new DOMDocument();
 | 
						|
		$domDocument->loadXml($xml);
 | 
						|
 | 
						|
		return self::getBodyFromDomDocument($domDocument);
 | 
						|
	}
 | 
						|
 | 
						|
	/**
 | 
						|
	 * Get the rid and increment it by one.
 | 
						|
	 * Required by RFC
 | 
						|
	 *
 | 
						|
	 * @return int
 | 
						|
	 */
 | 
						|
	protected function getAndIncrementRid() {
 | 
						|
		return $this->rid++;
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Standard XmppPrebind Exception
 | 
						|
 */
 | 
						|
class XmppPrebindException extends Exception{}
 | 
						|
 | 
						|
class XmppPrebindConnectionException extends XmppPrebindException{}
 |