Compare commits

...

4 Commits

Author SHA1 Message Date
Alexei Sorokin 37f69546e8 Fix up Roster::addContact 2020-09-13 23:00:21 +03:00
Alexei Sorokin 7cbdc99d08 Allow XMLStream handlers to accept all proper callables
Additionally, change the type of XMLStream::getId() to string as ids in XMPP
can take all shapes and sizes.

To override the compatible behaviour and pass a simple procedure as a callable
either use an anonymous function or pass "false" as $obj.
2020-09-13 22:19:15 +03:00
Alexei Sorokin eb648db476 Fix XMLObj::sub argument order
XMLStream::endXML assumes $ns to be second.
2020-09-13 18:47:26 +03:00
Swappp a5346a47dc Fix XPath parser 2020-09-13 18:21:49 +03:00
4 changed files with 136 additions and 86 deletions

View File

@ -79,14 +79,14 @@ class Roster
* Retrieve contact via jid
*
* @param string $jid
* @return mixed|void
* @return array|null
*/
public function getContact(string $jid)
public function getContact(string $jid): ?array
{
if ($this->isContact($jid)) {
return $this->roster_array[$jid]['contact'];
}
return;
return null;
}
/**
@ -128,12 +128,16 @@ class Roster
* Add given contact to roster
*
* @param string $jid
* @param string $subscription
* @param string $subscription (optional)
* @param string $name (optional)
* @param array $groups (optional)
*/
public function addContact(string $jid, string $subscription, string $name = '', array $groups = []): void
{
public function addContact(
string $jid,
string $subscription = 'none',
string $name = '',
array $groups = []
): void {
$contact = ['jid' => $jid, 'subscription' => $subscription, 'name' => $name, 'groups' => $groups];
if ($this->isContact($jid)) {
$this->roster_array[$jid]['contact'] = $contact;

View File

@ -158,15 +158,15 @@ class XMLObj
* Return a sub
*
* @param string $name
* @param array|null $attrs (optional)
* @param string|null $ns (optional)
* @param array|null $attrs (optional)
* @return mixed
*/
public function sub(string $name, ?array $attrs = null, ?string $ns = null)
public function sub(string $name, ?string $ns = null, ?array $attrs = null)
{
#TODO attrs is ignored
// @todo attrs is ignored
foreach ($this->subs as $sub) {
if ($sub->name == $name and ($ns == null or $sub->ns == $ns)) {
if ($sub->name === $name && (is_null($ns) || $sub->ns === $ns)) {
return $sub;
}
}

View File

@ -374,13 +374,10 @@ class XMLStream
*/
public function event(string $name, ?array $payload = null): void
{
$this->log->log("EVENT: $name", Log::LEVEL_DEBUG);
$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);
if ($handler[0] === $name) {
call_user_func_array($handler[1], [&$payload]);
}
}
foreach ($this->until as $key => $until) {
@ -505,12 +502,12 @@ class XMLStream
/**
* Get next ID
*
* @return int
* @return string
*/
public function getId(): int
public function getId(): string
{
$this->lastid++;
return $this->lastid;
++$this->lastid;
return (string) $this->lastid;
}
/**
@ -522,16 +519,45 @@ class XMLStream
$this->use_ssl = $use;
}
/**
* Compose a proper callable if given legacy syntax
*
* @param callable|string $pointer
* @param object|null|bool $obj
* @return callable
* @throws InvalidArgumentException
*/
protected function ensureHandler($pointer, $obj = false): callable
{
$handler = $pointer;
if (is_string($pointer)) {
if (is_object($obj)) {
$handler = [$obj, $pointer];
} elseif (is_null($obj)) {
// Questionable behaviour for backwards compatibility
$handler = [$this, $pointer];
}
}
if (!is_callable($handler)) {
throw new \InvalidArgumentException(
'Cannot compose a proper callable'
);
}
return $handler;
}
/**
* Add ID Handler
*
* @param int $id
* @param string $pointer
* @param string|null $obj
* @param callable|string $pointer
* @param object|bool|null $obj
*/
public function addIdHandler(int $id, string $pointer, ?string $obj = null): void
public function addIdHandler(string $id, $pointer, $obj = null): void
{
$this->idhandlers[$id] = [$pointer, $obj];
$this->idhandlers[$id] = [$this->ensureHandler($pointer, $obj)];
}
/**
@ -540,52 +566,49 @@ class XMLStream
* @param string $name
* @param string $ns
* @param string $pointer
* @param string|null $obj
* @param object|bool|null $obj
* @param int $depth
*
* public function addHandler(string $name, string $ns, string $pointer, ?string $obj = null, int $depth = 1): void
* public function addHandler(string $name, string $ns, $pointer, $obj = null, int $depth = 1): void
* {
* #TODO deprication warning
* $this->nshandlers[] = [$name, $ns, $pointer, $obj, $depth];
* // TODO deprecation warning
* $this->nshandlers[] = [$name, $ns, $this->ensureHandler($pointer, $obj), $depth];
* }*/
/**
* Add XPath Handler
*
* @param string $xpath
* @param string $pointer
* @param string|null $obj
* @param callable|string $pointer
* @param object|bool|null $obj
*/
public function addXPathHandler(string $xpath, string $pointer, ?string $obj = null): void
public function addXPathHandler(string $xpath, $pointer, $obj = null): void
{
if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) {
$ns_tags = $regs[0];
if (preg_match_all('/\/?(\{[^\}]+\})?[^\/]+/', $xpath, $regs)) {
$tag = $regs[0];
} else {
$ns_tags = [$xpath];
$tag = [$xpath];
}
$xpath_array = [];
foreach ($ns_tags as $ns_tag) {
list($l, $r) = explode("}", $ns_tag);
if ($r != null) {
$xpart = [substr($l, 1), $r];
} else {
$xpart = [null, $l];
}
$xpath_array[] = $xpart;
foreach ($tag as $t) {
$t = ltrim($t, '/');
preg_match('/(\{([^\}]+)\})?(.*)/', $t, $regs);
$xpath_array[] = [$regs[2], $regs[3]];
}
$this->xpathhandlers[] = [$xpath_array, $pointer, $obj];
$this->xpathhandlers[] = [$xpath_array, $this->ensureHandler($pointer, $obj)];
}
/**
* Add Event Handler
*
* @param string $name
* @param string $pointer
* @param object $obj
* @param callable|string $pointer
* @param object|bool|null $obj
*/
public function addEventHandler(string $name, string $pointer, object $obj)
public function addEventHandler(string $name, $pointer, $obj = null): void
{
$this->eventhandlers[] = [$name, $pointer, $obj];
$this->eventhandlers[] = [$name, $this->ensureHandler($pointer, $obj)];
}
/**
@ -719,12 +742,8 @@ class XMLStream
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]);
if (!is_null($searchxml)) {
call_user_func_array($handler[1], [&$this->xmlobj[2]]);
}
}
}
@ -735,21 +754,25 @@ class XMLStream
} 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]);
if (
!is_null($searchxml)
&& $searchxml->name === $handler[0]
&& (
(!$handler[1] && $searchxml->ns === $this->default_ns)
|| $searchxml->ns === $handler[1]
)
) {
call_user_func_array($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
if (
array_key_exists(2, $this->xmlobj)
&& array_key_exists('id', $this->xmlobj[2]->attrs)
&& $this->xmlobj[2]->attrs['id'] === (string) $id
) {
call_user_func_array($handler[0], [&$this->xmlobj[2]]);
// id handlers are only used once
unset($this->idhandlers[$id]);
break;
}

View File

@ -135,13 +135,34 @@ class XMPP extends XMLStream
$this->stream_end = '</stream:stream>';
$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');
$this->addXPathHandler(
'{http://etherx.jabber.org/streams}features',
[$this, 'features_handler']
);
$this->addXPathHandler(
'{urn:ietf:params:xml:ns:xmpp-sasl}success',
[$this, 'sasl_success_handler']
);
$this->addXPathHandler(
'{urn:ietf:params:xml:ns:xmpp-sasl}failure',
[$this, 'sasl_failure_handler']
);
$this->addXPathHandler(
'{urn:ietf:params:xml:ns:xmpp-tls}proceed',
[$this, 'tls_proceed_handler']
);
$this->addXPathHandler(
'{jabber:client}message',
[$this, 'message_handler']
);
$this->addXPathHandler(
'{jabber:client}presence',
[$this, 'presence_handler']
);
$this->addXPathHandler(
'iq/{jabber:iq:roster}query',
[$this, 'roster_iq_handler']
);
}
/**
@ -326,8 +347,8 @@ class XMPP extends XMLStream
*/
public function getVCard(?string $jid = null): void
{
$id = $this->getID();
$this->addIdHandler($id, 'vcard_get_handler');
$id = $this->getId();
$this->addIdHandler($id, [$this, 'vcard_get_handler']);
if ($jid) {
$this->send("<iq type='get' id='$id' to='$jid'><vCard xmlns='vcard-temp' /></iq>");
} else {
@ -347,7 +368,7 @@ class XMPP extends XMLStream
$this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'><required /></starttls>");
} elseif ($xml->hasSub('bind') and $this->authed) {
$id = $this->getId();
$this->addIdHandler($id, 'resource_bind_handler');
$this->addIdHandler($id, [$this, 'resource_bind_handler']);
$this->send("<iq xmlns=\"jabber:client\" type=\"set\" id=\"$id\"><bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"><resource>{$this->resource}</resource></bind></iq>");
} else {
$this->log->log("Attempting Auth...");
@ -401,7 +422,7 @@ class XMPP extends XMLStream
$this->jid = $jidarray[0];
}
$id = $this->getId();
$this->addIdHandler($id, 'session_start_handler');
$this->addIdHandler($id, [$this, 'session_start_handler']);
$this->send("<iq xmlns='jabber:client' type='set' id='$id'><session xmlns='urn:ietf:params:xml:ns:xmpp-session' /></iq>");
}
@ -414,28 +435,30 @@ class XMPP extends XMLStream
*/
protected function roster_iq_handler(XMLObj $xml): void
{
$status = "result";
$status = 'result';
$xmlroster = $xml->sub('query');
$contacts = [];
foreach ($xmlroster->subs as $item) {
$groups = [];
if ($item->name == 'item') {
$jid = $item->attrs['jid']; //REQUIRED
$name = $item->attrs['name']; //MAY
$subscription = $item->attrs['subscription'];
if ($item->name === 'item') {
$jid = $item->attrs['jid']; // REQUIRED
$name = $item->attrs['name'] ?? '';
$subscription = $item->attrs['subscription'] ?? 'none';
foreach ($item->subs as $subitem) {
if ($subitem->name == 'group') {
if ($subitem->name === 'group') {
$groups[] = $subitem->data;
}
}
$contacts[] = [$jid, $subscription, $name, $groups]; //Store for action if no errors happen
// Store for action if no errors happen
$contacts[] = [$jid, $subscription, $name, $groups];
} else {
$status = "error";
$status = 'error';
}
}
if ($status == "result") { //No errors, add contacts
// No errors, add contacts
if ($status === 'result') {
foreach ($contacts as $contact) {
$this->roster->addContact($contact[0], $contact[1], $contact[2], $contact[3]);
$this->roster->addContact(...$contact);
}
}
if ($xml->attrs['type'] == 'set') {