xmpp-parsers: Implement XEP-0377: Spam Reporting

Emmanuel Gil Peyrot created

Change summary

parsers/ChangeLog             |   1 
parsers/doap.xml              |   8 ++
parsers/src/lib.rs            |   3 +
parsers/src/ns.rs             |   3 +
parsers/src/spam_reporting.rs | 102 +++++++++++++++++++++++++++++++++++++
5 files changed, 117 insertions(+)

Detailed changes

parsers/ChangeLog 🔗

@@ -38,6 +38,7 @@ XXXX-YY-ZZ RELEASER <admin@example.com>
         the optional nickname instead of a String (!485)
     * New parsers/serialisers:
       - Stream Features (RFC 6120) (!400)
+      - Spam Reporting (XEP-0377) (!506)
       - Extensible SASL Profile (XEP-0388)
       - SASL Channel-Binding Type Capability (XEP-0440)
       - Stream Limits Advertisement (XEP-0478)

parsers/doap.xml 🔗

@@ -578,6 +578,14 @@
             <xmpp:since>0.16.0</xmpp:since>
         </xmpp:SupportedXep>
     </implements>
+    <implements>
+        <xmpp:SupportedXep>
+            <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0377.html"/>
+            <xmpp:status>complete</xmpp:status>
+            <xmpp:version>0.3.1</xmpp:version>
+            <xmpp:since>NEXT</xmpp:since>
+        </xmpp:SupportedXep>
+    </implements>
     <implements>
         <xmpp:SupportedXep>
             <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0380.html"/>

parsers/src/lib.rs 🔗

@@ -257,6 +257,9 @@ pub mod mix;
 /// XEP-0373: OpenPGP for XMPP
 pub mod openpgp;
 
+/// XEP-0377: Spam Reporting
+pub mod spam_reporting;
+
 /// XEP-0380: Explicit Message Encryption
 pub mod eme;
 

parsers/src/ns.rs 🔗

@@ -265,6 +265,9 @@ pub const OX: &str = "urn:xmpp:openpgp:0";
 /// XEP-0373: OpenPGP for XMPP
 pub const OX_PUBKEYS: &str = "urn:xmpp:openpgp:0:public-keys";
 
+/// XEP-0377: Spam Reporting
+pub const SPAM_REPORTING: &str = "urn:xmpp:reporting:1";
+
 /// XEP-0380: Explicit Message Encryption
 pub const EME: &str = "urn:xmpp:eme:0";
 

parsers/src/spam_reporting.rs 🔗

@@ -0,0 +1,102 @@
+// Copyright (c) 2024 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+use xso::{AsXml, FromXml};
+
+use crate::ns;
+use crate::stanza_id::StanzaId;
+use alloc::collections::BTreeMap;
+
+generate_attribute!(
+    /// The possible reasons for a report.
+    Reason, "reason", {
+        /// Used for reporting a JID that is sending unwanted messages.
+        Spam => "urn:xmpp:reporting:spam",
+
+        /// Used for reporting general abuse.
+        Abuse => "urn:xmpp:reporting:abuse",
+    }
+);
+
+type Lang = String;
+
+/// Represents an abuse or spam report.
+#[derive(FromXml, AsXml, Debug, Clone, PartialEq)]
+#[xml(namespace = ns::SPAM_REPORTING, name = "report")]
+pub struct Report {
+    /// The reason for this report.
+    #[xml(attribute)]
+    reason: Reason,
+
+    /// Ids of the incriminated stanzas.
+    #[xml(child(n = ..))]
+    stanza_ids: Vec<StanzaId>,
+
+    /// Some text explaining the reason for this report.
+    #[xml(extract(n = .., name = "text", fields(
+        attribute(name = "xml:lang", type_ = Lang),
+        text(type_ = String)
+    )))]
+    texts: BTreeMap<Lang, String>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use jid::Jid;
+    use minidom::Element;
+
+    #[cfg(target_pointer_width = "32")]
+    #[test]
+    fn test_size() {
+        assert_size!(Reason, 1);
+        assert_size!(Report, 28);
+    }
+
+    #[cfg(target_pointer_width = "64")]
+    #[test]
+    fn test_size() {
+        assert_size!(Reason, 1);
+        assert_size!(Report, 56);
+    }
+
+    #[test]
+    // Comes from https://xmpp.org/extensions/xep-0377.html#example-2
+    fn test_example_1() {
+        let elem: Element =
+            "<report xmlns='urn:xmpp:reporting:1' reason='urn:xmpp:reporting:spam'/>"
+                .parse()
+                .unwrap();
+        let report = Report::try_from(elem).unwrap();
+        assert_eq!(report.reason, Reason::Spam);
+        assert!(report.stanza_ids.is_empty());
+        assert!(report.texts.is_empty());
+    }
+
+    #[test]
+    // Comes from https://xmpp.org/extensions/xep-0377.html#example-5
+    fn test_example_5() {
+        let elem: Element = "<report xmlns='urn:xmpp:reporting:1' reason='urn:xmpp:reporting:spam'>
+            <stanza-id xmlns='urn:xmpp:sid:0' by='romeo@example.net' id='28482-98726-73623'/>
+            <stanza-id xmlns='urn:xmpp:sid:0' by='romeo@example.net' id='38383-38018-18385'/>
+            <text xml:lang='en'>Never came trouble to my house like this.</text>
+        </report>"
+            .parse()
+            .unwrap();
+        let report = Report::try_from(elem).unwrap();
+        let romeo = Jid::new("romeo@example.net").unwrap();
+        assert_eq!(report.reason, Reason::Spam);
+        assert_eq!(report.stanza_ids.len(), 2);
+        assert_eq!(report.stanza_ids[0].by, romeo);
+        assert_eq!(report.stanza_ids[0].id, "28482-98726-73623");
+        assert_eq!(report.stanza_ids[1].by, romeo);
+        assert_eq!(report.stanza_ids[1].id, "38383-38018-18385");
+        assert_eq!(
+            report.texts["en"],
+            "Never came trouble to my house like this."
+        );
+    }
+}