1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use xso::{FromXml, IntoXml};
8
9use crate::iq::{IqGetPayload, IqResultPayload, IqSetPayload};
10use crate::ns;
11use crate::Element;
12use jid::Jid;
13use xso::error::FromElementError;
14
15/// The element requesting the blocklist, the result iq will contain a
16/// [BlocklistResult].
17#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
18#[xml(namespace = ns::BLOCKING, name = "blocklist")]
19pub struct BlocklistRequest;
20
21impl IqGetPayload for BlocklistRequest {}
22
23macro_rules! generate_blocking_element {
24 ($(#[$meta:meta])* $elem:ident, $name:tt) => (
25 $(#[$meta])*
26 #[derive(Debug, Clone)]
27 pub struct $elem {
28 /// List of JIDs affected by this command.
29 pub items: Vec<Jid>,
30 }
31
32 impl TryFrom<Element> for $elem {
33 type Error = FromElementError;
34
35 fn try_from(elem: Element) -> Result<$elem, FromElementError> {
36 check_self!(elem, $name, BLOCKING);
37 check_no_attributes!(elem, $name);
38 let mut items = vec!();
39 for child in elem.children() {
40 check_child!(child, "item", BLOCKING);
41 check_no_unknown_attributes!(child, "item", ["jid"]);
42 check_no_children!(child, "item");
43 items.push(get_attr!(child, "jid", Required));
44 }
45 Ok($elem { items })
46 }
47 }
48
49 impl From<$elem> for Element {
50 fn from(elem: $elem) -> Element {
51 Element::builder($name, ns::BLOCKING)
52 .append_all(elem.items.into_iter().map(|jid| {
53 Element::builder("item", ns::BLOCKING)
54 .attr("jid", jid)
55 }))
56 .build()
57 }
58 }
59 );
60}
61
62generate_blocking_element!(
63 /// The element containing the current blocklist, as a reply from
64 /// [BlocklistRequest].
65 BlocklistResult,
66 "blocklist"
67);
68
69impl IqResultPayload for BlocklistResult {}
70
71// TODO: Prevent zero elements from being allowed.
72generate_blocking_element!(
73 /// A query to block one or more JIDs.
74 Block,
75 "block"
76);
77
78impl IqSetPayload for Block {}
79
80generate_blocking_element!(
81 /// A query to unblock one or more JIDs, or all of them.
82 ///
83 /// Warning: not putting any JID there means clearing out the blocklist.
84 Unblock,
85 "unblock"
86);
87
88impl IqSetPayload for Unblock {}
89
90/// The application-specific error condition when a message is blocked.
91#[derive(FromXml, IntoXml, PartialEq, Debug, Clone)]
92#[xml(namespace = ns::BLOCKING_ERRORS, name = "blocked")]
93pub struct Blocked;
94
95#[cfg(test)]
96mod tests {
97 use xso::error::Error;
98
99 use super::*;
100
101 #[cfg(target_pointer_width = "32")]
102 #[test]
103 fn test_size() {
104 assert_size!(BlocklistRequest, 0);
105 assert_size!(BlocklistResult, 12);
106 assert_size!(Block, 12);
107 assert_size!(Unblock, 12);
108 }
109
110 #[cfg(target_pointer_width = "64")]
111 #[test]
112 fn test_size() {
113 assert_size!(BlocklistRequest, 0);
114 assert_size!(BlocklistResult, 24);
115 assert_size!(Block, 24);
116 assert_size!(Unblock, 24);
117 }
118
119 #[test]
120 fn test_simple() {
121 let elem: Element = "<blocklist xmlns='urn:xmpp:blocking'/>".parse().unwrap();
122 let request_elem = elem.clone();
123 BlocklistRequest::try_from(request_elem).unwrap();
124
125 let result_elem = elem.clone();
126 let result = BlocklistResult::try_from(result_elem).unwrap();
127 assert!(result.items.is_empty());
128
129 let elem: Element = "<block xmlns='urn:xmpp:blocking'/>".parse().unwrap();
130 let block = Block::try_from(elem).unwrap();
131 assert!(block.items.is_empty());
132
133 let elem: Element = "<unblock xmlns='urn:xmpp:blocking'/>".parse().unwrap();
134 let unblock = Unblock::try_from(elem).unwrap();
135 assert!(unblock.items.is_empty());
136 }
137
138 #[test]
139 fn test_items() {
140 let elem: Element = "<blocklist xmlns='urn:xmpp:blocking'><item jid='coucou@coucou'/><item jid='domain'/></blocklist>".parse().unwrap();
141 let two_items = vec![
142 Jid::new("coucou@coucou").unwrap(),
143 Jid::new("domain").unwrap(),
144 ];
145
146 let result_elem = elem.clone();
147 let result = BlocklistResult::try_from(result_elem).unwrap();
148 assert_eq!(result.items, two_items);
149
150 let elem: Element = "<block xmlns='urn:xmpp:blocking'><item jid='coucou@coucou'/><item jid='domain'/></block>".parse().unwrap();
151 let block = Block::try_from(elem).unwrap();
152 assert_eq!(block.items, two_items);
153
154 let elem: Element = "<unblock xmlns='urn:xmpp:blocking'><item jid='coucou@coucou'/><item jid='domain'/></unblock>".parse().unwrap();
155 let unblock = Unblock::try_from(elem).unwrap();
156 assert_eq!(unblock.items, two_items);
157 }
158
159 #[cfg(not(feature = "disable-validation"))]
160 #[test]
161 fn test_invalid() {
162 let elem: Element = "<blocklist xmlns='urn:xmpp:blocking' coucou=''/>"
163 .parse()
164 .unwrap();
165 let request_elem = elem.clone();
166 let error = BlocklistRequest::try_from(request_elem).unwrap_err();
167 let message = match error {
168 FromElementError::Invalid(Error::Other(string)) => string,
169 _ => panic!(),
170 };
171 assert_eq!(message, "Unknown attribute in blocklist element.");
172
173 let result_elem = elem.clone();
174 let error = BlocklistResult::try_from(result_elem).unwrap_err();
175 let message = match error {
176 FromElementError::Invalid(Error::Other(string)) => string,
177 _ => panic!(),
178 };
179 assert_eq!(message, "Unknown attribute in blocklist element.");
180
181 let elem: Element = "<block xmlns='urn:xmpp:blocking' coucou=''/>"
182 .parse()
183 .unwrap();
184 let error = Block::try_from(elem).unwrap_err();
185 let message = match error {
186 FromElementError::Invalid(Error::Other(string)) => string,
187 _ => panic!(),
188 };
189 assert_eq!(message, "Unknown attribute in block element.");
190
191 let elem: Element = "<unblock xmlns='urn:xmpp:blocking' coucou=''/>"
192 .parse()
193 .unwrap();
194 let error = Unblock::try_from(elem).unwrap_err();
195 let message = match error {
196 FromElementError::Invalid(Error::Other(string)) => string,
197 _ => panic!(),
198 };
199 assert_eq!(message, "Unknown attribute in unblock element.");
200 }
201
202 #[cfg(not(feature = "disable-validation"))]
203 #[test]
204 fn test_non_empty_blocklist_request() {
205 let elem: Element = "<blocklist xmlns='urn:xmpp:blocking'><item jid='coucou@coucou'/><item jid='domain'/></blocklist>".parse().unwrap();
206 let error = BlocklistRequest::try_from(elem).unwrap_err();
207 let message = match error {
208 FromElementError::Invalid(Error::Other(string)) => string,
209 _ => panic!(),
210 };
211 assert_eq!(message, "Unknown child in blocklist element.");
212 }
213}