2017-05-28 15:30:43 +00:00
|
|
|
|
// Copyright (c) 2017 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/.
|
|
|
|
|
|
2018-12-18 14:32:05 +00:00
|
|
|
|
use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
|
2019-06-10 21:17:09 +00:00
|
|
|
|
use jid::BareJid;
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2018-07-31 21:47:55 +00:00
|
|
|
|
generate_elem_id!(
|
|
|
|
|
/// Represents a group a contact is part of.
|
2018-12-18 14:32:05 +00:00
|
|
|
|
Group,
|
|
|
|
|
"group",
|
|
|
|
|
ROSTER
|
2018-07-31 21:47:55 +00:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
generate_attribute!(
|
|
|
|
|
/// The state of your mutual subscription with a contact.
|
|
|
|
|
Subscription, "subscription", {
|
|
|
|
|
/// The user doesn’t have any subscription to this contact’s presence,
|
|
|
|
|
/// and neither does this contact.
|
|
|
|
|
None => "none",
|
|
|
|
|
|
|
|
|
|
/// Only this contact has a subscription with you, not the opposite.
|
|
|
|
|
From => "from",
|
|
|
|
|
|
|
|
|
|
/// Only you have a subscription with this contact, not the opposite.
|
|
|
|
|
To => "to",
|
|
|
|
|
|
|
|
|
|
/// Both you and your contact are subscribed to each other’s presence.
|
|
|
|
|
Both => "both",
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2018-07-31 21:47:55 +00:00
|
|
|
|
/// In a roster set, this asks the server to remove this contact item
|
|
|
|
|
/// from your roster.
|
|
|
|
|
Remove => "remove",
|
|
|
|
|
}, Default = None
|
|
|
|
|
);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2019-01-07 13:49:33 +00:00
|
|
|
|
generate_attribute!(
|
|
|
|
|
/// The sub-state of subscription with a contact.
|
2019-01-12 21:51:02 +00:00
|
|
|
|
Ask, "ask", (
|
|
|
|
|
/// Pending sub-state of the 'none' subscription state.
|
|
|
|
|
Subscribe => "subscribe"
|
|
|
|
|
)
|
2019-01-07 13:49:33 +00:00
|
|
|
|
);
|
|
|
|
|
|
2018-05-28 14:45:13 +00:00
|
|
|
|
generate_element!(
|
2017-11-24 05:09:25 +00:00
|
|
|
|
/// Contact from the user’s contact list.
|
|
|
|
|
#[derive(PartialEq)]
|
2018-05-14 14:30:28 +00:00
|
|
|
|
Item, "item", ROSTER,
|
2017-11-24 05:09:25 +00:00
|
|
|
|
attributes: [
|
|
|
|
|
/// JID of this contact.
|
2019-06-10 21:17:09 +00:00
|
|
|
|
jid: Required<BareJid> = "jid",
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2017-11-24 05:09:25 +00:00
|
|
|
|
/// Name of this contact.
|
2019-02-24 19:26:40 +00:00
|
|
|
|
name: OptionEmpty<String> = "name",
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2017-11-24 05:09:25 +00:00
|
|
|
|
/// Subscription status of this contact.
|
2019-02-24 19:26:40 +00:00
|
|
|
|
subscription: Default<Subscription> = "subscription",
|
2019-01-07 13:49:33 +00:00
|
|
|
|
|
2019-01-12 21:51:02 +00:00
|
|
|
|
/// Indicates “Pending Out” sub-states for this contact.
|
2019-02-24 19:26:40 +00:00
|
|
|
|
ask: Default<Ask> = "ask",
|
2017-11-24 05:09:25 +00:00
|
|
|
|
],
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2017-11-24 05:09:25 +00:00
|
|
|
|
children: [
|
|
|
|
|
/// Groups this contact is part of.
|
2018-05-14 14:30:28 +00:00
|
|
|
|
groups: Vec<Group> = ("group", ROSTER) => Group
|
2017-11-24 05:09:25 +00:00
|
|
|
|
]
|
|
|
|
|
);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2018-05-28 14:45:13 +00:00
|
|
|
|
generate_element!(
|
2017-11-23 16:45:05 +00:00
|
|
|
|
/// The contact list of the user.
|
2018-05-14 14:30:28 +00:00
|
|
|
|
Roster, "query", ROSTER,
|
2017-11-23 16:45:05 +00:00
|
|
|
|
attributes: [
|
|
|
|
|
/// Version of the contact list.
|
|
|
|
|
///
|
|
|
|
|
/// This is an opaque string that should only be sent back to the server on
|
|
|
|
|
/// a new connection, if this client is storing the contact list between
|
|
|
|
|
/// connections.
|
2019-02-24 19:26:40 +00:00
|
|
|
|
ver: Option<String> = "ver"
|
2017-11-23 16:45:05 +00:00
|
|
|
|
],
|
|
|
|
|
children: [
|
|
|
|
|
/// List of the contacts of the user.
|
2018-05-14 14:30:28 +00:00
|
|
|
|
items: Vec<Item> = ("item", ROSTER) => Item
|
2017-11-23 16:45:05 +00:00
|
|
|
|
]
|
|
|
|
|
);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2018-05-16 13:08:17 +00:00
|
|
|
|
impl IqGetPayload for Roster {}
|
|
|
|
|
impl IqSetPayload for Roster {}
|
|
|
|
|
impl IqResultPayload for Roster {}
|
|
|
|
|
|
2017-05-28 15:30:43 +00:00
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
2019-01-13 11:39:51 +00:00
|
|
|
|
use crate::util::compare_elements::NamespaceAwareCompare;
|
|
|
|
|
use crate::util::error::Error;
|
2019-09-25 08:28:44 +00:00
|
|
|
|
use crate::Element;
|
2019-04-12 08:58:42 +00:00
|
|
|
|
use std::convert::TryFrom;
|
2019-10-22 23:32:41 +00:00
|
|
|
|
use std::str::FromStr;
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2018-10-28 12:10:48 +00:00
|
|
|
|
#[cfg(target_pointer_width = "32")]
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_size() {
|
|
|
|
|
assert_size!(Group, 12);
|
|
|
|
|
assert_size!(Subscription, 1);
|
2019-01-13 11:06:37 +00:00
|
|
|
|
assert_size!(Ask, 1);
|
2019-04-22 12:34:25 +00:00
|
|
|
|
assert_size!(Item, 52);
|
2018-10-28 12:10:48 +00:00
|
|
|
|
assert_size!(Roster, 24);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(target_pointer_width = "64")]
|
2018-10-26 12:26:16 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_size() {
|
|
|
|
|
assert_size!(Group, 24);
|
|
|
|
|
assert_size!(Subscription, 1);
|
2019-01-13 11:06:37 +00:00
|
|
|
|
assert_size!(Ask, 1);
|
2019-04-22 10:24:11 +00:00
|
|
|
|
assert_size!(Item, 104);
|
2018-10-26 12:26:16 +00:00
|
|
|
|
assert_size!(Roster, 48);
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-28 15:30:43 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_get() {
|
|
|
|
|
let elem: Element = "<query xmlns='jabber:iq:roster'/>".parse().unwrap();
|
|
|
|
|
let roster = Roster::try_from(elem).unwrap();
|
|
|
|
|
assert!(roster.ver.is_none());
|
|
|
|
|
assert!(roster.items.is_empty());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_result() {
|
|
|
|
|
let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net'/></query>".parse().unwrap();
|
|
|
|
|
let roster = Roster::try_from(elem).unwrap();
|
|
|
|
|
assert_eq!(roster.ver, Some(String::from("ver7")));
|
|
|
|
|
assert_eq!(roster.items.len(), 2);
|
|
|
|
|
|
2017-05-29 02:42:11 +00:00
|
|
|
|
let elem2: Element = "<query xmlns='jabber:iq:roster' ver='ver7'><item jid='nurse@example.com'/><item jid='romeo@example.net' name=''/></query>".parse().unwrap();
|
|
|
|
|
let roster2 = Roster::try_from(elem2).unwrap();
|
|
|
|
|
assert_eq!(roster.items, roster2.items);
|
|
|
|
|
|
2018-12-18 14:32:05 +00:00
|
|
|
|
let elem: Element = "<query xmlns='jabber:iq:roster' ver='ver9'/>"
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let roster = Roster::try_from(elem).unwrap();
|
|
|
|
|
assert_eq!(roster.ver, Some(String::from("ver9")));
|
|
|
|
|
assert!(roster.items.is_empty());
|
|
|
|
|
|
|
|
|
|
let elem: Element = r#"
|
|
|
|
|
<query xmlns='jabber:iq:roster' ver='ver11'>
|
|
|
|
|
<item jid='romeo@example.net'
|
|
|
|
|
name='Romeo'
|
|
|
|
|
subscription='both'>
|
|
|
|
|
<group>Friends</group>
|
|
|
|
|
</item>
|
|
|
|
|
<item jid='mercutio@example.com'
|
|
|
|
|
name='Mercutio'
|
|
|
|
|
subscription='from'/>
|
|
|
|
|
<item jid='benvolio@example.net'
|
|
|
|
|
name='Benvolio'
|
|
|
|
|
subscription='both'/>
|
2019-01-07 13:49:33 +00:00
|
|
|
|
<item jid='contact@example.org'
|
|
|
|
|
subscription='none'
|
|
|
|
|
ask='subscribe'
|
|
|
|
|
name='MyContact'>
|
|
|
|
|
<group>MyBuddies</group>
|
|
|
|
|
</item>
|
2017-05-28 15:30:43 +00:00
|
|
|
|
</query>
|
2018-12-18 14:32:05 +00:00
|
|
|
|
"#
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let roster = Roster::try_from(elem).unwrap();
|
|
|
|
|
assert_eq!(roster.ver, Some(String::from("ver11")));
|
2019-01-07 13:49:33 +00:00
|
|
|
|
assert_eq!(roster.items.len(), 4);
|
2018-12-18 14:32:05 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
roster.items[0].jid,
|
2019-06-10 21:17:09 +00:00
|
|
|
|
BareJid::from_str("romeo@example.net").unwrap()
|
2018-12-18 14:32:05 +00:00
|
|
|
|
);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
assert_eq!(roster.items[0].name, Some(String::from("Romeo")));
|
2017-10-10 16:40:29 +00:00
|
|
|
|
assert_eq!(roster.items[0].subscription, Subscription::Both);
|
2019-01-12 21:51:02 +00:00
|
|
|
|
assert_eq!(roster.items[0].ask, Ask::None);
|
2018-12-18 14:32:05 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
roster.items[0].groups,
|
|
|
|
|
vec!(Group::from_str("Friends").unwrap())
|
|
|
|
|
);
|
2019-01-07 13:49:33 +00:00
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
roster.items[3].jid,
|
2019-06-10 21:17:09 +00:00
|
|
|
|
BareJid::from_str("contact@example.org").unwrap()
|
2019-01-07 13:49:33 +00:00
|
|
|
|
);
|
|
|
|
|
assert_eq!(roster.items[3].name, Some(String::from("MyContact")));
|
|
|
|
|
assert_eq!(roster.items[3].subscription, Subscription::None);
|
2019-01-12 21:51:02 +00:00
|
|
|
|
assert_eq!(roster.items[3].ask, Ask::Subscribe);
|
2019-01-07 13:49:33 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
roster.items[3].groups,
|
|
|
|
|
vec!(Group::from_str("MyBuddies").unwrap())
|
|
|
|
|
);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-06-25 22:27:38 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_multiple_groups() {
|
|
|
|
|
let elem: Element = r#"
|
|
|
|
|
<query xmlns='jabber:iq:roster'>
|
|
|
|
|
<item jid='test@example.org'>
|
|
|
|
|
<group>A</group>
|
|
|
|
|
<group>B</group>
|
|
|
|
|
</item>
|
|
|
|
|
</query>
|
2018-12-18 14:32:05 +00:00
|
|
|
|
"#
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-06-25 22:27:38 +00:00
|
|
|
|
let elem1 = elem.clone();
|
|
|
|
|
let roster = Roster::try_from(elem).unwrap();
|
|
|
|
|
assert!(roster.ver.is_none());
|
|
|
|
|
assert_eq!(roster.items.len(), 1);
|
2018-12-18 14:32:05 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
roster.items[0].jid,
|
2019-06-10 21:17:09 +00:00
|
|
|
|
BareJid::from_str("test@example.org").unwrap()
|
2018-12-18 14:32:05 +00:00
|
|
|
|
);
|
2017-06-25 22:27:38 +00:00
|
|
|
|
assert_eq!(roster.items[0].name, None);
|
|
|
|
|
assert_eq!(roster.items[0].groups.len(), 2);
|
2017-07-29 04:36:59 +00:00
|
|
|
|
assert_eq!(roster.items[0].groups[0], Group::from_str("A").unwrap());
|
|
|
|
|
assert_eq!(roster.items[0].groups[1], Group::from_str("B").unwrap());
|
2017-06-25 22:27:38 +00:00
|
|
|
|
let elem2 = roster.into();
|
2017-08-18 23:04:18 +00:00
|
|
|
|
assert!(elem1.compare_to(&elem2));
|
2017-06-25 22:27:38 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-28 15:30:43 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_set() {
|
2018-12-18 14:32:05 +00:00
|
|
|
|
let elem: Element =
|
|
|
|
|
"<query xmlns='jabber:iq:roster'><item jid='nurse@example.com'/></query>"
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let roster = Roster::try_from(elem).unwrap();
|
|
|
|
|
assert!(roster.ver.is_none());
|
|
|
|
|
assert_eq!(roster.items.len(), 1);
|
|
|
|
|
|
|
|
|
|
let elem: Element = r#"
|
|
|
|
|
<query xmlns='jabber:iq:roster'>
|
|
|
|
|
<item jid='nurse@example.com'
|
|
|
|
|
name='Nurse'>
|
|
|
|
|
<group>Servants</group>
|
|
|
|
|
</item>
|
|
|
|
|
</query>
|
2018-12-18 14:32:05 +00:00
|
|
|
|
"#
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let roster = Roster::try_from(elem).unwrap();
|
|
|
|
|
assert!(roster.ver.is_none());
|
|
|
|
|
assert_eq!(roster.items.len(), 1);
|
2018-12-18 14:32:05 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
roster.items[0].jid,
|
2019-06-10 21:17:09 +00:00
|
|
|
|
BareJid::from_str("nurse@example.com").unwrap()
|
2018-12-18 14:32:05 +00:00
|
|
|
|
);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
assert_eq!(roster.items[0].name, Some(String::from("Nurse")));
|
|
|
|
|
assert_eq!(roster.items[0].groups.len(), 1);
|
2018-12-18 14:32:05 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
roster.items[0].groups[0],
|
|
|
|
|
Group::from_str("Servants").unwrap()
|
|
|
|
|
);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
|
|
|
|
let elem: Element = r#"
|
|
|
|
|
<query xmlns='jabber:iq:roster'>
|
|
|
|
|
<item jid='nurse@example.com'
|
|
|
|
|
subscription='remove'/>
|
|
|
|
|
</query>
|
2018-12-18 14:32:05 +00:00
|
|
|
|
"#
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let roster = Roster::try_from(elem).unwrap();
|
|
|
|
|
assert!(roster.ver.is_none());
|
|
|
|
|
assert_eq!(roster.items.len(), 1);
|
2018-12-18 14:32:05 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
roster.items[0].jid,
|
2019-06-10 21:17:09 +00:00
|
|
|
|
BareJid::from_str("nurse@example.com").unwrap()
|
2018-12-18 14:32:05 +00:00
|
|
|
|
);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
assert!(roster.items[0].name.is_none());
|
|
|
|
|
assert!(roster.items[0].groups.is_empty());
|
2017-10-10 16:40:29 +00:00
|
|
|
|
assert_eq!(roster.items[0].subscription, Subscription::Remove);
|
2017-05-28 15:30:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2019-01-12 21:00:46 +00:00
|
|
|
|
#[cfg(not(feature = "disable-validation"))]
|
2017-05-28 15:30:43 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn test_invalid() {
|
2018-12-18 14:32:05 +00:00
|
|
|
|
let elem: Element = "<query xmlns='jabber:iq:roster'><coucou/></query>"
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let error = Roster::try_from(elem).unwrap_err();
|
|
|
|
|
let message = match error {
|
|
|
|
|
Error::ParseError(string) => string,
|
|
|
|
|
_ => panic!(),
|
|
|
|
|
};
|
2017-11-23 16:45:05 +00:00
|
|
|
|
assert_eq!(message, "Unknown child in query element.");
|
2017-05-28 15:30:43 +00:00
|
|
|
|
|
2018-12-18 14:32:05 +00:00
|
|
|
|
let elem: Element = "<query xmlns='jabber:iq:roster' coucou=''/>"
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let error = Roster::try_from(elem).unwrap_err();
|
|
|
|
|
let message = match error {
|
|
|
|
|
Error::ParseError(string) => string,
|
|
|
|
|
_ => panic!(),
|
|
|
|
|
};
|
2017-11-23 16:45:05 +00:00
|
|
|
|
assert_eq!(message, "Unknown attribute in query element.");
|
2017-05-28 15:30:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn test_invalid_item() {
|
2018-12-18 14:32:05 +00:00
|
|
|
|
let elem: Element = "<query xmlns='jabber:iq:roster'><item/></query>"
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let error = Roster::try_from(elem).unwrap_err();
|
|
|
|
|
let message = match error {
|
|
|
|
|
Error::ParseError(string) => string,
|
|
|
|
|
_ => panic!(),
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(message, "Required attribute 'jid' missing.");
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
let elem: Element = "<query xmlns='jabber:iq:roster'><item jid=''/></query>".parse().unwrap();
|
|
|
|
|
let error = Roster::try_from(elem).unwrap_err();
|
|
|
|
|
let error = match error {
|
|
|
|
|
Error::JidParseError(error) => error,
|
|
|
|
|
_ => panic!(),
|
|
|
|
|
};
|
|
|
|
|
assert_eq!(error.description(), "Invalid JID, I guess?");
|
|
|
|
|
*/
|
|
|
|
|
|
2018-12-18 14:32:05 +00:00
|
|
|
|
let elem: Element =
|
|
|
|
|
"<query xmlns='jabber:iq:roster'><item jid='coucou'><coucou/></item></query>"
|
|
|
|
|
.parse()
|
|
|
|
|
.unwrap();
|
2017-05-28 15:30:43 +00:00
|
|
|
|
let error = Roster::try_from(elem).unwrap_err();
|
|
|
|
|
let message = match error {
|
|
|
|
|
Error::ParseError(string) => string,
|
|
|
|
|
_ => panic!(),
|
|
|
|
|
};
|
2017-11-24 05:09:25 +00:00
|
|
|
|
assert_eq!(message, "Unknown child in item element.");
|
2017-05-28 15:30:43 +00:00
|
|
|
|
}
|
|
|
|
|
}
|