Skip to content

Commit

Permalink
Refactor MUC stanza parsing
Browse files Browse the repository at this point in the history
- Tighten up queries to search for direct children
- Remove `is_me` in favor of `is_self`
  • Loading branch information
jcbrand committed Dec 28, 2024
1 parent 69c2fb8 commit 0dba95b
Show file tree
Hide file tree
Showing 5 changed files with 46 additions and 29 deletions.
2 changes: 1 addition & 1 deletion src/headless/plugins/muc/muc.js
Original file line number Diff line number Diff line change
Expand Up @@ -1808,7 +1808,7 @@ class MUC extends ModelWithMessages(ColorAwareModel(ChatBoxBase)) {
resource: Strophe.getResourceFromJid(jid) || occupant?.attributes?.resource,
};

if (attrs.is_me) {
if (attrs.is_self) {
let modified = false;
if (attrs.codes.includes(converse.MUC_NICK_CHANGED_CODE)) {
modified = true;
Expand Down
67 changes: 43 additions & 24 deletions src/headless/plugins/muc/parsers.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
isValidReceiptRequest,
throwErrorIfInvalidForward,
} from '../../shared/parsers';
import { ACTION_INFO_CODES, NEW_NICK_CODES, STATUS_CODE_STANZAS } from './constants.js';
import { STATUS_CODE_STANZAS } from './constants.js';

const { Strophe, sizzle, u } = converse.env;
const { NS } = Strophe;
Expand Down Expand Up @@ -71,7 +71,7 @@ export function getMEPActivities (stanza) {
* @returns {Object}
*/
function getJIDFromMUCUserData (stanza) {
const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, stanza).pop();
const item = sizzle(`message > x[xmlns="${Strophe.NS.MUC_USER}"] item`, stanza).pop();
return item?.getAttribute('jid');
}

Expand Down Expand Up @@ -124,7 +124,7 @@ function getStatusCodes(stanza, type) {
/**
* @typedef {import('./types').MUCStatusCode} MUCStatusCode
*/
const codes = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] status`, stanza)
const codes = sizzle(`${type} > x[xmlns="${Strophe.NS.MUC_USER}"] status`, stanza)
.map(/** @param {Element} s */ (s) => s.getAttribute('code'))
.filter(
/** @param {MUCStatusCode} c */
Expand Down Expand Up @@ -175,51 +175,71 @@ function getSender (attrs, chatbox) {

/**
* Parses a passed in message stanza and returns an object of attributes.
* @param {Element} stanza - The message stanza
* @param {Element} original_stanza - The message stanza
* @param {MUC} chatbox
* @returns {Promise<MUCMessageAttributes|StanzaParseError>}
*/
export async function parseMUCMessage (stanza, chatbox) {
throwErrorIfInvalidForward(stanza);
export async function parseMUCMessage (original_stanza, chatbox) {
throwErrorIfInvalidForward(original_stanza);

const selector = `[xmlns="${NS.MAM}"] > forwarded[xmlns="${NS.FORWARD}"] > message`;
const original_stanza = stanza;
stanza = sizzle(selector, stanza).pop() || stanza;
const forwarded_stanza = sizzle(
`result[xmlns="${NS.MAM}"] > forwarded[xmlns="${NS.FORWARD}"] > message`,
original_stanza
).pop();

const stanza = forwarded_stanza || original_stanza;
if (sizzle(`message > forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).length) {
return new StanzaParseError(
stanza,
`Invalid Stanza: Forged MAM groupchat message from ${stanza.getAttribute('from')}`,
);
}
const delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop();

let delay;
let body;

if (forwarded_stanza) {
if (sizzle(`message > forwarded[xmlns="${Strophe.NS.FORWARD}"]`, forwarded_stanza).length) {
return new StanzaParseError(
original_stanza,
`Invalid Stanza: Forged MAM groupchat message from ${original_stanza.getAttribute('from')}`,
);
}
delay = sizzle(`delay[xmlns="${Strophe.NS.DELAY}"]`, forwarded_stanza.parentElement).pop();
body = forwarded_stanza.querySelector(':scope > body')?.textContent?.trim();
} else {
delay = sizzle(`message > delay[xmlns="${Strophe.NS.DELAY}"]`, original_stanza).pop();
body = original_stanza.querySelector(':scope > body')?.textContent?.trim();
}


const from = stanza.getAttribute('from');
const marker = getChatMarker(stanza);

let attrs = /** @type {MUCMessageAttributes} */(Object.assign(
{
from,
body,
'activities': getMEPActivities(stanza),
'body': stanza.querySelector(':scope > body')?.textContent?.trim(),
'chat_state': getChatState(stanza),
'from_muc': Strophe.getBareJidFromJid(from),
'is_archived': isArchived(original_stanza),
'is_carbon': isCarbon(original_stanza),
'is_delayed': !!delay,
'is_forwarded': !!stanza.querySelector('forwarded'),
'is_forwarded': !!sizzle(`message > forwarded[xmlns="${Strophe.NS.FORWARD}"]`, stanza).length,
'is_headline': isHeadline(stanza),
'is_markable': !!sizzle(`markable[xmlns="${Strophe.NS.MARKERS}"]`, stanza).length,
'is_markable': !!sizzle(`message > markable[xmlns="${Strophe.NS.MARKERS}"]`, stanza).length,
'is_marker': !!marker,
'is_unstyled': !!sizzle(`unstyled[xmlns="${Strophe.NS.STYLING}"]`, stanza).length,
'is_unstyled': !!sizzle(`message > unstyled[xmlns="${Strophe.NS.STYLING}"]`, stanza).length,
'marker_id': marker && marker.getAttribute('id'),
'msgid': stanza.getAttribute('id') || original_stanza.getAttribute('id'),
'nick': Strophe.unescapeNode(Strophe.getResourceFromJid(from)),
'occupant_id': getOccupantID(stanza, chatbox),
'receipt_id': getReceiptId(stanza),
'received': new Date().toISOString(),
'references': getReferences(stanza),
'subject': stanza.querySelector('subject')?.textContent,
'thread': stanza.querySelector('thread')?.textContent,
'subject': stanza.querySelector(':scope > subject')?.textContent,
'thread': stanza.querySelector(':scope > thread')?.textContent,
'time': delay ? dayjs(delay.getAttribute('stamp')).toISOString() : new Date().toISOString(),
'to': stanza.getAttribute('to'),
'type': stanza.getAttribute('type')
Expand Down Expand Up @@ -247,16 +267,16 @@ export async function parseMUCMessage (stanza, chatbox) {

if (attrs.is_archived && original_stanza.getAttribute('from') !== attrs.from_muc) {
return new StanzaParseError(
stanza,
original_stanza,
`Invalid Stanza: Forged MAM message from ${original_stanza.getAttribute('from')}`,
);
} else if (attrs.is_archived && original_stanza.getAttribute('from') !== chatbox.get('jid')) {
return new StanzaParseError(
stanza,
original_stanza,
`Invalid Stanza: Forged MAM groupchat message from ${stanza.getAttribute('from')}`,
);
} else if (attrs.is_carbon) {
return new StanzaParseError(stanza, 'Invalid Stanza: MUC messages SHOULD NOT be XEP-0280 carbon copied');
return new StanzaParseError(original_stanza, 'Invalid Stanza: MUC messages SHOULD NOT be XEP-0280 carbon copied');
}

// We prefer to use one of the XEP-0359 unique and stable stanza IDs as the Model id, to avoid duplicates.
Expand All @@ -266,7 +286,7 @@ export async function parseMUCMessage (stanza, chatbox) {
* *Hook* which allows plugins to add additional parsing
* @event _converse#parseMUCMessage
*/
attrs = await api.hook('parseMUCMessage', stanza, attrs);
attrs = await api.hook('parseMUCMessage', original_stanza, attrs);

// We call this after the hook, to allow plugins to decrypt encrypted
// messages, since we need to parse the message text to determine whether
Expand Down Expand Up @@ -318,7 +338,7 @@ function parsePresenceUserItem(stanza, nick) {
* @typedef {import('./types').MUCAffiliation} MUCAffiliation
* @typedef {import('./types').MUCRole} MUCRole
*/
const item = sizzle(`x[xmlns="${Strophe.NS.MUC_USER}"] item`, stanza).pop();
const item = sizzle(`presence > x[xmlns="${Strophe.NS.MUC_USER}"] item`, stanza).pop();
if (item) {
const actor = item.querySelector('actor');
return {
Expand Down Expand Up @@ -354,14 +374,13 @@ export function parseMUCPresence (stanza, chatbox) {
const nick = Strophe.getResourceFromJid(from);
const attrs = /** @type {MUCPresenceAttributes} */({
from,
is_me: !!stanza.querySelector("status[code='110']"),
nick,
occupant_id: getOccupantID(stanza, chatbox),
type,
status: stanza.querySelector(':scope > status')?.textContent ?? undefined,
show: stanza.querySelector(':scope > show')?.textContent ?? (type !== 'unavailable' ? 'online' : 'offline'),
image_hash: sizzle(`:scope > x[xmlns="${Strophe.NS.VCARDUPDATE}"] photo`, stanza).pop()?.textContent,
hats: sizzle(`:scope x[xmlns="${Strophe.NS.MUC_HATS}"] hat`, stanza).map(/** @param {Element} h */(h) => ({
image_hash: sizzle(`presence > x[xmlns="${Strophe.NS.VCARDUPDATE}"] photo`, stanza).pop()?.textContent,
hats: sizzle(`presence > hats[xmlns="${Strophe.NS.MUC_HATS}"] hat`, stanza).map(/** @param {Element} h */(h) => ({
title: h.getAttribute('title'),
uri: h.getAttribute('uri')
})),
Expand Down
1 change: 0 additions & 1 deletion src/headless/plugins/muc/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export type MUCPresenceAttributes = MUCPresenceItemAttributes & {
from: string; // The sender JID (${muc_jid}/${nick})
hats: Array<MUCHat>; // An array of XEP-0317 hats
image_hash?: string;
is_me?: boolean;
is_self: boolean;
nick: string; // The nickname of the sender
occupant_id: string; // The XEP-0421 occupant ID
Expand Down
4 changes: 2 additions & 2 deletions src/headless/types/plugins/muc/parsers.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@
export function getMEPActivities(stanza: Element): any[];
/**
* Parses a passed in message stanza and returns an object of attributes.
* @param {Element} stanza - The message stanza
* @param {Element} original_stanza - The message stanza
* @param {MUC} chatbox
* @returns {Promise<MUCMessageAttributes|StanzaParseError>}
*/
export function parseMUCMessage(stanza: Element, chatbox: MUC): Promise<MUCMessageAttributes | StanzaParseError>;
export function parseMUCMessage(original_stanza: Element, chatbox: MUC): Promise<MUCMessageAttributes | StanzaParseError>;
/**
* Given an IQ stanza with a member list, create an array of objects containing
* known member data (e.g. jid, nick, role, affiliation).
Expand Down
1 change: 0 additions & 1 deletion src/headless/types/plugins/muc/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ export type MUCPresenceAttributes = MUCPresenceItemAttributes & {
from: string;
hats: Array<MUCHat>;
image_hash?: string;
is_me?: boolean;
is_self: boolean;
nick: string;
occupant_id: string;
Expand Down

0 comments on commit 0dba95b

Please sign in to comment.