Merge branch 'compat'

Emmanuel Gil Peyrot created

Change summary

.gitlab-ci.yml         | 21 +++++++++++++++++----
Cargo.toml             |  2 ++
src/attention.rs       |  3 +++
src/bind.rs            |  1 +
src/blocking.rs        | 21 +++++++++++++--------
src/caps.rs            |  1 +
src/chatstates.rs      |  2 ++
src/jingle_ft.rs       | 40 ++++++++++++++++++++++++++--------------
src/macros.rs          |  3 +++
src/message_correct.rs |  1 +
src/muc/muc.rs         |  1 +
src/muc/user.rs        |  5 +++++
src/nick.rs            |  3 +++
src/ping.rs            |  3 +++
src/presence.rs        |  2 ++
src/pubsub/event.rs    |  1 +
src/roster.rs          |  1 +
src/sasl.rs            | 19 +++++++++++++++++++
18 files changed, 104 insertions(+), 26 deletions(-)

Detailed changes

.gitlab-ci.yml 🔗

@@ -2,26 +2,39 @@ stages:
   - build
   - test
 
+variables:
+  FEATURES: ""
+
 rust-latest-build:
   stage: build
   image: rust:latest
   script:
-    - cargo build --verbose
+    - cargo build --verbose --no-default-features --features=$FEATURES
 
 rust-nightly-build:
   stage: build
   image: rustlang/rust:nightly
   script:
-    - cargo build --verbose
+    - cargo build --verbose --no-default-features --features=$FEATURES
 
 rust-latest-test:
   stage: test
   image: rust:latest
   script:
-    - cargo test --verbose
+    - cargo test --verbose --no-default-features --features=$FEATURES
 
 rust-nightly-test:
   stage: test
   image: rustlang/rust:nightly
   script:
-    - cargo test --verbose
+    - cargo test --verbose --no-default-features --features=$FEATURES
+
+"rust-latest-test with features=disable-validation":
+  extends: rust-latest-test
+  variables:
+    FEATURES: "disable-validation"
+
+"rust-nightly-test with features=disable-validation":
+  extends: rust-nightly-test
+  variables:
+    FEATURES: "disable-validation"

Cargo.toml 🔗

@@ -28,3 +28,5 @@ try_from = "0.3.2"
 [features]
 # Build xmpp-parsers to make components instead of clients.
 component = []
+# Disable validation of unknown attributes.
+disable-validation = []

src/attention.rs 🔗

@@ -18,6 +18,7 @@ impl MessagePayload for Attention {}
 #[cfg(test)]
 mod tests {
     use super::*;
+    #[cfg(not(feature = "disable-validation"))]
     use crate::error::Error;
     use minidom::Element;
     use try_from::TryFrom;
@@ -33,6 +34,7 @@ mod tests {
         Attention::try_from(elem).unwrap();
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_child() {
         let elem: Element = "<attention xmlns='urn:xmpp:attention:0'><coucou/></attention>"
@@ -46,6 +48,7 @@ mod tests {
         assert_eq!(message, "Unknown child in attention element.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_attribute() {
         let elem: Element = "<attention xmlns='urn:xmpp:attention:0' coucou=''/>"

src/bind.rs 🔗

@@ -112,6 +112,7 @@ mod tests {
         assert_eq!(bind, Bind::None);
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_resource() {
         let elem: Element = "<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource attr='coucou'>resource</resource></bind>"

src/blocking.rs 🔗

@@ -155,14 +155,6 @@ mod tests {
             },
         ];
 
-        let request_elem = elem.clone();
-        let error = BlocklistRequest::try_from(request_elem).unwrap_err();
-        let message = match error {
-            Error::ParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message, "Unknown child in blocklist element.");
-
         let result_elem = elem.clone();
         let result = BlocklistResult::try_from(result_elem).unwrap();
         assert_eq!(result.items, two_items);
@@ -176,6 +168,7 @@ mod tests {
         assert_eq!(unblock.items, two_items);
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid() {
         let elem: Element = "<blocklist xmlns='urn:xmpp:blocking' coucou=''/>"
@@ -217,4 +210,16 @@ mod tests {
         };
         assert_eq!(message, "Unknown attribute in unblock element.");
     }
+
+    #[cfg(not(feature = "disable-validation"))]
+    #[test]
+    fn test_non_empty_blocklist_request() {
+        let elem: Element = "<blocklist xmlns='urn:xmpp:blocking'><item jid='coucou@coucou'/><item jid='domain'/></blocklist>".parse().unwrap();
+        let error = BlocklistRequest::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Unknown child in blocklist element.");
+    }
 }

src/caps.rs 🔗

@@ -233,6 +233,7 @@ mod tests {
         );
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_child() {
         let elem: Element = "<c xmlns='http://jabber.org/protocol/caps'><hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>K1Njy3HZBThlo4moOD5gBGhn0U0oK7/CbfLlIUDi6o4=</hash></c>".parse().unwrap();

src/chatstates.rs 🔗

@@ -63,6 +63,7 @@ mod tests {
         assert_eq!(message, "This is not a chatstate element.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_child() {
         let elem: Element = "<gone xmlns='http://jabber.org/protocol/chatstates'><coucou/></gone>"
@@ -76,6 +77,7 @@ mod tests {
         assert_eq!(message, "Unknown child in chatstate element.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_attribute() {
         let elem: Element = "<inactive xmlns='http://jabber.org/protocol/chatstates' coucou=''/>"

src/jingle_ft.rs 🔗

@@ -510,14 +510,6 @@ mod tests {
         };
         assert_eq!(message, "Unknown child in received element.");
 
-        let elem: Element = "<received xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator' coucou=''/>".parse().unwrap();
-        let error = Received::try_from(elem).unwrap_err();
-        let message = match error {
-            Error::ParseError(string) => string,
-            _ => panic!(),
-        };
-        assert_eq!(message, "Unknown attribute in received element.");
-
         let elem: Element =
             "<received xmlns='urn:xmpp:jingle:apps:file-transfer:5' creator='initiator'/>"
                 .parse()
@@ -538,6 +530,18 @@ mod tests {
         assert_eq!(message, "Unknown value for 'creator' attribute.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
+    #[test]
+    fn test_invalid_received() {
+        let elem: Element = "<received xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator' coucou=''/>".parse().unwrap();
+        let error = Received::try_from(elem).unwrap_err();
+        let message = match error {
+            Error::ParseError(string) => string,
+            _ => panic!(),
+        };
+        assert_eq!(message, "Unknown attribute in received element.");
+    }
+
     #[test]
     fn test_checksum() {
         let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
@@ -575,29 +579,33 @@ mod tests {
         };
         assert_eq!(message, "This is not a file element.");
 
-        let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator' coucou=''><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
+        let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' creator='initiator'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
         let error = Checksum::try_from(elem).unwrap_err();
         let message = match error {
             Error::ParseError(string) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown attribute in checksum element.");
+        assert_eq!(message, "Required attribute 'name' missing.");
 
-        let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' creator='initiator'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
+        let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='coucou'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
         let error = Checksum::try_from(elem).unwrap_err();
         let message = match error {
             Error::ParseError(string) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Required attribute 'name' missing.");
+        assert_eq!(message, "Unknown value for 'creator' attribute.");
+    }
 
-        let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='coucou'><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
+    #[cfg(not(feature = "disable-validation"))]
+    #[test]
+    fn test_invalid_checksum() {
+        let elem: Element = "<checksum xmlns='urn:xmpp:jingle:apps:file-transfer:5' name='coucou' creator='initiator' coucou=''><file><hash xmlns='urn:xmpp:hashes:2' algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash></file></checksum>".parse().unwrap();
         let error = Checksum::try_from(elem).unwrap_err();
         let message = match error {
             Error::ParseError(string) => string,
             _ => panic!(),
         };
-        assert_eq!(message, "Unknown value for 'creator' attribute.");
+        assert_eq!(message, "Unknown attribute in checksum element.");
     }
 
     #[test]
@@ -627,7 +635,11 @@ mod tests {
         assert_eq!(range2.offset, 2048);
         assert_eq!(range2.length, Some(1024));
         assert_eq!(range2.hashes, hashes);
+    }
 
+    #[cfg(not(feature = "disable-validation"))]
+    #[test]
+    fn test_invalid_range() {
         let elem: Element = "<range xmlns='urn:xmpp:jingle:apps:file-transfer:5' coucou=''/>"
             .parse()
             .unwrap();

src/macros.rs 🔗

@@ -248,6 +248,7 @@ macro_rules! check_ns_only {
 
 macro_rules! check_no_children {
     ($elem:ident, $name:tt) => {
+        #[cfg(not(feature = "disable-validation"))]
         for _ in $elem.children() {
             return Err(crate::error::Error::ParseError(concat!(
                 "Unknown child in ",
@@ -260,6 +261,7 @@ macro_rules! check_no_children {
 
 macro_rules! check_no_attributes {
     ($elem:ident, $name:tt) => {
+        #[cfg(not(feature = "disable-validation"))]
         for _ in $elem.attrs() {
             return Err(crate::error::Error::ParseError(concat!(
                 "Unknown attribute in ",
@@ -272,6 +274,7 @@ macro_rules! check_no_attributes {
 
 macro_rules! check_no_unknown_attributes {
     ($elem:ident, $name:tt, [$($attr:tt),*]) => (
+        #[cfg(not(feature = "disable-validation"))]
         for (_attr, _) in $elem.attrs() {
             $(
                 if _attr == $attr {

src/message_correct.rs 🔗

@@ -45,6 +45,7 @@ mod tests {
         Replace::try_from(elem).unwrap();
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_attribute() {
         let elem: Element = "<replace xmlns='urn:xmpp:message-correct:0' coucou=''/>"

src/muc/muc.rs 🔗

@@ -142,6 +142,7 @@ mod tests {
         assert_eq!(elem, elem2);
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_muc_invalid_attribute() {
         let elem: Element = "<x xmlns='http://jabber.org/protocol/muc' coucou=''/>"

src/muc/user.rs 🔗

@@ -303,6 +303,7 @@ mod tests {
         assert!(elem.compare_to(&elem2));
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_attribute() {
         let elem: Element = "
@@ -343,6 +344,7 @@ mod tests {
         assert_eq!(message, "Required attribute 'code' missing.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_status_invalid_child() {
         let elem: Element = "
@@ -513,6 +515,7 @@ mod tests {
         assert_eq!(reason.0, "Reason".to_owned());
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_reason_invalid_attribute() {
         let elem: Element = "
@@ -528,6 +531,7 @@ mod tests {
         assert_eq!(message, "Unknown attribute in reason element.".to_owned());
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_reason_invalid() {
         let elem: Element = "
@@ -545,6 +549,7 @@ mod tests {
         assert_eq!(message, "Unknown child in reason element.".to_owned());
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_item_invalid_attr() {
         let elem: Element = "

src/nick.rs 🔗

@@ -14,6 +14,7 @@ generate_elem_id!(
 #[cfg(test)]
 mod tests {
     use super::*;
+    #[cfg(not(feature = "disable-validation"))]
     use crate::error::Error;
     use minidom::Element;
     use try_from::TryFrom;
@@ -48,6 +49,7 @@ mod tests {
         assert_eq!(elem1, elem2);
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid() {
         let elem: Element = "<nick xmlns='http://jabber.org/protocol/nick'><coucou/></nick>"
@@ -61,6 +63,7 @@ mod tests {
         assert_eq!(message, "Unknown child in nick element.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_attribute() {
         let elem: Element = "<nick xmlns='http://jabber.org/protocol/nick' coucou=''/>"

src/ping.rs 🔗

@@ -20,6 +20,7 @@ impl IqGetPayload for Ping {}
 #[cfg(test)]
 mod tests {
     use super::*;
+    #[cfg(not(feature = "disable-validation"))]
     use crate::error::Error;
     use minidom::Element;
     use try_from::TryFrom;
@@ -42,6 +43,7 @@ mod tests {
         assert_eq!(elem1, elem2);
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid() {
         let elem: Element = "<ping xmlns='urn:xmpp:ping'><coucou/></ping>"
@@ -55,6 +57,7 @@ mod tests {
         assert_eq!(message, "Unknown child in ping element.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_attribute() {
         let elem: Element = "<ping xmlns='urn:xmpp:ping' coucou=''/>".parse().unwrap();

src/presence.rs 🔗

@@ -570,6 +570,7 @@ mod tests {
         assert!(payload.is("test", "invalid"));
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_status_child() {
         #[cfg(not(feature = "component"))]
@@ -589,6 +590,7 @@ mod tests {
         assert_eq!(message, "Unknown child in status element.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_attribute() {
         #[cfg(not(feature = "component"))]

src/pubsub/event.rs 🔗

@@ -420,6 +420,7 @@ mod tests {
         assert_eq!(message, "Unknown child in event element.");
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid_attribute() {
         let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event' coucou=''/>"

src/roster.rs 🔗

@@ -245,6 +245,7 @@ mod tests {
         assert_eq!(roster.items[0].subscription, Subscription::Remove);
     }
 
+    #[cfg(not(feature = "disable-validation"))]
     #[test]
     fn test_invalid() {
         let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>"

src/sasl.rs 🔗

@@ -288,4 +288,23 @@ mod tests {
             String::from("Call 212-555-1212 for assistance.")
         );
     }
+
+    /// Some servers apparently use a non-namespaced 'lang' attribute, which is invalid as not part
+    /// of the schema.  This tests whether we can parse it when disabling validation.
+    #[cfg(feature = "disable-validation")]
+    #[test]
+    fn invalid_failure_with_non_prefixed_text_lang() {
+        let elem: Element = "<failure xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>
+            <not-authorized xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>
+            <text xmlns='urn:ietf:params:xml:ns:xmpp-sasl' lang='en'>Invalid username or password</text>
+        </failure>"
+            .parse()
+            .unwrap();
+        let failure = Failure::try_from(elem).unwrap();
+        assert_eq!(failure.defined_condition, DefinedCondition::NotAuthorized);
+        assert_eq!(
+            failure.texts[""],
+            String::from("Invalid username or password")
+        );
+    }
 }