From: Diogo Cordeiro Date: Sun, 21 Apr 2019 00:23:50 +0000 (+0100) Subject: Modern version of XMPPHP extlib X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=3290227b50582ed29790f0bb10210362ca2f4093;p=quix0rs-gnu-social.git Modern version of XMPPHP extlib Original XMPPHP is no longer maintained Therefore I've done some optimizations and imported some commits from birkner and zorn-v forks. None of the forks really looked ready to be adopted... --- diff --git a/lib/implugin.php b/lib/implugin.php index 457c9dba52..74bb593fd2 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -525,7 +525,7 @@ abstract class ImPlugin extends Plugin { // If we don't require CLI mode, or if we do and GNUSOCIAL_CLI _is_ set, then connect the transports // This check is made mostly because some IM plugins can't deliver to transports unless they - // have continously running daemons (such as XMPP) and we can't have that over HTTP requests. + // have continuously running daemons (such as XMPP) and we can't have that over HTTP requests. if (!$this->requires_cli || defined('GNUSOCIAL_CLI')) { $manager->connect($this->transport . '-in', new ImReceiverQueueHandler($this), 'im'); $manager->connect($this->transport, new ImQueueHandler($this)); diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index da20504329..933e5c3982 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -5,7 +5,7 @@ * * Send and receive notices using the XMPP network * - * PHP version 5 + * PHP version 7 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -48,7 +48,7 @@ class XmppPlugin extends ImPlugin { public $server = null; public $port = 5222; - public $user = 'update'; + public $user = 'update'; public $resource = 'gnusocial'; public $encryption = true; public $password = null; @@ -57,22 +57,66 @@ class XmppPlugin extends ImPlugin public $transport = 'xmpp'; - function getDisplayName(){ + function getDisplayName() + { // TRANS: Plugin display name. return _m('XMPP/Jabber'); } + function daemonScreenname() + { + $ret = $this->user . '@' . $this->server; + if ($this->resource) { + return $ret . '/' . $this->resource; + } else { + return $ret; + } + } + + function validate($screenname) + { + return $this->validateBaseJid($screenname, common_config('email', 'check_domain')); + } + + /** + * Checks whether a string is a syntactically valid base Jabber ID (JID). + * A base JID won't include a resource specifier on the end; since we + * take it off when reading input we can't really use them reliably + * to direct outgoing messages yet (sorry guys!) + * + * Note that a bare domain can be a valid JID. + * + * @param string $jid string to check + * @param bool $check_domain whether we should validate that domain... + * + * @return boolean whether the string is a valid JID + */ + protected function validateBaseJid($jid, $check_domain = false) + { + try { + $parts = $this->splitJid($jid); + if ($check_domain) { + if (!$this->checkDomain($parts['domain'])) { + return false; + } + } + return ($parts['resource'] === null); // missing; empty ain't kosher + } catch (Exception $e) { + return false; + } + } + /** * Splits a Jabber ID (JID) into node, domain, and resource portions. * * Based on validation routine submitted by: - * @copyright 2009 Patrick Georgi - * @license Licensed under ISC-L, which is compatible with everything else that keeps the copyright notice intact. - * * @param string $jid string to check * * @return array with "node", "domain", and "resource" indices * @throws Exception if input is not valid + * @license Licensed under ISC-L, which is compatible with everything else that keeps the copyright notice intact. + * + * @copyright 2009 Patrick Georgi */ protected function splitJid($jid) { @@ -109,10 +153,9 @@ class XmppPlugin extends ImPlugin $parts = explode("/", $jid, 2); if (count($parts) > 1) { $resource = $parts[1]; - if ($resource == '') { - // Warning: empty resource isn't legit. - // But if we're normalizing, we may as well take it... - } + // if ($resource == '') then + // Warning: empty resource isn't legit. + // But if we're normalizing, we may as well take it... } else { $resource = null; } @@ -128,7 +171,7 @@ class XmppPlugin extends ImPlugin $domain = $node[1]; $node = $node[0]; if ($node == '') { - // TRANS: Exception thrown when using @ sign not followed by a Jabber ID. + // TRANS: Exception thrown when using @ sign not followed by a Jabber ID. throw new Exception(_m('Invalid JID: @ but no node')); } } @@ -139,10 +182,10 @@ class XmppPlugin extends ImPlugin // TRANS: Exception thrown when using too long a Jabber ID (>1023). throw new Exception(_m('Invalid JID: node too long.')); } - if (preg_match("/[".$nodeprepchars."]/u", $node)) { + if (preg_match("/[" . $nodeprepchars . "]/u", $node)) { // TRANS: Exception thrown when using an invalid Jabber ID. // TRANS: %s is the invalid Jabber ID. - throw new Exception(sprintf(_m('Invalid JID node "%s".'),$node)); + throw new Exception(sprintf(_m('Invalid JID node "%s".'), $node)); } } @@ -153,7 +196,7 @@ class XmppPlugin extends ImPlugin if (!common_valid_domain($domain)) { // TRANS: Exception thrown when using an invalid Jabber domain name. // TRANS: %s is the invalid domain name. - throw new Exception(sprintf(_m('Invalid JID domain name "%s".'),$domain)); + throw new Exception(sprintf(_m('Invalid JID domain name "%s".'), $domain)); } if ($resource !== null) { @@ -161,96 +204,22 @@ class XmppPlugin extends ImPlugin // TRANS: Exception thrown when using too long a resource (>1023). throw new Exception("Invalid JID: resource too long."); } - if (preg_match("/[".$chars."]/u", $resource)) { + if (preg_match("/[" . $chars . "]/u", $resource)) { // TRANS: Exception thrown when using an invalid Jabber resource. // TRANS: %s is the invalid resource. - throw new Exception(sprintf(_m('Invalid JID resource "%s".'),$resource)); + throw new Exception(sprintf(_m('Invalid JID resource "%s".'), $resource)); } } return array('node' => is_null($node) ? null : mb_strtolower($node), - 'domain' => is_null($domain) ? null : mb_strtolower($domain), - 'resource' => $resource); - } - - /** - * Checks whether a string is a syntactically valid Jabber ID (JID), - * either with or without a resource. - * - * Note that a bare domain can be a valid JID. - * - * @param string $jid string to check - * @param bool $check_domain whether we should validate that domain... - * - * @return boolean whether the string is a valid JID - */ - protected function validateFullJid($jid, $check_domain=false) - { - try { - $parts = $this->splitJid($jid); - if ($check_domain) { - if (!$this->checkDomain($parts['domain'])) { - return false; - } - } - return $parts['resource'] !== ''; // missing or present; empty ain't kosher - } catch (Exception $e) { - return false; - } - } - - /** - * Checks whether a string is a syntactically valid base Jabber ID (JID). - * A base JID won't include a resource specifier on the end; since we - * take it off when reading input we can't really use them reliably - * to direct outgoing messages yet (sorry guys!) - * - * Note that a bare domain can be a valid JID. - * - * @param string $jid string to check - * @param bool $check_domain whether we should validate that domain... - * - * @return boolean whether the string is a valid JID - */ - protected function validateBaseJid($jid, $check_domain=false) - { - try { - $parts = $this->splitJid($jid); - if ($check_domain) { - if (!$this->checkDomain($parts['domain'])) { - return false; - } - } - return ($parts['resource'] === null); // missing; empty ain't kosher - } catch (Exception $e) { - return false; - } - } - - /** - * Normalizes a Jabber ID for comparison, dropping the resource component if any. - * - * @param string $jid JID to check - * @param bool $check_domain if true, reject if the domain isn't findable - * - * @return string an equivalent JID in normalized (lowercase) form - */ - function normalize($jid) - { - try { - $parts = $this->splitJid($jid); - if ($parts['node'] !== null) { - return $parts['node'] . '@' . $parts['domain']; - } else { - return $parts['domain']; - } - } catch (Exception $e) { - return null; - } + 'domain' => is_null($domain) ? null : mb_strtolower($domain), + 'resource' => $resource); } /** * Check if this domain's got some legit DNS record + * @param $domain + * @return bool */ protected function checkDomain($domain) { @@ -263,22 +232,6 @@ class XmppPlugin extends ImPlugin return false; } - function daemonScreenname() - { - $ret = $this->user . '@' . $this->server; - if($this->resource) - { - return $ret . '/' . $this->resource; - }else{ - return $ret; - } - } - - function validate($screenname) - { - return $this->validateBaseJid($screenname, common_config('email', 'check_domain')); - } - /** * Load related modules when needed * @@ -289,13 +242,10 @@ class XmppPlugin extends ImPlugin function onAutoload($cls) { - $dir = dirname(__FILE__); - - switch ($cls) - { - case 'XMPPHP_XMPP': - require_once $dir . '/extlib/XMPPHP/XMPP.php'; - return false; + switch ($cls) { + case 'XMPPHP_XMPP': + require_once __DIR__ . '/extlib/XMPPHP/XMPP.php'; + return false; } return parent::onAutoload($cls); @@ -313,10 +263,51 @@ class XmppPlugin extends ImPlugin $this->queuedConnection()->message($screenname, $body, 'chat'); } + /** + * Build a queue-proxied XMPP interface object. Any outgoing messages + * will be run back through us for enqueing rather than sent directly. + * + * @return QueuedXMPP + * @throws Exception if server settings are invalid. + */ + function queuedConnection() + { + if (!isset($this->server)) { + // TRANS: Exception thrown when the plugin configuration is incorrect. + throw new Exception(_m('You must specify a server in the configuration.')); + } + if (!isset($this->port)) { + // TRANS: Exception thrown when the plugin configuration is incorrect. + throw new Exception(_m('You must specify a port in the configuration.')); + } + if (!isset($this->user)) { + // TRANS: Exception thrown when the plugin configuration is incorrect. + throw new Exception(_m('You must specify a user in the configuration.')); + } + if (!isset($this->password)) { + // TRANS: Exception thrown when the plugin configuration is incorrect. + throw new Exception(_m('You must specify a password in the configuration.')); + } + + return new QueuedXMPP($this, $this->host ? + $this->host : + $this->server, + $this->port, + $this->user, + $this->password, + $this->resource, + $this->server, + $this->debug ? + true : false, + $this->debug ? + \XMPPHP\Log::LEVEL_VERBOSE : null + ); + } + function sendNotice($screenname, Notice $notice) { try { - $msg = $this->formatNotice($notice); + $msg = $this->formatNotice($notice); $entry = $this->format_entry($notice); } catch (Exception $e) { common_log(LOG_ERR, __METHOD__ . ": Discarding outgoing stanza because of exception: {$e->getMessage()}"); @@ -329,8 +320,7 @@ class XmppPlugin extends ImPlugin /** * extra information for XMPP messages, as defined by Twitter * - * @param Profile $profile Profile of the sending user - * @param Notice $notice Notice being sent + * @param Notice $notice Notice being sent * * @return string Extra information (Atom, HTML, addresses) in string format */ @@ -351,23 +341,18 @@ class XmppPlugin extends ImPlugin $xs->text(" => "); $xs->element('a', array('href' => $orig_profurl), $orig_profile->nickname); $xs->text(": "); - } catch (InvalidUrlException $e) { - $xs->text(sprintf(' => %s', $orig_profile->nickname)); } catch (NoParentNoticeException $e) { $xs->text(": "); - } catch (NoResultException $e) { - // Parent notice was probably deleted. - $xs->text(": "); } // FIXME: Why do we replace \t with ''? is it just to make it pretty? shouldn't whitespace be handled well...? $xs->raw(str_replace("\t", "", $notice->getRendered())); $xs->text(" "); $xs->element('a', array( - 'href'=>common_local_url('conversation', - array('id' => $notice->conversation)).'#notice-'.$notice->id), - // TRANS: Link description to notice in conversation. - // TRANS: %s is a notice ID. - sprintf(_m('[%u]'),$notice->id)); + 'href' => common_local_url('conversation', + array('id' => $notice->conversation)) . '#notice-' . $notice->id), + // TRANS: Link description to notice in conversation. + // TRANS: %s is a notice ID. + sprintf(_m('[%u]'), $notice->id)); $xs->elementEnd('body'); $xs->elementEnd('html'); @@ -381,12 +366,12 @@ class XmppPlugin extends ImPlugin $from = $this->normalize($pl['from']); if ($pl['type'] != 'chat') { - $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from: " . $pl['xml']->toString()); + $this->log(LOG_WARNING, "Ignoring message of type " . $pl['type'] . " from $from: " . $pl['xml']->toString()); return true; } if (mb_strlen($pl['body']) == 0) { - $this->log(LOG_WARNING, "Ignoring message with empty body from $from: " . $pl['xml']->toString()); + $this->log(LOG_WARNING, "Ignoring message with empty body from $from: " . $pl['xml']->toString()); return true; } @@ -396,43 +381,23 @@ class XmppPlugin extends ImPlugin } /** - * Build a queue-proxied XMPP interface object. Any outgoing messages - * will be run back through us for enqueing rather than sent directly. + * Normalizes a Jabber ID for comparison, dropping the resource component if any. * - * @return QueuedXMPP - * @throws Exception if server settings are invalid. + * @param string $jid JID to check + * @return string an equivalent JID in normalized (lowercase) form */ - function queuedConnection(){ - if(!isset($this->server)){ - // TRANS: Exception thrown when the plugin configuration is incorrect. - throw new Exception(_m('You must specify a server in the configuration.')); - } - if(!isset($this->port)){ - // TRANS: Exception thrown when the plugin configuration is incorrect. - throw new Exception(_m('You must specify a port in the configuration.')); - } - if(!isset($this->user)){ - // TRANS: Exception thrown when the plugin configuration is incorrect. - throw new Exception(_m('You must specify a user in the configuration.')); - } - if(!isset($this->password)){ - // TRANS: Exception thrown when the plugin configuration is incorrect. - throw new Exception(_m('You must specify a password in the configuration.')); + function normalize($jid) + { + try { + $parts = $this->splitJid($jid); + if ($parts['node'] !== null) { + return $parts['node'] . '@' . $parts['domain']; + } else { + return $parts['domain']; + } + } catch (Exception $e) { + return null; } - - return new QueuedXMPP($this, $this->host ? - $this->host : - $this->server, - $this->port, - $this->user, - $this->password, - $this->resource, - $this->server, - $this->debug ? - true : false, - $this->debug ? - XMPPHP_Log::LEVEL_VERBOSE : null - ); } /** @@ -444,10 +409,10 @@ class XmppPlugin extends ImPlugin */ function onGetValidDaemons(&$daemons) { - if( isset($this->server) && - isset($this->port) && - isset($this->user) && - isset($this->password) ){ + if (isset($this->server) && + isset($this->port) && + isset($this->user) && + isset($this->password)) { array_push( $daemons, @@ -459,17 +424,42 @@ class XmppPlugin extends ImPlugin return true; } - function onPluginVersion(array &$versions) { $versions[] = array('name' => 'XMPP', - 'version' => GNUSOCIAL_VERSION, - 'author' => 'Craig Andrews, Evan Prodromou', - 'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/XMPP', - 'rawdescription' => - // TRANS: Plugin description. - _m('The XMPP plugin allows users to send and receive notices over the XMPP/Jabber network.')); + 'version' => GNUSOCIAL_VERSION, + 'author' => 'Craig Andrews, Evan Prodromou', + 'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/XMPP', + 'rawdescription' => + // TRANS: Plugin description. + _m('The XMPP plugin allows users to send and receive notices over the XMPP/Jabber network.')); return true; } + + /** + * Checks whether a string is a syntactically valid Jabber ID (JID), + * either with or without a resource. + * + * Note that a bare domain can be a valid JID. + * + * @param string $jid string to check + * @param bool $check_domain whether we should validate that domain... + * + * @return boolean whether the string is a valid JID + */ + protected function validateFullJid($jid, $check_domain = false) + { + try { + $parts = $this->splitJid($jid); + if ($check_domain) { + if (!$this->checkDomain($parts['domain'])) { + return false; + } + } + return $parts['resource'] !== ''; // missing or present; empty ain't kosher + } catch (Exception $e) { + return false; + } + } } diff --git a/plugins/Xmpp/extlib/XMPPHP/BOSH.php b/plugins/Xmpp/extlib/XMPPHP/BOSH.php index befaf60a77..140ff7c823 100644 --- a/plugins/Xmpp/extlib/XMPPHP/BOSH.php +++ b/plugins/Xmpp/extlib/XMPPHP/BOSH.php @@ -1,188 +1,409 @@ - * @author Stephan Wentz - * @author Michael Garvin + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @author Alexander Birkner (https://github.com/BirknerAlex) + * @author zorn-v (https://github.com/zorn-v/xmpphp/) + * @author GNU social * @copyright 2008 Nathanael C. Fritz */ +namespace XMPPHP; + +use SimpleXMLElement; + /** XMPPHP_XMLStream */ -require_once dirname(__FILE__) . "/XMPP.php"; +require_once __DIR__ . "/XMPP.php"; /** - * XMPPHP Main Class - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ + * XMPPHP BOSH + * + * @property int lat + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ */ -class XMPPHP_BOSH extends XMPPHP_XMPP { - - protected $rid; - protected $sid; - protected $http_server; - protected $http_buffer = Array(); - protected $session = false; - - public function connect($server, $wait='1', $session=false) { - $this->http_server = $server; - $this->use_encryption = false; - $this->session = $session; - - $this->rid = 3001; - $this->sid = null; - if($session) - { - $this->loadSession(); - } - if(!$this->sid) { - $body = $this->__buildBody(); - $body->addAttribute('hold','1'); - $body->addAttribute('to', $this->host); - $body->addAttribute('route', "xmpp:{$this->host}:{$this->port}"); - $body->addAttribute('secure','true'); - $body->addAttribute('xmpp:version','1.6', 'urn:xmpp:xbosh'); - $body->addAttribute('wait', strval($wait)); - $body->addAttribute('ack','1'); - $body->addAttribute('xmlns:xmpp','urn:xmpp:xbosh'); - $buff = ""; - xml_parse($this->parser, $buff, false); - $response = $this->__sendBody($body); - $rxml = new SimpleXMLElement($response); - $this->sid = $rxml['sid']; - - } else { - $buff = ""; - xml_parse($this->parser, $buff, false); - } - } - - public function __sendBody($body=null, $recv=true) { - if(!$body) { - $body = $this->__buildBody(); - } - $ch = curl_init($this->http_server); - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $body->asXML()); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); - $header = array('Accept-Encoding: gzip, deflate','Content-Type: text/xml; charset=utf-8'); - curl_setopt($ch, CURLOPT_HTTPHEADER, $header ); - curl_setopt($ch, CURLOPT_VERBOSE, 0); - $output = ''; - if($recv) { - curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); - $output = curl_exec($ch); - $this->http_buffer[] = $output; - } - curl_close($ch); - return $output; - } - - public function __buildBody($sub=null) { - $xml = new SimpleXMLElement(""); - $xml->addAttribute('content', 'text/xml; charset=utf-8'); - $xml->addAttribute('rid', $this->rid); - $this->rid += 1; - if($this->sid) $xml->addAttribute('sid', $this->sid); - #if($this->sid) $xml->addAttribute('xmlns', 'http://jabber.org/protocol/httpbind'); - $xml->addAttribute('xml:lang', 'en'); - if($sub) { // ok, so simplexml is lame - $p = dom_import_simplexml($xml); - $c = dom_import_simplexml($sub); - $cn = $p->ownerDocument->importNode($c, true); - $p->appendChild($cn); - $xml = simplexml_import_dom($p); - } - return $xml; - } - - public function __process() { - if($this->http_buffer) { - $this->__parseBuffer(); - } else { - $this->__sendBody(); - $this->__parseBuffer(); - } - } - - public function __parseBuffer() { - while ($this->http_buffer) { - $idx = key($this->http_buffer); - $buffer = $this->http_buffer[$idx]; - unset($this->http_buffer[$idx]); - if($buffer) { - $xml = new SimpleXMLElement($buffer); - $children = $xml->xpath('child::node()'); - foreach ($children as $child) { - $buff = $child->asXML(); - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } - } - } - } - - public function send($msg) { - $this->log->log("SEND: $msg", XMPPHP_Log::LEVEL_VERBOSE); - $msg = new SimpleXMLElement($msg); - #$msg->addAttribute('xmlns', 'jabber:client'); - $this->__sendBody($this->__buildBody($msg), true); - #$this->__parseBuffer(); - } - - public function reset() { - $this->xml_depth = 0; - unset($this->xmlobj); - $this->xmlobj = array(); - $this->setupParser(); - #$this->send($this->stream_start); - $body = $this->__buildBody(); - $body->addAttribute('to', $this->host); - $body->addAttribute('xmpp:restart', 'true', 'urn:xmpp:xbosh'); - $buff = ""; - $response = $this->__sendBody($body); - $this->been_reset = true; - xml_parse($this->parser, $buff, false); - } - - public function loadSession() { - if(isset($_SESSION['XMPPHP_BOSH_RID'])) $this->rid = $_SESSION['XMPPHP_BOSH_RID']; - if(isset($_SESSION['XMPPHP_BOSH_SID'])) $this->sid = $_SESSION['XMPPHP_BOSH_SID']; - if(isset($_SESSION['XMPPHP_BOSH_authed'])) $this->authed = $_SESSION['XMPPHP_BOSH_authed']; - if(isset($_SESSION['XMPPHP_BOSH_jid'])) $this->jid = $_SESSION['XMPPHP_BOSH_jid']; - if(isset($_SESSION['XMPPHP_BOSH_fulljid'])) $this->fulljid = $_SESSION['XMPPHP_BOSH_fulljid']; - } - - public function saveSession() { - $_SESSION['XMPPHP_BOSH_RID'] = (string) $this->rid; - $_SESSION['XMPPHP_BOSH_SID'] = (string) $this->sid; - $_SESSION['XMPPHP_BOSH_authed'] = (boolean) $this->authed; - $_SESSION['XMPPHP_BOSH_jid'] = (string) $this->jid; - $_SESSION['XMPPHP_BOSH_fulljid'] = (string) $this->fulljid; - } +class BOSH extends XMPP +{ + /** + * @var integer + */ + protected $rid; + + /** + * @var string + */ + protected $sid; + + /** + * @var string + */ + protected $http_server; + + /** + * @var array + */ + protected $http_buffer = array(); + + /** + * @var string + */ + protected $session = false; + + /** + * @var integer + */ + protected $inactivity; + + /** + * Connect + * + * @param $server + * @param $wait + * @param $session + * @throws Exception + * @throws Exception + */ + public function connect($server = null, $wait = '1', $session = false) + { + + if (is_null($server)) { + + // If we aren't given the server http url, try and guess it + $port_string = ($this->port AND $this->port != 80) ? ':' . $this->port : ''; + $this->http_server = 'http://' . $this->host . $port_string . '/http-bind/'; + } else { + $this->http_server = $server; + } + + $this->use_encryption = false; + $this->session = $session; + $this->rid = 3001; + $this->sid = null; + $this->inactivity = 0; + + if ($session) { + $this->loadSession(); + } + + if (!$this->sid) { + + $body = $this->__buildBody(); + $body->addAttribute('hold', '1'); + $body->addAttribute('to', $this->server); + $body->addAttribute('route', 'xmpp:' . $this->host . ':' . $this->port); + $body->addAttribute('secure', 'true'); + $body->addAttribute('xmpp:version', '1.0', 'urn:xmpp:xbosh'); + $body->addAttribute('wait', strval($wait)); + $body->addAttribute('ack', '1'); + $body->addAttribute('xmlns:xmpp', 'urn:xmpp:xbosh'); + $buff = ''; + xml_parse($this->parser, $buff, false); + $response = $this->__sendBody($body); + $rxml = new SimpleXMLElement($response); + $this->sid = $rxml['sid']; + $this->inactivity = $rxml['inactivity']; + } else { + $buff = ''; + xml_parse($this->parser, $buff, false); + } + } + + /** + * Load session + * + */ + public function loadSession() + { + + if ($this->session == 'ON_FILE') { + + // Session not started so use session_file + $session_file = $this->getSessionFile(); + + // manage multiple accesses + if (!file_exists($session_file)) { + file_put_contents($session_file, ''); + } + $session_file_fp = fopen($session_file, 'r'); + flock($session_file_fp, LOCK_EX); + $session_serialized = file_get_contents($session_file, null, null, 6); + flock($session_file_fp, LOCK_UN); + fclose($session_file_fp); + + $this->log->log('SESSION: reading ' . $session_serialized . ' from ' . $session_file, Log::LEVEL_VERBOSE); + if ($session_serialized != '') { + $_SESSION['XMPPHP_BOSH'] = unserialize($session_serialized); + } + } + + if (isset($_SESSION['XMPPHP_BOSH']['inactivity'])) { + $this->inactivity = $_SESSION['XMPPHP_BOSH']['inactivity']; + } + + $this->lat = (time() - (isset($_SESSION['XMPPHP_BOSH']['lat']))) ? $_SESSION['XMPPHP_BOSH']['lat'] : 0; + + if ($this->lat < $this->inactivity) { + + if (isset($_SESSION['XMPPHP_BOSH']['RID'])) { + $this->rid = $_SESSION['XMPPHP_BOSH']['RID']; + } + if (isset($_SESSION['XMPPHP_BOSH']['SID'])) { + $this->sid = $_SESSION['XMPPHP_BOSH']['SID']; + } + if (isset($_SESSION['XMPPHP_BOSH']['authed'])) { + $this->authed = $_SESSION['XMPPHP_BOSH']['authed']; + } + if (isset($_SESSION['XMPPHP_BOSH']['basejid'])) { + $this->basejid = $_SESSION['XMPPHP_BOSH']['basejid']; + } + if (isset($_SESSION['XMPPHP_BOSH']['fulljid'])) { + $this->fulljid = $_SESSION['XMPPHP_BOSH']['fulljid']; + } + } + } + + /** + * Get the session file + * + */ + public function getSessionFile() + { + return sys_get_temp_dir() . '/' . $this->user . '_' . $this->server . '_session'; + } + + /** + * Build body + * + * @param $sub + * @return SimpleXMLElement|string + */ + public function __buildBody($sub = null) + { + + $xml = ''; + $xml = new SimpleXMLElement($xml); + $xml->addAttribute('content', 'text/xml; charset=utf-8'); + $xml->addAttribute('rid', $this->rid); + $this->rid++; + if ($this->sid) { + $xml->addAttribute('sid', $this->sid); + } + + $xml->addAttribute('xml:lang', 'en'); + + if ($sub !== null) { + + // Ok, so simplexml is lame + $parent = dom_import_simplexml($xml); + $content = dom_import_simplexml($sub); + $child = $parent->ownerDocument->importNode($content, true); + $parent->appendChild($child); + $xml = simplexml_import_dom($parent); + } + + return $xml; + } + + /** + * Send body + * + * @param $body + * @param $recv + * @return bool|string + * @throws Exception + * @throws Exception + */ + public function __sendBody($body = null, $recv = true) + { + + if (!$body) { + $body = $this->__buildBody(); + } + + $output = ''; + $header = array('Accept-Encoding: gzip, deflate', 'Content-Type: text/xml; charset=utf-8'); + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $this->http_server); + curl_setopt($ch, CURLOPT_HEADER, 0); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, $body->asXML()); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, $header); + curl_setopt($ch, CURLOPT_VERBOSE, 0); + + if ($recv) { + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + $output = curl_exec($ch); + if (curl_getinfo($ch, CURLINFO_HTTP_CODE) != '200') { + throw new Exception('Wrong response from server!'); + } + + $this->http_buffer[] = $output; + } + curl_close($ch); + + return $output; + } + + /** + * Process + * + * @param $null1 + * @param $null2 + * + * null params are not used and just to statify Strict Function Declaration + * @return bool + * @throws Exception + * @throws Exception + */ + public function __process($null1 = null, $null2 = null) + { + + if ($this->http_buffer) { + $this->__parseBuffer(); + } else { + $this->__sendBody(); + $this->__parseBuffer(); + } + + $this->saveSession(); + + return true; + } + + public function __parseBuffer() + { + + while ($this->http_buffer) { + + $idx = key($this->http_buffer); + $buffer = $this->http_buffer[$idx]; + unset($this->http_buffer[$idx]); + + if ($buffer) { + + $xml = new SimpleXMLElement($buffer); + $children = $xml->xpath('child::node()'); + + foreach ($children as $child) { + $buff = $child->asXML(); + $this->log->log('RECV: ' . $buff, Log::LEVEL_VERBOSE); + xml_parse($this->parser, $buff, false); + } + } + } + } + + /** + * Save session + * + */ + public function saveSession() + { + + $_SESSION['XMPPHP_BOSH']['RID'] = (string)$this->rid; + $_SESSION['XMPPHP_BOSH']['SID'] = (string)$this->sid; + $_SESSION['XMPPHP_BOSH']['authed'] = (boolean)$this->authed; + $_SESSION['XMPPHP_BOSH']['basejid'] = (string)$this->basejid; + $_SESSION['XMPPHP_BOSH']['fulljid'] = (string)$this->fulljid; + $_SESSION['XMPPHP_BOSH']['inactivity'] = (string)$this->inactivity; + $_SESSION['XMPPHP_BOSH']['lat'] = (string)time(); + + if ($this->session == 'ON_FILE') { + + $session_file = $this->getSessionFile(); + $session_file_fp = fopen($session_file, 'r'); + flock($session_file_fp, LOCK_EX); + // log->log('SEND: ' . $msg, Log::LEVEL_VERBOSE); + $msg = new SimpleXMLElement($msg); + $this->__sendBody($this->__buildBody($msg), true); + } + + /** + * Reset + * + * @throws Exception + */ + public function reset() + { + + $this->xml_depth = 0; + unset($this->xmlobj); + $this->xmlobj = array(); + $this->setupParser(); + $body = $this->__buildBody(); + $body->addAttribute('to', $this->host); + $body->addAttribute('xmpp:restart', 'true', 'urn:xmpp:xbosh'); + $buff = ''; + $response = $this->__sendBody($body); + $this->been_reset = true; + xml_parse($this->parser, $buff, false); + } + + /** + * Disconnect + * + * @throws Exception + */ + public function disconnect() + { + + parent::disconnect(); + + if ($this->session == 'ON_FILE') { + unlink($this->getSessionFile()); + } else { + $keys = array('RID', 'SID', 'authed', 'basejid', 'fulljid', 'inactivity', 'lat'); + foreach ($keys as $key) { + unset($_SESSION['XMPPHP_BOSH'][$key]); + } + } + } } diff --git a/plugins/Xmpp/extlib/XMPPHP/Exception.php b/plugins/Xmpp/extlib/XMPPHP/Exception.php index da59bc7918..525bdd962a 100644 --- a/plugins/Xmpp/extlib/XMPPHP/Exception.php +++ b/plugins/Xmpp/extlib/XMPPHP/Exception.php @@ -1,41 +1,49 @@ * @author Stephan Wentz - * @author Michael Garvin + * @author Michael Garvin + * @author Alexander Birkner (https://github.com/BirknerAlex) + * @author zorn-v (https://github.com/zorn-v/xmpphp/) + * @author GNU social * @copyright 2008 Nathanael C. Fritz */ +namespace XMPPHP; + +use Exception as ObjectException; + /** * XMPPHP Exception * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ */ -class XMPPHP_Exception extends Exception { +class Exception extends ObjectException +{ } diff --git a/plugins/Xmpp/extlib/XMPPHP/Log.php b/plugins/Xmpp/extlib/XMPPHP/Log.php index a9bce3d841..7b511d5014 100644 --- a/plugins/Xmpp/extlib/XMPPHP/Log.php +++ b/plugins/Xmpp/extlib/XMPPHP/Log.php @@ -1,119 +1,129 @@ - * @author Stephan Wentz - * @author Michael Garvin + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @author Alexander Birkner (https://github.com/BirknerAlex) + * @author zorn-v (https://github.com/zorn-v/xmpphp/) + * @author GNU social * @copyright 2008 Nathanael C. Fritz */ +namespace XMPPHP; + /** * XMPPHP Log - * - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ + * + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ */ -class XMPPHP_Log { - - const LEVEL_ERROR = 0; - const LEVEL_WARNING = 1; - const LEVEL_INFO = 2; - const LEVEL_DEBUG = 3; - const LEVEL_VERBOSE = 4; - - /** - * @var array - */ - protected $data = array(); +class Log +{ + const LEVEL_ERROR = 0; + const LEVEL_WARNING = 1; + const LEVEL_INFO = 2; + const LEVEL_DEBUG = 3; + const LEVEL_VERBOSE = 4; + + /** + * @var array + */ + protected $data = array(); + + /** + * @var array + */ + protected $names = array('ERROR', 'WARNING', 'INFO', 'DEBUG', 'VERBOSE'); - /** - * @var array - */ - protected $names = array('ERROR', 'WARNING', 'INFO', 'DEBUG', 'VERBOSE'); + /** + * @var integer + */ + protected $runlevel; - /** - * @var integer - */ - protected $runlevel; + /** + * @var boolean + */ + protected $printout; - /** - * @var boolean - */ - protected $printout; + /** + * Constructor + * + * @param boolean $printout + * @param int $runlevel + */ + public function __construct($printout = false, $runlevel = self::LEVEL_INFO) + { + $this->printout = (boolean)$printout; + $this->runlevel = (int)$runlevel; + } - /** - * Constructor - * - * @param boolean $printout - * @param string $runlevel - */ - public function __construct($printout = false, $runlevel = self::LEVEL_INFO) { - $this->printout = (boolean)$printout; - $this->runlevel = (int)$runlevel; - } + /** + * Add a message to the log data array + * If printout in this instance is set to true, directly output the message + * + * @param string $msg + * @param integer $runlevel + */ + public function log($msg, $runlevel = self::LEVEL_INFO) + { + $time = time(); + #$this->data[] = array($this->runlevel, $msg, $time); + if ($this->printout and $runlevel <= $this->runlevel) { + $this->writeLine($msg, $runlevel, $time); + } + } - /** - * Add a message to the log data array - * If printout in this instance is set to true, directly output the message - * - * @param string $msg - * @param integer $runlevel - */ - public function log($msg, $runlevel = self::LEVEL_INFO) { - $time = time(); - #$this->data[] = array($this->runlevel, $msg, $time); - if($this->printout and $runlevel <= $this->runlevel) { - $this->writeLine($msg, $runlevel, $time); - } - } + protected function writeLine($msg, $runlevel, $time) + { + //echo date('Y-m-d H:i:s', $time)." [".$this->names[$runlevel]."]: ".$msg."\n"; + echo $time . " [" . $this->names[$runlevel] . "]: " . $msg . "\n"; + flush(); + } - /** - * Output the complete log. - * Log will be cleared if $clear = true - * - * @param boolean $clear - * @param integer $runlevel - */ - public function printout($clear = true, $runlevel = null) { - if($runlevel === null) { - $runlevel = $this->runlevel; - } - foreach($this->data as $data) { - if($runlevel <= $data[0]) { - $this->writeLine($data[1], $runlevel, $data[2]); - } - } - if($clear) { - $this->data = array(); - } - } - - protected function writeLine($msg, $runlevel, $time) { - //echo date('Y-m-d H:i:s', $time)." [".$this->names[$runlevel]."]: ".$msg."\n"; - echo $time." [".$this->names[$runlevel]."]: ".$msg."\n"; - flush(); - } + /** + * Output the complete log. + * Log will be cleared if $clear = true + * + * @param boolean $clear + * @param integer $runlevel + */ + public function printout($clear = true, $runlevel = null) + { + if ($runlevel === null) { + $runlevel = $this->runlevel; + } + foreach ($this->data as $data) { + if ($runlevel <= $data[0]) { + $this->writeLine($data[1], $runlevel, $data[2]); + } + } + if ($clear) { + $this->data = array(); + } + } } diff --git a/plugins/Xmpp/extlib/XMPPHP/Roster.php b/plugins/Xmpp/extlib/XMPPHP/Roster.php index 500ff6dcc1..799c549140 100644 --- a/plugins/Xmpp/extlib/XMPPHP/Roster.php +++ b/plugins/Xmpp/extlib/XMPPHP/Roster.php @@ -1,164 +1,183 @@ - * @author Stephan Wentz - * @author Michael Garvin + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @author Alexander Birkner (https://github.com/BirknerAlex) + * @author zorn-v (https://github.com/zorn-v/xmpphp/) + * @author GNU social * @copyright 2008 Nathanael C. Fritz */ +namespace XMPPHP; + /** - * XMPPHP Roster Object - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ + * XMPPHP Roster + * + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ */ +class Roster +{ + /** + * Roster array, handles contacts and presence. Indexed by jid. + * Contains array with potentially two indexes 'contact' and 'presence' + * @var array + */ + protected $roster_array = array(); + + /** + * Constructor + * @param array $roster_array + */ + public function __construct($roster_array = array()) + { + if ($this->verifyRoster($roster_array)) { + $this->roster_array = $roster_array; //Allow for pre-population with existing roster + } else { + $this->roster_array = array(); + } + } + + /** + * + * Check that a given roster array is of a valid structure (empty is still valid) + * + * @param array $roster_array + * @return bool + */ + protected function verifyRoster($roster_array) + { + #TODO once we know *what* a valid roster array looks like + return true; + } -class Roster { - /** - * Roster array, handles contacts and presence. Indexed by jid. - * Contains array with potentially two indexes 'contact' and 'presence' - * @var array - */ - protected $roster_array = array(); - /** - * Constructor - * - */ - public function __construct($roster_array = array()) { - if ($this->verifyRoster($roster_array)) { - $this->roster_array = $roster_array; //Allow for prepopulation with existing roster - } else { - $this->roster_array = array(); - } - } + /** + * + * Retrieve contact via jid + * + * @param string $jid + * @return mixed + */ + public function getContact($jid) + { + if ($this->isContact($jid)) { + return $this->roster_array[$jid]['contact']; + } + } - /** - * - * Check that a given roster array is of a valid structure (empty is still valid) - * - * @param array $roster_array - */ - protected function verifyRoster($roster_array) { - #TODO once we know *what* a valid roster array looks like - return True; - } + /** + * + * Discover if a contact exists in the roster via jid + * + * @param string $jid + * @return bool + */ + public function isContact($jid) + { + return (array_key_exists($jid, $this->roster_array)); + } - /** - * - * Add given contact to roster - * - * @param string $jid - * @param string $subscription - * @param string $name - * @param array $groups - */ - public function addContact($jid, $subscription, $name='', $groups=array()) { - $contact = array('jid' => $jid, 'subscription' => $subscription, 'name' => $name, 'groups' => $groups); - if ($this->isContact($jid)) { - $this->roster_array[$jid]['contact'] = $contact; - } else { - $this->roster_array[$jid] = array('contact' => $contact); - } - } + /** + * + * Set presence + * + * @param string $presence + * @param integer $priority + * @param string $show + * @param string $status + */ + public function setPresence($presence, $priority, $show, $status) + { + $presence = explode('/', $presence, 2); + $jid = $presence[0]; + $resource = isset($presence[1]) ? $presence[1] : ''; + if ($show != 'unavailable') { + if (!$this->isContact($jid)) { + $this->addContact($jid, 'not-in-roster'); + } + $this->roster_array[$jid]['presence'][$resource] = array('priority' => $priority, 'show' => $show, 'status' => $status); + } else { //Nuke unavailable resources to save memory + unset($this->roster_array[$jid]['resource'][$resource]); + unset($this->roster_array[$jid]['presence'][$resource]); + } + } - /** - * - * Retrieve contact via jid - * - * @param string $jid - */ - public function getContact($jid) { - if ($this->isContact($jid)) { - return $this->roster_array[$jid]['contact']; - } - } + /** + * + * Add given contact to roster + * + * @param string $jid + * @param string $subscription + * @param string $name + * @param array $groups + */ + public function addContact($jid, $subscription, $name = '', $groups = array()) + { + $contact = array('jid' => $jid, 'subscription' => $subscription, 'name' => $name, 'groups' => $groups); + if ($this->isContact($jid)) { + $this->roster_array[$jid]['contact'] = $contact; + } else { + $this->roster_array[$jid] = array('contact' => $contact); + } + } - /** - * - * Discover if a contact exists in the roster via jid - * - * @param string $jid - */ - public function isContact($jid) { - return (array_key_exists($jid, $this->roster_array)); - } + /* + * + * Return best presence for jid + * + * @param string $jid + */ - /** - * - * Set presence - * - * @param string $presence - * @param integer $priority - * @param string $show - * @param string $status - */ - public function setPresence($presence, $priority, $show, $status) { - $parts = explode('/', $presence); - $jid = $parts[0]; - $resource = isset($parts[1]) ? $parts[1] : ''; // apparently we can do '' as an associative array index - if ($show != 'unavailable') { - if (!$this->isContact($jid)) { - $this->addContact($jid, 'not-in-roster'); - } - $this->roster_array[$jid]['presence'][$resource] = array('priority' => $priority, 'show' => $show, 'status' => $status); - } else { //Nuke unavailable resources to save memory - unset($this->roster_array[$jid]['resource'][$resource]); - } - } + public function getPresence($jid) + { + $split = explode('/', $jid, 2); + $jid = $split[0]; + if ($this->isContact($jid)) { + $current = array('resource' => '', 'active' => '', 'priority' => -129, 'show' => '', 'status' => ''); //Priorities can only be -128 = 127 + foreach ($this->roster_array[$jid]['presence'] as $resource => $presence) { + //Highest available priority or just highest priority + if ($presence['priority'] > $current['priority'] and (($presence['show'] == "chat" or $presence['show'] == "available") or ($current['show'] != "chat" or $current['show'] != "available"))) { + $current = $presence; + $current['resource'] = $resource; + } + } + return $current; + } + } - /* - * - * Return best presence for jid - * - * @param string $jid - */ - public function getPresence($jid) { - $split = explode("/", $jid); - $jid = $split[0]; - if($this->isContact($jid)) { - $current = array('resource' => '', 'active' => '', 'priority' => -129, 'show' => '', 'status' => ''); //Priorities can only be -128 = 127 - foreach($this->roster_array[$jid]['presence'] as $resource => $presence) { - //Highest available priority or just highest priority - if ($presence['priority'] > $current['priority'] and (($presence['show'] == "chat" or $presence['show'] == "available") or ($current['show'] != "chat" or $current['show'] != "available"))) { - $current = $presence; - $current['resource'] = $resource; - } - } - return $current; - } - } - /** - * - * Get roster - * - */ - public function getRoster() { - return $this->roster_array; - } + /** + * + * Get roster + * + */ + public function getRoster() + { + return $this->roster_array; + } } -?> diff --git a/plugins/Xmpp/extlib/XMPPHP/XMLObj.php b/plugins/Xmpp/extlib/XMPPHP/XMLObj.php index 0d3e219912..de359ae96a 100644 --- a/plugins/Xmpp/extlib/XMPPHP/XMLObj.php +++ b/plugins/Xmpp/extlib/XMPPHP/XMLObj.php @@ -1,158 +1,174 @@ - - * @author Stephan Wentz - * @author Michael Garvin + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @author Alexander Birkner (https://github.com/BirknerAlex) + * @author zorn-v (https://github.com/zorn-v/xmpphp/) + * @author GNU social * @copyright 2008 Nathanael C. Fritz */ +namespace XMPPHP; + /** - * XMPPHP XML Object - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ + * XMPPHP XMLObject + * + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ */ -class XMPPHP_XMLObj { - /** - * Tag name - * - * @var string - */ - public $name; - - /** - * Namespace - * - * @var string - */ - public $ns; - - /** - * Attributes - * - * @var array - */ - public $attrs = array(); - - /** - * Subs? - * - * @var array - */ - public $subs = array(); - - /** - * Node data - * - * @var string - */ - public $data = ''; +class XMLObj +{ + /** + * Tag name + * + * @var string + */ + public $name; + + /** + * Namespace + * + * @var string + */ + public $ns; + + /** + * Attributes + * + * @var array + */ + public $attrs = array(); + + /** + * Subs? + * + * @var array + */ + public $subs = array(); + + /** + * Node data + * + * @var string + */ + public $data = ''; - /** - * Constructor - * - * @param string $name - * @param string $ns - * @param array $attrs - * @param string $data - */ - public function __construct($name, $ns = '', $attrs = array(), $data = '') { - $this->name = strtolower($name); - $this->ns = $ns; - if(is_array($attrs) && count($attrs)) { - foreach($attrs as $key => $value) { - $this->attrs[strtolower($key)] = $value; - } - } - $this->data = $data; - } + /** + * Constructor + * + * @param string $name + * @param string $ns + * @param array $attrs + * @param string $data + */ + public function __construct($name, $ns = '', $attrs = array(), $data = '') + { + $this->name = strtolower($name); + $this->ns = $ns; + if (is_array($attrs) && count($attrs)) { + foreach ($attrs as $key => $value) { + $this->attrs[strtolower($key)] = $value; + } + } + $this->data = $data; + } - /** - * Dump this XML Object to output. - * - * @param integer $depth - */ - public function printObj($depth = 0) { - print str_repeat("\t", $depth) . $this->name . " " . $this->ns . ' ' . $this->data; - print "\n"; - foreach($this->subs as $sub) { - $sub->printObj($depth + 1); - } - } + /** + * Dump this XML Object to output. + * + * @param integer $depth + */ + public function printObj($depth = 0) + { + print str_repeat("\t", $depth) . $this->name . " " . $this->ns . ' ' . $this->data; + print "\n"; + foreach ($this->subs as $sub) { + $sub->printObj($depth + 1); + } + } - /** - * Return this XML Object in xml notation - * - * @param string $str - */ - public function toString($str = '') { - $str .= "<{$this->name} xmlns='{$this->ns}' "; - foreach($this->attrs as $key => $value) { - if($key != 'xmlns') { - $value = htmlspecialchars($value); - $str .= "$key='$value' "; - } - } - $str .= ">"; - foreach($this->subs as $sub) { - $str .= $sub->toString(); - } - $body = htmlspecialchars($this->data); - $str .= "$bodyname}>"; - return $str; - } + /** + * Return this XML Object in xml notation + * + * @param string $str + * @return string + */ + public function toString($str = '') + { + $str .= "<{$this->name} xmlns='{$this->ns}' "; + foreach ($this->attrs as $key => $value) { + if ($key != 'xmlns') { + $value = htmlspecialchars($value); + $str .= "$key='$value' "; + } + } + $str .= ">"; + foreach ($this->subs as $sub) { + $str .= $sub->toString(); + } + $body = htmlspecialchars($this->data); + $str .= "$bodyname}>"; + return $str; + } - /** - * Has this XML Object the given sub? - * - * @param string $name - * @return boolean - */ - public function hasSub($name, $ns = null) { - foreach($this->subs as $sub) { - if(($name == "*" or $sub->name == $name) and ($ns == null or $sub->ns == $ns)) return true; - } - return false; - } + /** + * Has this XML Object the given sub? + * + * @param string $name + * @param null $ns + * @return boolean + */ + public function hasSub($name, $ns = null) + { + foreach ($this->subs as $sub) { + if (($name == "*" or $sub->name == $name) and ($ns == null or $sub->ns == $ns)) { + return true; + } + } + return false; + } - /** - * Return a sub - * - * @param string $name - * @param string $attrs - * @param string $ns - */ - public function sub($name, $attrs = null, $ns = null) { - #TODO attrs is ignored - foreach($this->subs as $sub) { - if($sub->name == $name and ($ns == null or $sub->ns == $ns)) { - return $sub; - } - } - } + /** + * Return a sub + * + * @param string $name + * @param string $attrs + * @param string $ns + * @return mixed + */ + public function sub($name, $attrs = null, $ns = null) + { + #TODO attrs is ignored + foreach ($this->subs as $sub) { + if ($sub->name == $name and ($ns == null or $sub->ns == $ns)) { + return $sub; + } + } + } } diff --git a/plugins/Xmpp/extlib/XMPPHP/XMLStream.php b/plugins/Xmpp/extlib/XMPPHP/XMLStream.php index 1606a9f07e..615e7ca12d 100644 --- a/plugins/Xmpp/extlib/XMPPHP/XMLStream.php +++ b/plugins/Xmpp/extlib/XMPPHP/XMLStream.php @@ -1,763 +1,804 @@ - * @author Stephan Wentz - * @author Michael Garvin + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @author Alexander Birkner (https://github.com/BirknerAlex) + * @author zorn-v (https://github.com/zorn-v/xmpphp/) + * @author GNU social * @copyright 2008 Nathanael C. Fritz */ -/** XMPPHP_Exception */ +namespace XMPPHP; + +/** Exception */ require_once __DIR__ . DIRECTORY_SEPARATOR . 'Exception.php'; -/** XMPPHP_XMLObj */ +/** XMLObj */ require_once __DIR__ . DIRECTORY_SEPARATOR . 'XMLObj.php'; -/** XMPPHP_Log */ +/** Log */ require_once __DIR__ . DIRECTORY_SEPARATOR . 'Log.php'; + /** - * XMPPHP XML Stream - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ + * XMPPHP XMLStream + * + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ */ -class XMPPHP_XMLStream { - /** - * @var resource - */ - protected $socket; - /** - * @var resource - */ - protected $parser; - /** - * @var string - */ - protected $buffer; - /** - * @var integer - */ - protected $xml_depth = 0; - /** - * @var string - */ - protected $host; - /** - * @var integer - */ - protected $port; - /** - * @var string - */ - protected $stream_start = ''; - /** - * @var string - */ - protected $stream_end = ''; - /** - * @var boolean - */ - protected $disconnected = false; - /** - * @var boolean - */ - protected $sent_disconnect = false; - /** - * @var array - */ - protected $ns_map = array(); - /** - * @var array - */ - protected $current_ns = array(); - /** - * @var array - */ - protected $xmlobj = null; - /** - * @var array - */ - protected $nshandlers = array(); - /** - * @var array - */ - protected $xpathhandlers = array(); - /** - * @var array - */ - protected $idhandlers = array(); - /** - * @var array - */ - protected $eventhandlers = array(); - /** - * @var integer - */ - protected $lastid = 0; - /** - * @var string - */ - protected $default_ns; - /** - * @var string[] - */ - protected $until = []; - /** - * @var int[] - */ - protected $until_count = []; - /** - * @var array - */ - protected $until_happened = false; - /** - * @var array - */ - protected $until_payload = array(); - /** - * @var XMPPHP_Log - */ - protected $log; - /** - * @var boolean - */ - protected $reconnect = true; - /** - * @var boolean - */ - protected $been_reset = false; - /** - * @var boolean - */ - protected $is_server; - /** - * @var float - */ - protected $last_send = 0; - /** - * @var boolean - */ - protected $use_ssl = false; - /** - * @var integer - */ - protected $reconnectTimeout = 30; - - /** - * Constructor - * - * @param string $host - * @param string $port - * @param boolean $printlog - * @param string $loglevel - * @param boolean $is_server - */ - public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) { - $this->reconnect = !$is_server; - $this->is_server = $is_server; - $this->host = $host; - $this->port = $port; - $this->setupParser(); - $this->log = new XMPPHP_Log($printlog, $loglevel); - } - - /** - * Destructor - * Cleanup connection - */ - public function __destruct() { - if(!$this->disconnected && $this->socket) { - $this->disconnect(); - } - } - - /** - * Return the log instance - * - * @return XMPPHP_Log - */ - public function getLog() { - return $this->log; - } - - /** - * Get next ID - * - * @return integer - */ - public function getId() { - $this->lastid++; - return $this->lastid; - } - - /** - * Set SSL - * - * @return integer - */ - public function useSSL($use=true) { - $this->use_ssl = $use; - } - - /** - * Add ID Handler - * - * @param integer $id - * @param string $pointer - * @param string $obj - */ - public function addIdHandler($id, $pointer, $obj = null) { - $this->idhandlers[$id] = array($pointer, $obj); - } - - /** - * Add Handler - * - * @param string $name - * @param string $ns - * @param string $pointer - * @param string $obj - * @param integer $depth - */ - public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) { - #TODO deprication warning - $this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth); - } - - /** - * Add XPath Handler - * - * @param string $xpath - * @param string $pointer - * @param - */ - public function addXPathHandler($xpath, $pointer, $obj = null) { - if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) { - $ns_tags = $regs[0]; - } else { - $ns_tags = array($xpath); - } - foreach($ns_tags as $ns_tag) { - list($l, $r) = explode("}", $ns_tag); - if ($r != null) { - $xpart = array(substr($l, 1), $r); - } else { - $xpart = array(null, $l); - } - $xpath_array[] = $xpart; - } - $this->xpathhandlers[] = array($xpath_array, $pointer, $obj); - } - - /** - * Add Event Handler - * - * @param integer $id - * @param string $pointer - * @param string $obj - */ - public function addEventHandler($name, $pointer, $obj) { - $this->eventhandlers[] = array($name, $pointer, $obj); - } - - /** - * Connect to XMPP Host - * - * @param integer $timeout - * @param boolean $persistent - * @param boolean $sendinit - */ - public function connect($timeout = 30, $persistent = false, $sendinit = true) { - $this->sent_disconnect = false; - $starttime = time(); - - do { - $this->disconnected = false; - $this->sent_disconnect = false; - if($persistent) { - $conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT; - } else { - $conflag = STREAM_CLIENT_CONNECT; - } - $conntype = 'tcp'; - if($this->use_ssl) $conntype = 'ssl'; - $this->log->log("Connecting to $conntype://{$this->host}:{$this->port}"); - try { - $this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag); - } catch (Exception $e) { - throw new XMPPHP_Exception($e->getMessage()); - } - if(!$this->socket) { - $this->log->log("Could not connect.", XMPPHP_Log::LEVEL_ERROR); - $this->disconnected = true; - # Take it easy for a few seconds - sleep(min($timeout, 5)); - } - } while (!$this->socket && (time() - $starttime) < $timeout); - - if ($this->socket) { - stream_set_blocking($this->socket, 1); - if($sendinit) $this->send($this->stream_start); - } else { - throw new XMPPHP_Exception("Could not connect before timeout."); - } - } - - /** - * Reconnect XMPP Host - */ - public function doReconnect() { - if(!$this->is_server) { - $this->log->log("Reconnecting ($this->reconnectTimeout)...", XMPPHP_Log::LEVEL_WARNING); - $this->connect($this->reconnectTimeout, false, false); - $this->reset(); - $this->event('reconnect'); - } - } - - public function setReconnectTimeout($timeout) { - $this->reconnectTimeout = $timeout; - } - - /** - * Disconnect from XMPP Host - */ - public function disconnect() { - $this->log->log("Disconnecting...", XMPPHP_Log::LEVEL_VERBOSE); - if(false == (bool) $this->socket) { - return; - } - $this->reconnect = false; - $this->send($this->stream_end); - $this->sent_disconnect = true; - $this->processUntil('end_stream', 5); - $this->disconnected = true; - } - - /** - * Are we are disconnected? - * - * @return boolean - */ - public function isDisconnected() { - return $this->disconnected; - } - - /** - * Core reading tool - * 0 -> only read if data is immediately ready - * NULL -> wait forever and ever - * integer -> process for this amount of time - */ - - private function __process($maximum=5) { - - $remaining = $maximum; - - do { - $starttime = (microtime(true) * 1000000); - $read = array($this->socket); - $write = array(); - $except = array(); - if (is_null($maximum)) { - $secs = NULL; - $usecs = NULL; - } else if ($maximum == 0) { - $secs = 0; - $usecs = 0; - } else { - $usecs = $remaining % 1000000; - $secs = floor(($remaining - $usecs) / 1000000); - } - $updated = @stream_select($read, $write, $except, $secs, $usecs); - if ($updated === false) { - $this->log->log("Error on stream_select()", XMPPHP_Log::LEVEL_VERBOSE); - if ($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - $this->socket = NULL; - return false; - } - } else if ($updated > 0) { - # XXX: Is this big enough? - $buff = @fread($this->socket, 4096); - if(!$buff) { - if($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - $this->socket = NULL; - return false; - } - } - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } else { - # $updated == 0 means no changes during timeout. - } - $endtime = (microtime(true)*1000000); - $time_past = $endtime - $starttime; - $remaining = $remaining - $time_past; - } while (is_null($maximum) || $remaining > 0); - return true; - } - - /** - * Process - * - * @return string - */ - public function process() { - $this->__process(NULL); - } - - /** - * Process until a timeout occurs - * - * @param integer $timeout - * @return string - */ - public function processTime($timeout=NULL) { - if (is_null($timeout)) { - return $this->__process(NULL); - } else { - return $this->__process($timeout * 1000000); - } - } - - /** - * Process until a specified event or a timeout occurs - * - * @param string|array $event - * @param integer $timeout - * @return string - */ - public function processUntil($event, $timeout=-1) { - $start = time(); - if(!is_array($event)) $event = array($event); - $this->until[] = $event; - end($this->until); - $event_key = key($this->until); - reset($this->until); - $this->until_count[$event_key] = 0; - $updated = ''; - while(!$this->disconnected and $this->until_count[$event_key] < 1 and (time() - $start < $timeout or $timeout == -1)) { - $this->__process(); - } - if(array_key_exists($event_key, $this->until_payload)) { - $payload = $this->until_payload[$event_key]; - unset($this->until_payload[$event_key]); - unset($this->until_count[$event_key]); - unset($this->until[$event_key]); - } else { - $payload = array(); - } - return $payload; - } - - /** - * Obsolete? - */ - public function Xapply_socket($socket) { - $this->socket = $socket; - } - - /** - * XML start callback - * - * @see xml_set_element_handler - * - * @param resource $parser - * @param string $name - */ - public function startXML($parser, $name, $attr) { - if($this->been_reset) { - $this->been_reset = false; - $this->xml_depth = 0; - } - $this->xml_depth++; - if(array_key_exists('XMLNS', $attr)) { - $this->current_ns[$this->xml_depth] = $attr['XMLNS']; - } else { - $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1]; - if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns; - } - $ns = $this->current_ns[$this->xml_depth]; - foreach($attr as $key => $value) { - if(strstr($key, ":")) { - $key = explode(':', $key); - $key = $key[1]; - $this->ns_map[$key] = $value; - } - } - if(!strstr($name, ":") === false) - { - $name = explode(':', $name); - $ns = $this->ns_map[$name[0]]; - $name = $name[1]; - } - $obj = new XMPPHP_XMLObj($name, $ns, $attr); - if($this->xml_depth > 1) { - $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj; - } - $this->xmlobj[$this->xml_depth] = $obj; - } - - /** - * XML end callback - * - * @see xml_set_element_handler - * - * @param resource $parser - * @param string $name - */ - public function endXML($parser, $name) { - #$this->log->log("Ending $name", XMPPHP_Log::LEVEL_DEBUG); - #print "$name\n"; - if($this->been_reset) { - $this->been_reset = false; - $this->xml_depth = 0; - } - $this->xml_depth--; - if($this->xml_depth == 1) { - #clean-up old objects - #$found = false; #FIXME This didn't appear to be in use --Gar - foreach($this->xpathhandlers as $handler) { - if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) { - $searchxml = $this->xmlobj[2]; - $nstag = array_shift($handler[0]); - if (($nstag[0] == null or $searchxml->ns == $nstag[0]) and ($nstag[1] == "*" or $nstag[1] == $searchxml->name)) { - foreach($handler[0] as $nstag) { - if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns=$nstag[0])) { - $searchxml = $searchxml->sub($nstag[1], $ns=$nstag[0]); - } else { - $searchxml = null; - break; - } - } - if ($searchxml !== null) { - if($handler[2] === null) $handler[2] = $this; - $this->log->log("Calling {$handler[1]}", XMPPHP_Log::LEVEL_DEBUG); - $handler[2]->{$handler[1]}($this->xmlobj[2]); - } - } - } - } - foreach($this->nshandlers as $handler) { - if($handler[4] != 1 and array_key_exists(2, $this->xmlobj) and $this->xmlobj[2]->hasSub($handler[0])) { - $searchxml = $this->xmlobj[2]->sub($handler[0]); - } elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) { - $searchxml = $this->xmlobj[2]; - } - if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) { - if($handler[3] === null) $handler[3] = $this; - $this->log->log("Calling {$handler[2]}", XMPPHP_Log::LEVEL_DEBUG); - $handler[3]->{$handler[2]}($this->xmlobj[2]); - } - } - foreach($this->idhandlers as $id => $handler) { - if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) { - if($handler[1] === null) $handler[1] = $this; - $handler[1]->{$handler[0]}($this->xmlobj[2]); - #id handlers are only used once - unset($this->idhandlers[$id]); - break; - } - } - if(is_array($this->xmlobj)) { - $this->xmlobj = array_slice($this->xmlobj, 0, 1); - if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMPPHP_XMLObj) { - $this->xmlobj[0]->subs = null; - } - } - unset($this->xmlobj[2]); - } - if($this->xml_depth == 0 and !$this->been_reset) { - if(!$this->disconnected) { - if(!$this->sent_disconnect) { - $this->send($this->stream_end); - } - $this->disconnected = true; - $this->sent_disconnect = true; - fclose($this->socket); - if($this->reconnect) { - $this->doReconnect(); - } - } - $this->event('end_stream'); - } - } - - /** - * XML character callback - * @see xml_set_character_data_handler - * - * @param resource $parser - * @param string $data - */ - public function charXML($parser, $data) { - if(array_key_exists($this->xml_depth, $this->xmlobj)) { - $this->xmlobj[$this->xml_depth]->data .= $data; - } - } - - /** - * Event? - * - * @param string $name - * @param string $payload - */ - public function event($name, $payload = null) { - $this->log->log("EVENT: $name", XMPPHP_Log::LEVEL_DEBUG); - foreach($this->eventhandlers as $handler) { - if($name == $handler[0]) { - if($handler[2] === null) { - $handler[2] = $this; - } - $handler[2]->{$handler[1]}($payload); - } - } - foreach($this->until as $key => $until) { - if(is_array($until)) { - if(in_array($name, $until)) { - $this->until_payload[$key][] = array($name, $payload); - if(!isset($this->until_count[$key])) { - $this->until_count[$key] = 0; - } - $this->until_count[$key] += 1; - #$this->until[$key] = false; - } - } - } - } - - /** - * Read from socket - */ - public function read() { - $buff = @fread($this->socket, 1024); - if(!$buff) { - if($this->reconnect) { - $this->doReconnect(); - } else { - fclose($this->socket); - return false; - } - } - $this->log->log("RECV: $buff", XMPPHP_Log::LEVEL_VERBOSE); - xml_parse($this->parser, $buff, false); - } - - /** - * Send to socket - * - * @param string $msg - */ - public function send($msg, $timeout=NULL) { - - if (is_null($timeout)) { - $secs = NULL; - $usecs = NULL; - } else if ($timeout == 0) { - $secs = 0; - $usecs = 0; - } else { - $maximum = $timeout * 1000000; - $usecs = $maximum % 1000000; - $secs = floor(($maximum - $usecs) / 1000000); - } - - $read = array(); - $write = array($this->socket); - $except = array(); - - $select = @stream_select($read, $write, $except, $secs, $usecs); - - if($select === False) { - $this->log->log("ERROR sending message; reconnecting."); - $this->doReconnect(); - # TODO: retry send here - return false; - } elseif ($select > 0) { - $this->log->log("Socket is ready; send it.", XMPPHP_Log::LEVEL_VERBOSE); - } else { - $this->log->log("Socket is not ready; break.", XMPPHP_Log::LEVEL_ERROR); - return false; - } - - $sentbytes = @fwrite($this->socket, $msg); - $this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), XMPPHP_Log::LEVEL_VERBOSE); - if($sentbytes === FALSE) { - $this->log->log("ERROR sending message; reconnecting.", XMPPHP_Log::LEVEL_ERROR); - $this->doReconnect(); - return false; - } - $this->log->log("Successfully sent $sentbytes bytes.", XMPPHP_Log::LEVEL_VERBOSE); - return $sentbytes; - } - - public function time() { - list($usec, $sec) = explode(" ", microtime()); - return (float)$sec + (float)$usec; - } - - /** - * Reset connection - */ - public function reset() { - $this->xml_depth = 0; - unset($this->xmlobj); - $this->xmlobj = array(); - $this->setupParser(); - if(!$this->is_server) { - $this->send($this->stream_start); - } - $this->been_reset = true; - } - - /** - * Setup the XML parser - */ - public function setupParser() { - $this->parser = xml_parser_create('UTF-8'); - xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1); - xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); - xml_set_object($this->parser, $this); - xml_set_element_handler($this->parser, 'startXML', 'endXML'); - xml_set_character_data_handler($this->parser, 'charXML'); - } - - public function readyToProcess() { - $read = array($this->socket); - $write = array(); - $except = array(); - $updated = @stream_select($read, $write, $except, 0); - return (($updated !== false) && ($updated > 0)); - } +class XMLStream +{ + /** + * @var resource + */ + protected $socket; + /** + * @var resource + */ + protected $parser; + /** + * @var string + */ + protected $buffer; + /** + * @var integer + */ + protected $xml_depth = 0; + /** + * @var string + */ + protected $host; + /** + * @var integer + */ + protected $port; + /** + * @var string + */ + protected $stream_start = ''; + /** + * @var string + */ + protected $stream_end = ''; + /** + * @var boolean + */ + protected $disconnected = false; + /** + * @var boolean + */ + protected $sent_disconnect = false; + /** + * @var array + */ + protected $ns_map = array(); + /** + * @var array + */ + protected $current_ns = array(); + /** + * @var array + */ + protected $xmlobj = null; + /** + * @var array + */ + protected $nshandlers = array(); + /** + * @var array + */ + protected $xpathhandlers = array(); + /** + * @var array + */ + protected $idhandlers = array(); + /** + * @var array + */ + protected $eventhandlers = array(); + /** + * @var integer + */ + protected $lastid = 0; + /** + * @var string + */ + protected $default_ns; + /** + * @var string[] + */ + protected $until = []; + /** + * @var int[] + */ + protected $until_count = []; + /** + * @var array + */ + protected $until_happened = false; + /** + * @var array + */ + protected $until_payload = array(); + /** + * @var Log + */ + protected $log; + /** + * @var boolean + */ + protected $reconnect = true; + /** + * @var boolean + */ + protected $been_reset = false; + /** + * @var boolean + */ + protected $is_server; + /** + * @var float + */ + protected $last_send = 0; + /** + * @var boolean + */ + protected $use_ssl = false; + /** + * @var integer + */ + protected $reconnectTimeout = 30; + + /** + * Constructor + * + * @param string $host + * @param string $port + * @param boolean $printlog + * @param string $loglevel + * @param boolean $is_server + */ + public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) + { + $this->reconnect = !$is_server; + $this->is_server = $is_server; + $this->host = $host; + $this->port = $port; + $this->setupParser(); + $this->log = new Log($printlog, $loglevel); + } + + /** + * Setup the XML parser + */ + public function setupParser() + { + $this->parser = xml_parser_create('UTF-8'); + xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1); + xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8'); + xml_set_object($this->parser, $this); + xml_set_element_handler($this->parser, 'startXML', 'endXML'); + xml_set_character_data_handler($this->parser, 'charXML'); + } + + /** + * Destructor + * Cleanup connection + */ + public function __destruct() + { + if (!$this->disconnected && $this->socket) { + $this->disconnect(); + } + } + + /** + * Disconnect from XMPP Host + */ + public function disconnect() + { + $this->log->log("Disconnecting...", Log::LEVEL_VERBOSE); + if (false == (bool)$this->socket) { + return; + } + $this->reconnect = false; + $this->send($this->stream_end); + $this->sent_disconnect = true; + $this->processUntil('end_stream', 5); + $this->disconnected = true; + } + + /** + * Send to socket + * + * @param string $msg + * @param null $timeout + * @return bool|int + * @throws Exception + */ + public function send($msg, $timeout = NULL) + { + + if (is_null($timeout)) { + $secs = NULL; + $usecs = NULL; + } else if ($timeout == 0) { + $secs = 0; + $usecs = 0; + } else { + $maximum = $timeout * 1000000; + $usecs = $maximum % 1000000; + $secs = floor(($maximum - $usecs) / 1000000); + } + + $read = array(); + $write = array($this->socket); + $except = array(); + + $select = @stream_select($read, $write, $except, $secs, $usecs); + + if ($select === False) { + $this->log->log("ERROR sending message; reconnecting."); + $this->doReconnect(); + # TODO: retry send here + return false; + } elseif ($select > 0) { + $this->log->log("Socket is ready; send it.", Log::LEVEL_VERBOSE); + } else { + $this->log->log("Socket is not ready; break.", Log::LEVEL_ERROR); + return false; + } + + $sentbytes = @fwrite($this->socket, $msg); + $this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), Log::LEVEL_VERBOSE); + if ($sentbytes === FALSE) { + $this->log->log("ERROR sending message; reconnecting.", Log::LEVEL_ERROR); + $this->doReconnect(); + return false; + } + $this->log->log("Successfully sent $sentbytes bytes.", Log::LEVEL_VERBOSE); + return $sentbytes; + } + + /** + * Reconnect XMPP Host + * @throws Exception + */ + public function doReconnect() + { + if (!$this->is_server) { + $this->log->log("Reconnecting ($this->reconnectTimeout)...", Log::LEVEL_WARNING); + $this->connect($this->reconnectTimeout, false, false); + $this->reset(); + $this->event('reconnect'); + } + } + + /** + * Connect to XMPP Host + * + * @param integer $timeout + * @param boolean $persistent + * @param boolean $sendinit + * @throws Exception + * @throws Exception + */ + public function connect($timeout = 30, $persistent = false, $sendinit = true) + { + $this->sent_disconnect = false; + $starttime = time(); + + do { + $this->disconnected = false; + $this->sent_disconnect = false; + if ($persistent) { + $conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT; + } else { + $conflag = STREAM_CLIENT_CONNECT; + } + $conntype = 'tcp'; + if ($this->use_ssl) $conntype = 'ssl'; + $this->log->log("Connecting to $conntype://{$this->host}:{$this->port}"); + $this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag); + if (!$this->socket) { + $this->log->log("Could not connect.", Log::LEVEL_ERROR); + $this->disconnected = true; + # Take it easy for a few seconds + sleep(min($timeout, 5)); + } + } while (!$this->socket && (time() - $starttime) < $timeout); + + if ($this->socket) { + stream_set_blocking($this->socket, 1); + if ($sendinit) $this->send($this->stream_start); + } else { + throw new Exception("Could not connect before timeout."); + } + } + + /** + * Reset connection + */ + public function reset() + { + $this->xml_depth = 0; + unset($this->xmlobj); + $this->xmlobj = array(); + $this->setupParser(); + if (!$this->is_server) { + $this->send($this->stream_start); + } + $this->been_reset = true; + } + + /** + * Event? + * + * @param string $name + * @param string $payload + */ + public function event($name, $payload = null) + { + $this->log->log("EVENT: $name", Log::LEVEL_DEBUG); + foreach ($this->eventhandlers as $handler) { + if ($name == $handler[0]) { + if ($handler[2] === null) { + $handler[2] = $this; + } + $handler[2]->{$handler[1]}($payload); + } + } + foreach ($this->until as $key => $until) { + if (is_array($until)) { + if (in_array($name, $until)) { + $this->until_payload[$key][] = array($name, $payload); + if (!isset($this->until_count[$key])) { + $this->until_count[$key] = 0; + } + $this->until_count[$key] += 1; + #$this->until[$key] = false; + } + } + } + } + + /** + * Process until a specified event or a timeout occurs + * + * @param string|array $event + * @param integer $timeout + * @return string + * @throws Exception + */ + public function processUntil($event, $timeout = -1) + { + $start = time(); + if (!is_array($event)) $event = array($event); + $this->until[] = $event; + end($this->until); + $event_key = key($this->until); + reset($this->until); + $this->until_count[$event_key] = 0; + while (!$this->disconnected and $this->until_count[$event_key] < 1 and (time() - $start < $timeout or $timeout == -1)) { + $this->__process(); + } + if (array_key_exists($event_key, $this->until_payload)) { + $payload = $this->until_payload[$event_key]; + unset($this->until_payload[$event_key]); + unset($this->until_count[$event_key]); + unset($this->until[$event_key]); + } else { + $payload = array(); + } + return $payload; + } + + /** + * Core reading tool + * 0 -> only read if data is immediately ready + * NULL -> wait forever and ever + * integer -> process for this amount of time + * @param int $maximum + * @return bool + * @throws Exception + */ + + private function __process($maximum = 5) + { + + $remaining = $maximum; + + do { + $starttime = (microtime(true) * 1000000); + $read = array($this->socket); + $write = array(); + $except = array(); + if (is_null($maximum)) { + $secs = NULL; + $usecs = NULL; + } else if ($maximum == 0) { + $secs = 0; + $usecs = 0; + } else { + $usecs = $remaining % 1000000; + $secs = floor(($remaining - $usecs) / 1000000); + } + $updated = @stream_select($read, $write, $except, $secs, $usecs); + if ($updated === false) { + $this->log->log("Error on stream_select()", Log::LEVEL_VERBOSE); + if ($this->reconnect) { + $this->doReconnect(); + } else { + fclose($this->socket); + $this->socket = NULL; + return false; + } + } else if ($updated > 0) { + # XXX: Is this big enough? + $buff = @fread($this->socket, 4096); + if (!$buff) { + if ($this->reconnect) { + $this->doReconnect(); + } else { + fclose($this->socket); + $this->socket = NULL; + return false; + } + } + $this->log->log("RECV: $buff", Log::LEVEL_VERBOSE); + xml_parse($this->parser, $buff, false); + } // Otherwise, + // $updated == 0 means no changes during timeout. + + $endtime = (microtime(true) * 1000000); + $time_past = $endtime - $starttime; + $remaining = $remaining - $time_past; + } while (is_null($maximum) || $remaining > 0); + return true; + } + + /** + * Return the log instance + * + * @return Log + */ + public function getLog() + { + return $this->log; + } + + /** + * Get next ID + * + * @return integer + */ + public function getId() + { + $this->lastid++; + return $this->lastid; + } + + /** + * Set SSL + * @param bool $use + */ + public function useSSL($use = true) + { + $this->use_ssl = $use; + } + + /** + * Add ID Handler + * + * @param integer $id + * @param string $pointer + * @param string $obj + */ + public function addIdHandler($id, $pointer, $obj = null) + { + $this->idhandlers[$id] = array($pointer, $obj); + } + + /** + * Add Handler + * + * @param string $name + * @param string $ns + * @param string $pointer + * @param string $obj + * @param integer $depth + */ + public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) + { + #TODO deprication warning + $this->nshandlers[] = array($name, $ns, $pointer, $obj, $depth); + } + + /** + * Add XPath Handler + * + * @param string $xpath + * @param string $pointer + * @param + */ + public function addXPathHandler($xpath, $pointer, $obj = null) + { + if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) { + $ns_tags = $regs[0]; + } else { + $ns_tags = array($xpath); + } + foreach ($ns_tags as $ns_tag) { + list($l, $r) = explode("}", $ns_tag); + if ($r != null) { + $xpart = array(substr($l, 1), $r); + } else { + $xpart = array(null, $l); + } + $xpath_array[] = $xpart; + } + $this->xpathhandlers[] = array($xpath_array, $pointer, $obj); + } + + /** + * Add Event Handler + * + * @param $name + * @param string $pointer + * @param string $obj + */ + public function addEventHandler($name, $pointer, $obj) + { + $this->eventhandlers[] = array($name, $pointer, $obj); + } + + public function setReconnectTimeout($timeout) + { + $this->reconnectTimeout = $timeout; + } + + /** + * Are we are disconnected? + * + * @return boolean + */ + public function isDisconnected() + { + return $this->disconnected; + } + + /** + * Process + * + */ + public function process() + { + $this->__process(NULL); + } + + /** + * Process until a timeout occurs + * + * @param integer $timeout + * @return string + * @throws Exception + */ + public function processTime($timeout = NULL) + { + if (is_null($timeout)) { + return $this->__process(NULL); + } else { + return $this->__process($timeout * 1000000); + } + } + + /** + * Obsolete? + * @param $socket + */ + public function Xapply_socket($socket) + { + $this->socket = $socket; + } + + /** + * XML start callback + * + * @param resource $parser + * @param string $name + * @param $attr + * @see xml_set_element_handler + */ + public function startXML($parser, $name, $attr) + { + if ($this->been_reset) { + $this->been_reset = false; + $this->xml_depth = 0; + } + $this->xml_depth++; + if (array_key_exists('XMLNS', $attr)) { + $this->current_ns[$this->xml_depth] = $attr['XMLNS']; + } else { + $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1]; + if (!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns; + } + $ns = $this->current_ns[$this->xml_depth]; + foreach ($attr as $key => $value) { + if (strstr($key, ":")) { + $key = explode(':', $key); + $key = $key[1]; + $this->ns_map[$key] = $value; + } + } + if (!strstr($name, ":") === false) { + $name = explode(':', $name); + $ns = $this->ns_map[$name[0]]; + $name = $name[1]; + } + $obj = new XMLObj($name, $ns, $attr); + if ($this->xml_depth > 1) { + $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj; + } + $this->xmlobj[$this->xml_depth] = $obj; + } + + /** + * XML end callback + * + * @param resource $parser + * @param string $name + * @throws Exception + * @see xml_set_element_handler + * + */ + public function endXML($parser, $name) + { + #$this->log->log("Ending $name", Log::LEVEL_DEBUG); + #print "$name\n"; + if ($this->been_reset) { + $this->been_reset = false; + $this->xml_depth = 0; + } + $this->xml_depth--; + if ($this->xml_depth == 1) { + #clean-up old objects + #$found = false; #FIXME This didn't appear to be in use --Gar + foreach ($this->xpathhandlers as $handler) { + if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) { + $searchxml = $this->xmlobj[2]; + $nstag = array_shift($handler[0]); + if (($nstag[0] == null or $searchxml->ns == $nstag[0]) and ($nstag[1] == "*" or $nstag[1] == $searchxml->name)) { + foreach ($handler[0] as $nstag) { + if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns = $nstag[0])) { + $searchxml = $searchxml->sub($nstag[1], $ns = $nstag[0]); + } else { + $searchxml = null; + break; + } + } + if ($searchxml !== null) { + if ($handler[2] === null) $handler[2] = $this; + $this->log->log("Calling {$handler[1]}", Log::LEVEL_DEBUG); + $handler[2]->{$handler[1]}($this->xmlobj[2]); + } + } + } + } + foreach ($this->nshandlers as $handler) { + if ($handler[4] != 1 and array_key_exists(2, $this->xmlobj) and $this->xmlobj[2]->hasSub($handler[0])) { + $searchxml = $this->xmlobj[2]->sub($handler[0]); + } elseif (is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) { + $searchxml = $this->xmlobj[2]; + } + if ($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) { + if ($handler[3] === null) $handler[3] = $this; + $this->log->log("Calling {$handler[2]}", Log::LEVEL_DEBUG); + $handler[3]->{$handler[2]}($this->xmlobj[2]); + } + } + foreach ($this->idhandlers as $id => $handler) { + if (array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) { + if ($handler[1] === null) $handler[1] = $this; + $handler[1]->{$handler[0]}($this->xmlobj[2]); + #id handlers are only used once + unset($this->idhandlers[$id]); + break; + } + } + if (is_array($this->xmlobj)) { + $this->xmlobj = array_slice($this->xmlobj, 0, 1); + if (isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMLObj) { + $this->xmlobj[0]->subs = null; + } + } + unset($this->xmlobj[2]); + } + if ($this->xml_depth == 0 and !$this->been_reset) { + if (!$this->disconnected) { + if (!$this->sent_disconnect) { + $this->send($this->stream_end); + } + $this->disconnected = true; + $this->sent_disconnect = true; + fclose($this->socket); + if ($this->reconnect) { + $this->doReconnect(); + } + } + $this->event('end_stream'); + } + } + + /** + * XML character callback + * @param resource $parser + * @param string $data + * @see xml_set_character_data_handler + * + */ + public function charXML($parser, $data) + { + if (array_key_exists($this->xml_depth, $this->xmlobj)) { + $this->xmlobj[$this->xml_depth]->data .= $data; + } + } + + /** + * Read from socket + */ + public function read() + { + $buff = @fread($this->socket, 1024); + if (!$buff) { + if ($this->reconnect) { + $this->doReconnect(); + } else { + fclose($this->socket); + return false; + } + } + $this->log->log("RECV: $buff", Log::LEVEL_VERBOSE); + xml_parse($this->parser, $buff, false); + } + + public function time() + { + list($usec, $sec) = explode(" ", microtime()); + return (float)$sec + (float)$usec; + } + + public function readyToProcess() + { + $read = array($this->socket); + $write = array(); + $except = array(); + $updated = @stream_select($read, $write, $except, 0); + return (($updated !== false) && ($updated > 0)); + } } diff --git a/plugins/Xmpp/extlib/XMPPHP/XMPP.php b/plugins/Xmpp/extlib/XMPPHP/XMPP.php index c0f8963396..55b58db0fd 100644 --- a/plugins/Xmpp/extlib/XMPPHP/XMPP.php +++ b/plugins/Xmpp/extlib/XMPPHP/XMPP.php @@ -1,432 +1,477 @@ - * @author Stephan Wentz - * @author Michael Garvin + * @category xmpphp + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @author Alexander Birkner (https://github.com/BirknerAlex) + * @author zorn-v (https://github.com/zorn-v/xmpphp/) + * @author GNU social * @copyright 2008 Nathanael C. Fritz */ +namespace XMPPHP; + /** XMPPHP_XMLStream */ -require_once dirname(__FILE__) . "/XMLStream.php"; -require_once dirname(__FILE__) . "/Roster.php"; +require_once __DIR__ . DIRECTORY_SEPARATOR . 'XMLStream.php'; +require_once __DIR__ . DIRECTORY_SEPARATOR . 'Roster.php'; + /** - * XMPPHP Main Class - * - * @category xmpphp - * @package XMPPHP - * @author Nathanael C. Fritz - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - * @version $Id$ + * XMPPHP XMPP + * + * @package XMPPHP + * @author Nathanael C. Fritz + * @author Stephan Wentz + * @author Michael Garvin + * @copyright 2008 Nathanael C. Fritz + * @version $Id$ */ -class XMPPHP_XMPP extends XMPPHP_XMLStream { - /** - * @var string - */ - public $server; - - /** - * @var string - */ - public $user; - - /** - * @var string - */ - protected $password; - - /** - * @var string - */ - protected $resource; - - /** - * @var string - */ - protected $fulljid; - - /** - * @var string - */ - protected $basejid; - - /** - * @var boolean - */ - protected $authed = false; - protected $session_started = false; - - /** - * @var boolean - */ - protected $auto_subscribe = false; - - /** - * @var boolean - */ - protected $use_encryption = true; - - /** - * @var boolean - */ - public $track_presence = true; - - /** - * @var object - */ - public $roster; - - /** - * Constructor - * - * @param string $host - * @param integer $port - * @param string $user - * @param string $password - * @param string $resource - * @param string $server - * @param boolean $printlog - * @param string $loglevel - */ - public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) { - parent::__construct($host, $port, $printlog, $loglevel); - - $this->user = $user; - $this->password = $password; - $this->resource = $resource; - if(!$server) $server = $host; - $this->basejid = $this->user . '@' . $this->host; - - $this->roster = new Roster(); - $this->track_presence = true; - - $this->stream_start = ''; - $this->stream_end = ''; - $this->default_ns = 'jabber:client'; - - $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler'); - $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler'); - $this->addXPathHandler('{jabber:client}message', 'message_handler'); - $this->addXPathHandler('{jabber:client}presence', 'presence_handler'); - $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler'); - } - - /** - * Turn encryption on/ff - * - * @param boolean $useEncryption - */ - public function useEncryption($useEncryption = true) { - $this->use_encryption = $useEncryption; - } - - /** - * Turn on auto-authorization of subscription requests. - * - * @param boolean $autoSubscribe - */ - public function autoSubscribe($autoSubscribe = true) { - $this->auto_subscribe = $autoSubscribe; - } - - /** - * Send XMPP Message - * - * @param string $to - * @param string $body - * @param string $type - * @param string $subject - */ - public function message($to, $body, $type = 'chat', $subject = null, $payload = null) { - if(is_null($type)) - { - $type = 'chat'; - } - - $to = htmlspecialchars($to); - $body = htmlspecialchars($body); - $subject = htmlspecialchars($subject); - - $out = "fulljid}\" to=\"$to\" type='$type'>"; - if($subject) $out .= "$subject"; - $out .= "$body"; - if($payload) $out .= $payload; - $out .= ""; - - $this->send($out); - } - - /** - * Set Presence - * - * @param string $status - * @param string $show - * @param string $to - */ - public function presence($status = null, $show = 'available', $to = null, $type='available', $priority=0) { - if($type == 'available') $type = ''; - $to = htmlspecialchars($to); - $status = htmlspecialchars($status); - if($show == 'unavailable') $type = 'unavailable'; - - $out = "send($out); - } - /** - * Send Auth request - * - * @param string $jid - */ - public function subscribe($jid) { - $this->send(""); - #$this->send(""); - } - - /** - * Message handler - * - * @param string $xml - */ - public function message_handler($xml) { - if(isset($xml->attrs['type'])) { - $payload['type'] = $xml->attrs['type']; - } else { - $payload['type'] = 'chat'; - } - $payload['from'] = $xml->attrs['from']; - $payload['body'] = $xml->sub('body')->data; - $payload['xml'] = $xml; - $this->log->log("Message: {$xml->sub('body')->data}", XMPPHP_Log::LEVEL_DEBUG); - $this->event('message', $payload); - } - - /** - * Presence handler - * - * @param string $xml - */ - public function presence_handler($xml) { - $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available'; - $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type']; - $payload['from'] = $xml->attrs['from']; - $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : ''; - $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0; - $payload['xml'] = $xml; - if($this->track_presence) { - $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']); - } - $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", XMPPHP_Log::LEVEL_DEBUG); - if(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') { - if($this->auto_subscribe) { - $this->send(""); - $this->send(""); - } - $this->event('subscription_requested', $payload); - } elseif(array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') { - $this->event('subscription_accepted', $payload); - } else { - $this->event('presence', $payload); - } - } - - /** - * Features handler - * - * @param string $xml - */ - protected function features_handler($xml) { - if($xml->hasSub('starttls') and $this->use_encryption) { - $this->send(""); - } elseif($xml->hasSub('bind') and $this->authed) { - $id = $this->getId(); - $this->addIdHandler($id, 'resource_bind_handler'); - $this->send("{$this->resource}"); - } else { - $this->log->log("Attempting Auth..."); - if ($this->password) { - $this->send("" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . ""); - } else { - $this->send(""); - } - } - } - - /** - * SASL success handler - * - * @param string $xml - */ - protected function sasl_success_handler($xml) { - $this->log->log("Auth success!"); - $this->authed = true; - $this->reset(); - } - - /** - * SASL feature handler - * - * @param string $xml - */ - protected function sasl_failure_handler($xml) { - $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR); - $this->disconnect(); - - throw new XMPPHP_Exception('Auth failed!'); - } - - /** - * Resource bind handler - * - * @param string $xml - */ - protected function resource_bind_handler($xml) { - if($xml->attrs['type'] == 'result') { - $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data); - $this->fulljid = $xml->sub('bind')->sub('jid')->data; - $jidarray = explode('/',$this->fulljid); - $this->jid = $jidarray[0]; - } - $id = $this->getId(); - $this->addIdHandler($id, 'session_start_handler'); - $this->send(""); - } - - /** - * Retrieves the roster - * - */ - public function getRoster() { - $id = $this->getID(); - $this->send(""); - } - - /** - * Roster iq handler - * Gets all packets matching XPath "iq/{jabber:iq:roster}query' - * - * @param string $xml - */ - protected function roster_iq_handler($xml) { - $status = "result"; - $xmlroster = $xml->sub('query'); - foreach($xmlroster->subs as $item) { - $groups = array(); - if ($item->name == 'item') { - $jid = $item->attrs['jid']; //REQUIRED - $name = $item->attrs['name']; //MAY - $subscription = $item->attrs['subscription']; - foreach($item->subs as $subitem) { - if ($subitem->name == 'group') { - $groups[] = $subitem->data; - } - } - $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen - } else { - $status = "error"; - } - } - if ($status == "result") { //No errors, add contacts - foreach($contacts as $contact) { - $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]); - } - } - if ($xml->attrs['type'] == 'set') { - $this->send("attrs['id']}\" to=\"{$xml->attrs['from']}\" />"); - } - } - - /** - * Session start handler - * - * @param string $xml - */ - protected function session_start_handler($xml) { - $this->log->log("Session started"); - $this->session_started = true; - $this->event('session_start'); - } - - /** - * TLS proceed handler - * - * @param string $xml - */ - protected function tls_proceed_handler($xml) { - $this->log->log("Starting TLS encryption"); - stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); - $this->reset(); - } - - /** - * Retrieves the vcard - * - */ - public function getVCard($jid = Null) { - $id = $this->getID(); - $this->addIdHandler($id, 'vcard_get_handler'); - if($jid) { - $this->send(""); - } else { - $this->send(""); - } - } - - /** - * VCard retrieval handler - * - * @param XML Object $xml - */ - protected function vcard_get_handler($xml) { - $vcard_array = array(); - $vcard = $xml->sub('vcard'); - // go through all of the sub elements and add them to the vcard array - foreach ($vcard->subs as $sub) { - if ($sub->subs) { - $vcard_array[$sub->name] = array(); - foreach ($sub->subs as $sub_child) { - $vcard_array[$sub->name][$sub_child->name] = $sub_child->data; - } - } else { - $vcard_array[$sub->name] = $sub->data; - } - } - $vcard_array['from'] = $xml->attrs['from']; - $this->event('vcard', $vcard_array); - } +class XMPP extends XMLStream +{ + /** + * @var string + */ + public $server; + + /** + * @var string + */ + public $user; + /** + * @var boolean + */ + public $track_presence = true; + /** + * @var object + */ + public $roster; + /** + * @var string + */ + protected $password; + /** + * @var string + */ + protected $resource; + /** + * @var string + */ + protected $fulljid; + /** + * @var string + */ + protected $basejid; + /** + * @var boolean + */ + protected $authed = false; + protected $session_started = false; + /** + * @var boolean + */ + protected $auto_subscribe = false; + /** + * @var boolean + */ + protected $use_encryption = true; + + /** + * Constructor + * + * @param string $host + * @param integer $port + * @param string $user + * @param string $password + * @param string $resource + * @param string $server + * @param boolean $printlog + * @param string $loglevel + */ + public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) + { + parent::__construct($host, $port, $printlog, $loglevel); + + $this->user = $user; + $this->password = $password; + $this->resource = $resource; + if (!$server) { + $server = $host; + } + $this->server = $server; + $this->basejid = $this->user . '@' . $this->host; + + $this->roster = new Roster(); + $this->track_presence = true; + + $this->stream_start = ''; + $this->stream_end = ''; + $this->default_ns = 'jabber:client'; + + $this->addXPathHandler('{http://etherx.jabber.org/streams}features', 'features_handler'); + $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}success', 'sasl_success_handler'); + $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-sasl}failure', 'sasl_failure_handler'); + $this->addXPathHandler('{urn:ietf:params:xml:ns:xmpp-tls}proceed', 'tls_proceed_handler'); + $this->addXPathHandler('{jabber:client}message', 'message_handler'); + $this->addXPathHandler('{jabber:client}presence', 'presence_handler'); + $this->addXPathHandler('iq/{jabber:iq:roster}query', 'roster_iq_handler'); + } + + /** + * Turn encryption on/ff + * + * @param boolean $useEncryption + */ + public function useEncryption($useEncryption = true) + { + $this->use_encryption = $useEncryption; + } + + /** + * Turn on auto-authorization of subscription requests. + * + * @param boolean $autoSubscribe + */ + public function autoSubscribe($autoSubscribe = true) + { + $this->auto_subscribe = $autoSubscribe; + } + + /** + * Send XMPP Message + * + * @param string $to + * @param string $body + * @param string $type + * @param string $subject + * @param null $payload + * @throws Exception + */ + public function message($to, $body, $type = 'chat', $subject = null, $payload = null) + { + if ($this->disconnected) { + throw new Exception('You need to connect first'); + } + + if (empty($type)) { + $type = 'chat'; + } + + $to = htmlspecialchars($to); + $body = htmlspecialchars($body); + $subject = htmlspecialchars($subject); + $subject = ($subject) ? '' . $subject . '' : ''; + $payload = ($payload) ? $payload : ''; + $sprintf = '%s%s%s'; + $output = sprintf($sprintf, $this->fulljid, $to, $type, $subject, $body, $payload); + $this->send($output); + } + + /** + * Set Presence + * + * @param string $status + * @param string $show + * @param string $to + * @param string $type + * @param null $priority + * @throws Exception + */ + public function presence($status = null, $show = 'available', $to = null, $type = 'available', $priority = null) + { + if ($this->disconnected) { + throw new Exception('You need to connect first'); + } + + if ($type == 'available') { + $type = ''; + } + $to = htmlspecialchars($to); + $status = htmlspecialchars($status); + if ($show == 'unavailable') { + $type = 'unavailable'; + } + + $out = "send($out); + } + + /** + * Send Auth request + * + * @param string $jid + */ + public function subscribe($jid) + { + $this->send(""); + #$this->send(""); + } + + /** + * Message handler + * + * @param string $xml + */ + public function message_handler($xml) + { + if (isset($xml->attrs['type'])) { + $payload['type'] = $xml->attrs['type']; + } else { + $payload['type'] = 'chat'; + } + $body = $xml->sub('body'); + $payload['from'] = $xml->attrs['from']; + $payload['body'] = is_object($body) ? $body->data : false; // $xml->sub('body')->data; + $payload['xml'] = $xml; + $this->log->log("Message: {$payload['body']}", Log::LEVEL_DEBUG); + $this->event('message', $payload); + } + + /** + * Presence handler + * + * @param string $xml + */ + public function presence_handler($xml) + { + $payload['type'] = (isset($xml->attrs['type'])) ? $xml->attrs['type'] : 'available'; + $payload['show'] = (isset($xml->sub('show')->data)) ? $xml->sub('show')->data : $payload['type']; + $payload['from'] = $xml->attrs['from']; + $payload['status'] = (isset($xml->sub('status')->data)) ? $xml->sub('status')->data : ''; + $payload['priority'] = (isset($xml->sub('priority')->data)) ? intval($xml->sub('priority')->data) : 0; + $payload['xml'] = $xml; + if ($this->track_presence) { + $this->roster->setPresence($payload['from'], $payload['priority'], $payload['show'], $payload['status']); + } + $this->log->log("Presence: {$payload['from']} [{$payload['show']}] {$payload['status']}", Log::LEVEL_DEBUG); + if (array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribe') { + if ($this->auto_subscribe) { + $this->send(""); + $this->send(""); + } + $this->event('subscription_requested', $payload); + } elseif (array_key_exists('type', $xml->attrs) and $xml->attrs['type'] == 'subscribed') { + $this->event('subscription_accepted', $payload); + } else { + $this->event('presence', $payload); + } + } + + /** + * Retrieves the roster + * + */ + public function getRoster() + { + $id = $this->getID(); + $this->send(""); + } + + /** + * Retrieves the vcard + * @param null $jid + */ + public function getVCard($jid = null) + { + $id = $this->getID(); + $this->addIdHandler($id, 'vcard_get_handler'); + if ($jid) { + $this->send(""); + } else { + $this->send(""); + } + } + + /** + * Features handler + * + * @param string $xml + */ + protected function features_handler($xml) + { + if ($xml->hasSub('starttls') and $this->use_encryption) { + $this->send(""); + } elseif ($xml->hasSub('bind') and $this->authed) { + $id = $this->getId(); + $this->addIdHandler($id, 'resource_bind_handler'); + $this->send("{$this->resource}"); + } else { + $this->log->log("Attempting Auth..."); + if ($this->password) { + $this->send("" . base64_encode("\x00" . $this->user . "\x00" . $this->password) . ""); + } else { + $this->send(""); + } + } + } + + /** + * SASL success handler + * + * @param string $xml + */ + protected function sasl_success_handler($xml) + { + $this->log->log("Auth success!"); + $this->authed = true; + $this->reset(); + } + + /** + * SASL feature handler + * + * @param string $xml + * @throws Exception + */ + protected function sasl_failure_handler($xml) + { + $this->log->log("Auth failed!", Log::LEVEL_ERROR); + $this->disconnect(); + + throw new Exception('Auth failed!'); + } + + /** + * Resource bind handler + * + * @param string $xml + */ + protected function resource_bind_handler($xml) + { + if ($xml->attrs['type'] == 'result') { + $this->log->log("Bound to " . $xml->sub('bind')->sub('jid')->data); + $this->fulljid = $xml->sub('bind')->sub('jid')->data; + $jidarray = explode('/', $this->fulljid); + $this->jid = $jidarray[0]; + } + $id = $this->getId(); + $this->addIdHandler($id, 'session_start_handler'); + $this->send(""); + } + + /** + * Roster iq handler + * Gets all packets matching XPath "iq/{jabber:iq:roster}query' + * + * @param string $xml + */ + protected function roster_iq_handler($xml) + { + $status = "result"; + $xmlroster = $xml->sub('query'); + foreach ($xmlroster->subs as $item) { + $groups = array(); + if ($item->name == 'item') { + $jid = $item->attrs['jid']; //REQUIRED + $name = $item->attrs['name']; //MAY + $subscription = $item->attrs['subscription']; + foreach ($item->subs as $subitem) { + if ($subitem->name == 'group') { + $groups[] = $subitem->data; + } + } + $contacts[] = array($jid, $subscription, $name, $groups); //Store for action if no errors happen + } else { + $status = "error"; + } + } + if ($status == "result") { //No errors, add contacts + foreach ($contacts as $contact) { + $this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]); + } + } + if ($xml->attrs['type'] == 'set') { + $this->send("attrs['id']}\" to=\"{$xml->attrs['from']}\" />"); + } + } + + /** + * Session start handler + * + * @param string $xml + */ + protected function session_start_handler($xml) + { + $this->log->log("Session started"); + $this->session_started = true; + $this->event('session_start'); + } + + /** + * TLS proceed handler + * + * @param string $xml + */ + protected function tls_proceed_handler($xml) + { + $this->log->log("Starting TLS encryption"); + stream_socket_enable_crypto($this->socket, true, STREAM_CRYPTO_METHOD_SSLv23_CLIENT); + $this->reset(); + } + + /** + * VCard retrieval handler + * + * @param XMLObj $xml + */ + protected function vcard_get_handler($xml) + { + $vcard_array = array(); + $vcard = $xml->sub('vcard'); + // go through all of the sub elements and add them to the vcard array + foreach ($vcard->subs as $sub) { + if ($sub->subs) { + $vcard_array[$sub->name] = array(); + foreach ($sub->subs as $sub_child) { + $vcard_array[$sub->name][$sub_child->name] = $sub_child->data; + } + } else { + $vcard_array[$sub->name] = $sub->data; + } + } + $vcard_array['from'] = $xml->attrs['from']; + $this->event('vcard', $vcard_array); + } } diff --git a/plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php b/plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php deleted file mode 100644 index 43f56b1546..0000000000 --- a/plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php +++ /dev/null @@ -1,114 +0,0 @@ - - * @author Stephan Wentz - * @author Michael Garvin - * @copyright 2008 Nathanael C. Fritz - */ - -/** XMPPHP_XMPP - * - * This file is unnecessary unless you need to connect to older, non-XMPP-compliant servers like Dreamhost's. - * In this case, use instead of XMPPHP_XMPP, otherwise feel free to delete it. - * The old Jabber protocol wasn't standardized, so use at your own risk. - * - */ -require_once "XMPP.php"; - - class XMPPHP_XMPPOld extends XMPPHP_XMPP { - /** - * - * @var string - */ - protected $session_id; - - public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) { - parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); - if(!$server) $server = $host; - $this->stream_start = ''; - $this->fulljid = "{$user}@{$server}/{$resource}"; - } - - /** - * Override XMLStream's startXML - * - * @param parser $parser - * @param string $name - * @param array $attr - */ - public function startXML($parser, $name, $attr) { - if($this->xml_depth == 0) { - $this->session_id = $attr['ID']; - $this->authenticate(); - } - parent::startXML($parser, $name, $attr); - } - - /** - * Send Authenticate Info Request - * - */ - public function authenticate() { - $id = $this->getId(); - $this->addidhandler($id, 'authfieldshandler'); - $this->send("{$this->user}"); - } - - /** - * Retrieve auth fields and send auth attempt - * - * @param XMLObj $xml - */ - public function authFieldsHandler($xml) { - $id = $this->getId(); - $this->addidhandler($id, 'oldAuthResultHandler'); - if($xml->sub('query')->hasSub('digest')) { - $hash = sha1($this->session_id . $this->password); - print "{$this->session_id} {$this->password}\n"; - $out = "{$this->user}{$hash}{$this->resource}"; - } else { - $out = "{$this->user}{$this->password}{$this->resource}"; - } - $this->send($out); - - } - - /** - * Determine authenticated or failure - * - * @param XMLObj $xml - */ - public function oldAuthResultHandler($xml) { - if($xml->attrs['type'] != 'result') { - $this->log->log("Auth failed!", XMPPHP_Log::LEVEL_ERROR); - $this->disconnect(); - throw new XMPPHP_Exception('Auth failed!'); - } else { - $this->log->log("Session started"); - $this->event('session_start'); - } - } - } - - -?> diff --git a/plugins/Xmpp/lib/queuedxmpp.php b/plugins/Xmpp/lib/queuedxmpp.php index 5a3c464ef4..d0b93e0310 100644 --- a/plugins/Xmpp/lib/queuedxmpp.php +++ b/plugins/Xmpp/lib/queuedxmpp.php @@ -4,7 +4,7 @@ * * Queue-mediated proxy class for outgoing XMPP messages. * - * PHP version 5 + * PHP version 7 * * LICENCE: This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -31,28 +31,32 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -class QueuedXMPP extends XMPPHP_XMPP +require_once dirname(__DIR__) . '/extlib/XMPPHP/XMPP.php'; + +use XMPPHP\XMPP; + +class QueuedXMPP extends XMPP { /** * Reference to the XmppPlugin object we're hooked up to. */ public $plugin; - /** - * Constructor - * + /** + * Constructor + * * @param XmppPlugin $plugin - * @param string $host - * @param integer $port - * @param string $user - * @param string $password - * @param string $resource - * @param string $server - * @param boolean $printlog - * @param string $loglevel - */ - public function __construct($plugin, $host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) - { + * @param string $host + * @param integer $port + * @param string $user + * @param string $password + * @param string $resource + * @param string $server + * @param boolean $printlog + * @param string $loglevel + */ + public function __construct($plugin, $host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) + { $this->plugin = $plugin; parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); @@ -76,15 +80,21 @@ class QueuedXMPP extends XMPPHP_XMPP * to a real XMPP connection. * * @param string $msg + * @param null $timeout */ - public function send($msg, $timeout=NULL) + public function send($msg, $timeout = NULL) { - $this->plugin->enqueueOutgoingRaw($msg); + @$this->plugin->enqueueOutgoingRaw($msg); } //@{ + /** * Stream i/o functions disabled; only do output + * @param int $timeout + * @param bool $persistent + * @param bool $sendinit + * @throws Exception */ public function connect($timeout = 30, $persistent = false, $sendinit = true) { @@ -104,7 +114,7 @@ class QueuedXMPP extends XMPPHP_XMPP throw new Exception('Cannot read stream from fake XMPP.'); } - public function processUntil($event, $timeout=-1) + public function processUntil($event, $timeout = -1) { // No i18n needed. Test message. throw new Exception('Cannot read stream from fake XMPP.'); diff --git a/plugins/Xmpp/lib/sharingxmpp.php b/plugins/Xmpp/lib/sharingxmpp.php index 99e1978a37..1992320bc3 100644 --- a/plugins/Xmpp/lib/sharingxmpp.php +++ b/plugins/Xmpp/lib/sharingxmpp.php @@ -5,7 +5,7 @@ * * Send and receive notices using the Jabber network * - * PHP version 5 + * PHP version 7 * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by @@ -34,7 +34,11 @@ if (!defined('STATUSNET')) { exit(1); } -class SharingXMPP extends XMPPHP_XMPP +require_once dirname(__DIR__) . '/extlib/XMPPHP/XMPP.php'; + +use XMPPHP\XMPP; + +class SharingXMPP extends XMPP { function getSocket() { diff --git a/plugins/Xmpp/lib/xmppmanager.php b/plugins/Xmpp/lib/xmppmanager.php index 44e04a4ae7..e2ea57e7ec 100644 --- a/plugins/Xmpp/lib/xmppmanager.php +++ b/plugins/Xmpp/lib/xmppmanager.php @@ -17,7 +17,11 @@ * along with this program. If not, see . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +use XMPPHP\Log; /** * XMPP background connection manager for XMPP-using queue handlers, @@ -31,26 +35,84 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } */ class XmppManager extends ImManager { + const PING_INTERVAL = 120; + public $conn = null; protected $lastping = null; protected $pingid = null; - public $conn = null; - - const PING_INTERVAL = 120; - /** * Initialize connection to server. + * @param $master * @return boolean true on success */ public function start($master) { - if(parent::start($master)) - { + if (parent::start($master)) { $this->connect(); return true; - }else{ + } else { + return false; + } + } + + function connect() + { + if (!$this->conn || $this->conn->isDisconnected()) { + $resource = 'queue' . posix_getpid(); + $this->conn = new SharingXMPP($this->plugin->host ? + $this->plugin->host : + $this->plugin->server, + $this->plugin->port, + $this->plugin->user, + $this->plugin->password, + $this->plugin->resource, + $this->plugin->server, + $this->plugin->debug ? + true : false, + $this->plugin->debug ? + Log::LEVEL_VERBOSE : null + ); + + if (!$this->conn) { + return false; + } + $this->conn->addEventHandler('message', 'handle_xmpp_message', $this); + $this->conn->addEventHandler('reconnect', 'handle_xmpp_reconnect', $this); + $this->conn->setReconnectTimeout(600); + + $this->conn->autoSubscribe(); + $this->conn->useEncryption($this->plugin->encryption); + + $this->conn->connect(true); + + $this->conn->processUntil('session_start'); + // TRANS: Presence announcement for XMPP. + $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); + } + return $this->conn; + } + + /** + * sends a presence stanza on the XMPP network + * + * @param string $status current status, free-form string + * @param string $show structured status value + * @param string $to recipient of presence, null for general + * @param string $type type of status message, related to $show + * @param int $priority priority of the presence + * + * @return boolean success value + */ + + function send_presence($status, $show = 'available', $to = null, + $type = 'available', $priority = null) + { + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { return false; } + $this->conn->presence($status, $show, $to, $type, $priority); + return true; } function send_raw_message($data) @@ -80,14 +142,9 @@ class XmppManager extends ImManager public function handleInput($socket) { // Process the queue for as long as needed - try { - common_log(LOG_DEBUG, "Servicing the XMPP queue."); - $this->stats('xmpp_process'); - $this->conn->processTime(0); - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); - } + common_log(LOG_DEBUG, "Servicing the XMPP queue."); + $this->stats('xmpp_process'); + $this->conn->processTime(0); } /** @@ -99,9 +156,9 @@ class XmppManager extends ImManager public function getSockets() { $this->connect(); - if($this->conn){ + if ($this->conn) { return array($this->conn->getSocket()); - }else{ + } else { return array(); } } @@ -112,63 +169,17 @@ class XmppManager extends ImManager * * Side effect: kills process on exception from XMPP library. * + * @param int $timeout * @todo FIXME: non-dying error handling */ - public function idle($timeout=0) + public function idle($timeout = 0) { $now = time(); if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) { - try { - $this->send_ping(); - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); - } + $this->send_ping(); } } - function connect() - { - if (!$this->conn || $this->conn->isDisconnected()) { - $resource = 'queue' . posix_getpid(); - $this->conn = new SharingXMPP($this->plugin->host ? - $this->plugin->host : - $this->plugin->server, - $this->plugin->port, - $this->plugin->user, - $this->plugin->password, - $this->plugin->resource, - $this->plugin->server, - $this->plugin->debug ? - true : false, - $this->plugin->debug ? - XMPPHP_Log::LEVEL_VERBOSE : null - ); - - if (!$this->conn) { - return false; - } - $this->conn->addEventHandler('message', 'handle_xmpp_message', $this); - $this->conn->addEventHandler('reconnect', 'handle_xmpp_reconnect', $this); - $this->conn->setReconnectTimeout(600); - - $this->conn->autoSubscribe(); - $this->conn->useEncryption($this->plugin->encryption); - - try { - $this->conn->connect(true); // true = persistent connection - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, $e->getMessage()); - return false; - } - - $this->conn->processUntil('session_start'); - // TRANS: Presence announcement for XMPP. - $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); - } - return $this->conn; - } - function send_ping() { $this->connect(); @@ -183,7 +194,7 @@ class XmppManager extends ImManager } common_log(LOG_DEBUG, "Sending ping #{$this->pingid}"); - $this->conn->send(""); + $this->conn->send(""); $this->lastping = $now; return true; } @@ -207,35 +218,12 @@ class XmppManager extends ImManager $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); } - /** - * sends a presence stanza on the XMPP network - * - * @param string $status current status, free-form string - * @param string $show structured status value - * @param string $to recipient of presence, null for general - * @param string $type type of status message, related to $show - * @param int $priority priority of the presence - * - * @return boolean success value - */ - - function send_presence($status, $show='available', $to=null, - $type = 'available', $priority=null) - { - $this->connect(); - if (!$this->conn || $this->conn->isDisconnected()) { - return false; - } - $this->conn->presence($status, $show, $to, $type, $priority); - return true; - } - /** * sends a "special" presence stanza on the XMPP network * - * @param string $type Type of presence - * @param string $to JID to send presence to - * @param string $show show value for presence + * @param string $type Type of presence + * @param string $to JID to send presence to + * @param string $show show value for presence * @param string $status status value for presence * * @return boolean success flag @@ -243,7 +231,7 @@ class XmppManager extends ImManager * @see send_presence() */ - function special_presence($type, $to=null, $show=null, $status=null) + function special_presence($type, $to = null, $show = null, $status = null) { // @todo FIXME: why use this instead of send_presence()? $this->connect(); @@ -251,7 +239,7 @@ class XmppManager extends ImManager return false; } - $to = htmlspecialchars($to); + $to = htmlspecialchars($to); $status = htmlspecialchars($status); $out = "