XML stanza parsing fixes
- Add a `Stanza` class which can be used by Strophe because it has a `tree()` function. This is what gets returned by the `stx` tagged template. - Throw an error when no valid namespace is on the stanza. Strophe.Builder used to automatically add the `jabber:client` namespace, but that doesn't happen with `toStanza`, so we need to fail if it's not specified by the user. - Use the Strophe XML Parser This opens the door to NodeJS support
This commit is contained in:
parent
bab11b682b
commit
5029d93523
|
@ -428,8 +428,8 @@ export const api = _converse.api = {
|
|||
}
|
||||
if (typeof stanza === 'string') {
|
||||
stanza = u.toStanza(stanza);
|
||||
} else if (stanza?.nodeTree) {
|
||||
stanza = stanza.nodeTree;
|
||||
} else if (stanza?.tree) {
|
||||
stanza = stanza.tree();
|
||||
}
|
||||
|
||||
if (stanza.tagName === 'iq') {
|
||||
|
@ -454,7 +454,7 @@ export const api = _converse.api = {
|
|||
*/
|
||||
sendIQ (stanza, timeout=_converse.STANZA_TIMEOUT, reject=true) {
|
||||
let promise;
|
||||
stanza = stanza?.nodeTree ?? stanza;
|
||||
stanza = stanza.tree?.() ?? stanza;
|
||||
if (['get', 'set'].includes(stanza.getAttribute('type'))) {
|
||||
timeout = timeout || _converse.STANZA_TIMEOUT;
|
||||
if (reject) {
|
||||
|
|
|
@ -31,7 +31,7 @@ async function createCapsNode () {
|
|||
'hash': "sha-1",
|
||||
'node': "https://conversejs.org",
|
||||
'ver': await generateVerificationString()
|
||||
}).nodeTree;
|
||||
}).tree();
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -88,6 +88,8 @@ export function registerMessageHandlers () {
|
|||
* @param { MessageAttributes } attrs - The message attributes
|
||||
*/
|
||||
export async function handleMessageStanza (stanza) {
|
||||
stanza = stanza.tree?.() ?? stanza;
|
||||
|
||||
if (isServerMessage(stanza)) {
|
||||
// Prosody sends headline messages with type `chat`, so we need to filter them out here.
|
||||
const from = stanza.getAttribute('from');
|
||||
|
|
|
@ -549,6 +549,8 @@ const ChatRoomMixin = {
|
|||
* @param { XMLElement } stanza
|
||||
*/
|
||||
async handleMessageStanza (stanza) {
|
||||
stanza = stanza.tree?.() ?? stanza;
|
||||
|
||||
const type = stanza.getAttribute('type');
|
||||
if (type === 'error') {
|
||||
return this.handleErrorMessageStanza(stanza);
|
||||
|
@ -755,7 +757,7 @@ const ChatRoomMixin = {
|
|||
message.set({
|
||||
'retracted': new Date().toISOString(),
|
||||
'retracted_id': origin_id,
|
||||
'retraction_id': stanza.nodeTree.getAttribute('id'),
|
||||
'retraction_id': stanza.tree().getAttribute('id'),
|
||||
'editable': false
|
||||
});
|
||||
const result = await this.sendTimedMessage(stanza);
|
||||
|
|
|
@ -124,19 +124,19 @@ describe("A MUC message", function () {
|
|||
const muc_jid = 'lounge@montague.lit';
|
||||
const model = await mock.openAndEnterChatRoom(_converse, muc_jid, 'romeo');
|
||||
const received_stanza = u.toStanza(`
|
||||
<message to='${_converse.jid}' from='${muc_jid}/mallory' type='groupchat' id='${_converse.connection.getUniqueId()}' >
|
||||
<reply xmlns='urn:xmpp:reply:0' id='${_converse.connection.getUniqueId()}' to='${_converse.jid}'/>
|
||||
<fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'>
|
||||
<body start='0' end='10'/>
|
||||
</fallback>
|
||||
<active xmlns='http://jabber.org/protocol/chatstates'/>
|
||||
<body>> ping
|
||||
pong</body>
|
||||
<request xmlns='urn:xmpp:receipts'/>
|
||||
</message>
|
||||
`);
|
||||
<message to='${_converse.jid}' from='${muc_jid}/mallory' type='groupchat' id='${_converse.connection.getUniqueId()}' >
|
||||
<reply xmlns='urn:xmpp:reply:0' id='${_converse.connection.getUniqueId()}' to='${_converse.jid}'/>
|
||||
<fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'>
|
||||
<body start='0' end='10'/>
|
||||
</fallback>
|
||||
<active xmlns='http://jabber.org/protocol/chatstates'/>
|
||||
<body>> ping
|
||||
pong</body>
|
||||
<request xmlns='urn:xmpp:receipts'/>
|
||||
</message>
|
||||
`);
|
||||
await model.handleMessageStanza(received_stanza);
|
||||
await u.waitUntil(() => model.messages.last());
|
||||
expect(model.messages.last().get('body')).toBe('> ping\npong');
|
||||
expect(model.messages.last().get('body')).toBe('> ping\n pong');
|
||||
}));
|
||||
});
|
||||
|
|
|
@ -74,7 +74,7 @@ converse.plugins.add('converse-pubsub', {
|
|||
|
||||
// The publish-options precondition couldn't be
|
||||
// met. We re-publish but without publish-options.
|
||||
const el = stanza.nodeTree;
|
||||
const el = stanza.tree();
|
||||
el.querySelector('publish-options').outerHTML = '';
|
||||
log.warn(`PubSub: Republishing without publish options. ${el.outerHTML}`);
|
||||
await api.sendIQ(el);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import debounce from 'lodash-es/debounce';
|
||||
import isElement from 'lodash-es/isElement';
|
||||
import log from "../../log.js";
|
||||
import sizzle from 'sizzle';
|
||||
import { BOSH_WAIT } from '../../shared/constants.js';
|
||||
|
@ -441,9 +440,8 @@ export class MockConnection extends Connection {
|
|||
}
|
||||
|
||||
sendIQ (iq, callback, errback) {
|
||||
if (!isElement(iq)) {
|
||||
iq = iq.nodeTree;
|
||||
}
|
||||
iq = iq.tree?.() ?? iq;
|
||||
|
||||
this.IQ_stanzas.push(iq);
|
||||
const id = super.sendIQ(iq, callback, errback);
|
||||
this.IQ_ids.push(id);
|
||||
|
@ -451,11 +449,8 @@ export class MockConnection extends Connection {
|
|||
}
|
||||
|
||||
send (stanza) {
|
||||
if (isElement(stanza)) {
|
||||
this.sent_stanzas.push(stanza);
|
||||
} else {
|
||||
this.sent_stanzas.push(stanza.nodeTree);
|
||||
}
|
||||
stanza = stanza.tree?.() ?? stanza;
|
||||
this.sent_stanzas.push(stanza);
|
||||
return super.send(stanza);
|
||||
}
|
||||
|
||||
|
|
|
@ -91,8 +91,8 @@ export function prefixMentions (message) {
|
|||
const u = {};
|
||||
|
||||
u.isTagEqual = function (stanza, name) {
|
||||
if (stanza.nodeTree) {
|
||||
return u.isTagEqual(stanza.nodeTree, name);
|
||||
if (stanza.tree?.()) {
|
||||
return u.isTagEqual(stanza.tree(), name);
|
||||
} else if (!(stanza instanceof Element)) {
|
||||
throw Error(
|
||||
"isTagEqual called with value which isn't "+
|
||||
|
|
|
@ -1,27 +1,63 @@
|
|||
const parser = new DOMParser();
|
||||
const parserErrorNS = parser.parseFromString('invalid', 'text/xml')
|
||||
.getElementsByTagName("parsererror")[0].namespaceURI;
|
||||
import log from '../log.js';
|
||||
import { Strophe } from 'strophe.js/src/strophe';
|
||||
|
||||
export function toStanza (string) {
|
||||
const node = parser.parseFromString(string, "text/xml");
|
||||
if (node.getElementsByTagNameNS(parserErrorNS, 'parsererror').length) {
|
||||
const PARSE_ERROR_NS = 'http://www.w3.org/1999/xhtml';
|
||||
|
||||
export function toStanza (string, throwErrorIfInvalidNS) {
|
||||
const doc = Strophe.xmlHtmlNode(string);
|
||||
|
||||
if (doc.getElementsByTagNameNS(PARSE_ERROR_NS, 'parsererror').length) {
|
||||
throw new Error(`Parser Error: ${string}`);
|
||||
}
|
||||
return node.firstElementChild;
|
||||
|
||||
const node = doc.firstElementChild;
|
||||
|
||||
if (
|
||||
['message', 'iq', 'presence'].includes(node.nodeName.toLowerCase()) &&
|
||||
node.namespaceURI !== 'jabber:client' &&
|
||||
node.namespaceURI !== 'jabber:server'
|
||||
) {
|
||||
const err_msg = `Invalid namespaceURI ${node.namespaceURI}`;
|
||||
log.error(err_msg);
|
||||
if (throwErrorIfInvalidNS) throw new Error(err_msg);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* A Stanza represents a XML element used in XMPP (commonly referred to as
|
||||
* stanzas).
|
||||
*/
|
||||
class Stanza {
|
||||
|
||||
constructor (strings, values) {
|
||||
this.strings = strings;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
toString () {
|
||||
this.string = this.string ||
|
||||
this.strings.reduce((acc, str) => {
|
||||
const idx = this.strings.indexOf(str);
|
||||
const value = this.values.length > idx ? this.values[idx].toString() : '';
|
||||
return acc + str + value;
|
||||
}, '');
|
||||
return this.string;
|
||||
}
|
||||
|
||||
tree () {
|
||||
this.node = this.node ?? toStanza(this.toString(), true);
|
||||
return this.node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tagged template literal function which can be used to generate XML stanzas.
|
||||
* Tagged template literal function which generates {@link Stanza } objects
|
||||
*
|
||||
* Similar to the `html` function, from Lit.
|
||||
*
|
||||
* @example stx`<presence type="${type}"><show>${show}</show></presence>`
|
||||
*/
|
||||
export function stx (strings, ...values) {
|
||||
return toStanza(
|
||||
strings.reduce((acc, str) => {
|
||||
const idx = strings.indexOf(str);
|
||||
return acc + str + (values.length > idx ? values[idx] : '')
|
||||
}, '')
|
||||
);
|
||||
return new Stanza(strings, values);
|
||||
}
|
||||
|
|
|
@ -276,6 +276,7 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
|
|||
await model.handleMessageStanza(
|
||||
stx`
|
||||
<message
|
||||
xmlns="jabber:server"
|
||||
from="lounge@montague.lit/newguy"
|
||||
to="_converse.connection.jid"
|
||||
type="groupchat"
|
||||
|
@ -293,6 +294,7 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
|
|||
await model.handleMessageStanza(
|
||||
stx`
|
||||
<message
|
||||
xmlns="jabber:server"
|
||||
from="lounge@montague.lit/newguy"
|
||||
to="_converse.connection.jid"
|
||||
type="groupchat"
|
||||
|
@ -315,6 +317,7 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
|
|||
await model.handleMessageStanza(
|
||||
stx`
|
||||
<message
|
||||
xmlns="jabber:server"
|
||||
from="lounge@montague.lit/newguy"
|
||||
to="_converse.connection.jid"
|
||||
type="groupchat"
|
||||
|
@ -355,6 +358,7 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
|
|||
await model.handleMessageStanza(
|
||||
stx`
|
||||
<message
|
||||
xmlns="jabber:server"
|
||||
from="lounge@montague.lit/${nick}"
|
||||
to="_converse.connection.jid"
|
||||
type="groupchat"
|
||||
|
@ -372,6 +376,7 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
|
|||
await model.handleMessageStanza(
|
||||
stx`
|
||||
<message
|
||||
xmlns="jabber:server"
|
||||
from="lounge@montague.lit/${nick}"
|
||||
to="_converse.connection.jid"
|
||||
type="groupchat"
|
||||
|
@ -389,6 +394,7 @@ describe('A Groupchat Message XEP-0308 correction ', function () {
|
|||
await model.handleMessageStanza(
|
||||
stx`
|
||||
<message
|
||||
xmlns="jabber:server"
|
||||
from="lounge@montague.lit/${nick}"
|
||||
to="_converse.connection.jid"
|
||||
type="groupchat"
|
||||
|
|
|
@ -40,6 +40,7 @@ describe("A MUC", function () {
|
|||
_converse.connection._dataRecv(mock.createRequest(
|
||||
stx`
|
||||
<presence
|
||||
xmlns="jabber:server"
|
||||
from='${muc_jid}/${nick}'
|
||||
id='DC352437-C019-40EC-B590-AF29E879AF98'
|
||||
to='${_converse.jid}'
|
||||
|
@ -60,6 +61,7 @@ describe("A MUC", function () {
|
|||
_converse.connection._dataRecv(mock.createRequest(
|
||||
stx`
|
||||
<presence
|
||||
xmlns="jabber:server"
|
||||
from='${muc_jid}/${newnick}'
|
||||
id='5B4F27A4-25ED-43F7-A699-382C6B4AFC67'
|
||||
to='${_converse.jid}'>
|
||||
|
|
Loading…
Reference in New Issue
Block a user