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 try_from::TryFrom;
8use std::str::FromStr;
9
10use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
11use jid::Jid;
12use chrono::{DateTime, FixedOffset};
13
14use error::Error;
15
16use ns;
17
18use data_forms::DataForm;
19
20#[derive(Debug, Clone)]
21pub struct Item {
22 payload: Option<Element>,
23 id: Option<String>,
24 node: Option<String>,
25 publisher: Option<Jid>,
26}
27
28impl From<Item> for Element {
29 fn from(item: Item) -> Element {
30 Element::builder("item")
31 .ns(ns::PUBSUB_EVENT)
32 .attr("id", item.id)
33 .attr("node", item.node)
34 .attr("publisher", item.publisher.and_then(|publisher| Some(String::from(publisher))))
35 .append(item.payload)
36 .build()
37 }
38}
39
40impl IntoElements for Item {
41 fn into_elements(self, emitter: &mut ElementEmitter) {
42 emitter.append_child(self.into());
43 }
44}
45
46generate_attribute!(Subscription, "subscription", {
47 None => "none",
48 Pending => "pending",
49 Subscribed => "subscribed",
50 Unconfigured => "unconfigured",
51}, Default = None);
52
53#[derive(Debug, Clone)]
54pub enum PubSubEvent {
55 /*
56 Collection {
57 },
58 */
59 Configuration {
60 node: String,
61 form: Option<DataForm>,
62 },
63 Delete {
64 node: String,
65 redirect: Option<String>,
66 },
67 EmptyItems {
68 node: String,
69 },
70 PublishedItems {
71 node: String,
72 items: Vec<Item>,
73 },
74 RetractedItems {
75 node: String,
76 items: Vec<String>,
77 },
78 Purge {
79 node: String,
80 },
81 Subscription {
82 node: String,
83 expiry: Option<DateTime<FixedOffset>>,
84 jid: Option<Jid>,
85 subid: Option<String>,
86 subscription: Option<Subscription>,
87 },
88}
89
90fn parse_items(elem: Element, node: String) -> Result<PubSubEvent, Error> {
91 let mut is_retract = None;
92 let mut items = vec!();
93 let mut retracts = vec!();
94 for child in elem.children() {
95 if child.is("item", ns::PUBSUB_EVENT) {
96 match is_retract {
97 None => is_retract = Some(false),
98 Some(false) => (),
99 Some(true) => return Err(Error::ParseError("Mix of item and retract in items element.")),
100 }
101 let mut payloads = child.children().cloned().collect::<Vec<_>>();
102 let payload = payloads.pop();
103 if !payloads.is_empty() {
104 return Err(Error::ParseError("More than a single payload in item element."));
105 }
106 let item = Item {
107 payload,
108 id: get_attr!(child, "id", optional),
109 node: get_attr!(child, "node", optional),
110 publisher: get_attr!(child, "publisher", optional),
111 };
112 items.push(item);
113 } else if child.is("retract", ns::PUBSUB_EVENT) {
114 match is_retract {
115 None => is_retract = Some(true),
116 Some(true) => (),
117 Some(false) => return Err(Error::ParseError("Mix of item and retract in items element.")),
118 }
119 for _ in child.children() {
120 return Err(Error::ParseError("Unknown child in retract element."));
121 }
122 for (attr, _) in child.attrs() {
123 if attr != "id" {
124 return Err(Error::ParseError("Unknown attribute in retract element."));
125 }
126 }
127 let id = get_attr!(child, "id", required);
128 retracts.push(id);
129 } else {
130 return Err(Error::ParseError("Invalid child in items element."));
131 }
132 }
133 Ok(match is_retract {
134 None => PubSubEvent::EmptyItems { node },
135 Some(false) => PubSubEvent::PublishedItems { node, items },
136 Some(true) => PubSubEvent::RetractedItems { node, items: retracts },
137 })
138}
139
140impl TryFrom<Element> for PubSubEvent {
141 type Err = Error;
142
143 fn try_from(elem: Element) -> Result<PubSubEvent, Error> {
144 if !elem.is("event", ns::PUBSUB_EVENT) {
145 return Err(Error::ParseError("This is not an event element."));
146 }
147 for _ in elem.attrs() {
148 return Err(Error::ParseError("Unknown attribute in event element."));
149 }
150 let mut payload = None;
151 for child in elem.children() {
152 /*
153 for (attr, _) in child.attrs() {
154 if attr != "node" {
155 return Err(Error::ParseError("Unknown attribute in items element."));
156 }
157 }
158 */
159 let node = get_attr!(child, "node", required);
160 if child.is("configuration", ns::PUBSUB_EVENT) {
161 let mut payloads = child.children().cloned().collect::<Vec<_>>();
162 let item = payloads.pop();
163 if !payloads.is_empty() {
164 return Err(Error::ParseError("More than a single payload in configuration element."));
165 }
166 let form = match item {
167 None => None,
168 Some(payload) => Some(DataForm::try_from(payload)?),
169 };
170 payload = Some(PubSubEvent::Configuration { node, form });
171 } else if child.is("delete", ns::PUBSUB_EVENT) {
172 let mut redirect = None;
173 for item in child.children() {
174 if item.is("redirect", ns::PUBSUB_EVENT) {
175 if redirect.is_some() {
176 return Err(Error::ParseError("More than one redirect in delete element."));
177 }
178 let uri = get_attr!(item, "uri", required);
179 redirect = Some(uri);
180 } else {
181 return Err(Error::ParseError("Unknown child in delete element."));
182 }
183 }
184 payload = Some(PubSubEvent::Delete { node, redirect });
185 } else if child.is("items", ns::PUBSUB_EVENT) {
186 payload = Some(parse_items(child.clone(), node)?);
187 } else if child.is("purge", ns::PUBSUB_EVENT) {
188 for _ in child.children() {
189 return Err(Error::ParseError("Unknown child in purge element."));
190 }
191 payload = Some(PubSubEvent::Purge { node });
192 } else if child.is("subscription", ns::PUBSUB_EVENT) {
193 for _ in child.children() {
194 return Err(Error::ParseError("Unknown child in purge element."));
195 }
196 payload = Some(PubSubEvent::Subscription {
197 node: node,
198 expiry: get_attr!(child, "expiry", optional),
199 jid: get_attr!(child, "jid", optional),
200 subid: get_attr!(child, "subid", optional),
201 subscription: get_attr!(child, "subscription", optional),
202 });
203 } else {
204 return Err(Error::ParseError("Unknown child in event element."));
205 }
206 }
207 Ok(payload.ok_or(Error::ParseError("No payload in event element."))?)
208 }
209}
210
211impl From<PubSubEvent> for Element {
212 fn from(event: PubSubEvent) -> Element {
213 let payload = match event {
214 PubSubEvent::Configuration { node, form } => {
215 Element::builder("configuration")
216 .ns(ns::PUBSUB_EVENT)
217 .attr("node", node)
218 .append(form)
219 .build()
220 },
221 PubSubEvent::Delete { node, redirect } => {
222 Element::builder("purge")
223 .ns(ns::PUBSUB_EVENT)
224 .attr("node", node)
225 .append(redirect.and_then(|redirect| {
226 Some(Element::builder("redirect")
227 .ns(ns::PUBSUB_EVENT)
228 .attr("uri", redirect)
229 .build())
230 }))
231 .build()
232 },
233 PubSubEvent::EmptyItems { node } => {
234 Element::builder("items")
235 .ns(ns::PUBSUB_EVENT)
236 .attr("node", node)
237 .build()
238 },
239 PubSubEvent::PublishedItems { node, items } => {
240 Element::builder("items")
241 .ns(ns::PUBSUB_EVENT)
242 .attr("node", node)
243 .append(items)
244 .build()
245 },
246 PubSubEvent::RetractedItems { node, items } => {
247 Element::builder("items")
248 .ns(ns::PUBSUB_EVENT)
249 .attr("node", node)
250 .append(items)
251 .build()
252 },
253 PubSubEvent::Purge { node } => {
254 Element::builder("purge")
255 .ns(ns::PUBSUB_EVENT)
256 .attr("node", node)
257 .build()
258 },
259 PubSubEvent::Subscription { node, expiry, jid, subid, subscription } => {
260 Element::builder("subscription")
261 .ns(ns::PUBSUB_EVENT)
262 .attr("node", node)
263 .attr("expiry", expiry.and_then(|expiry| Some(expiry.to_rfc3339())))
264 .attr("jid", jid.and_then(|jid| Some(String::from(jid))))
265 .attr("subid", subid)
266 .attr("subscription", subscription)
267 .build()
268 },
269 };
270 Element::builder("event")
271 .ns(ns::PUBSUB_EVENT)
272 .append(payload)
273 .build()
274 }
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280 use std::str::FromStr;
281
282 #[test]
283 fn test_simple() {
284 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='coucou'/></event>".parse().unwrap();
285 let event = PubSubEvent::try_from(elem).unwrap();
286 match event {
287 PubSubEvent::EmptyItems { node } => assert_eq!(node, String::from("coucou")),
288 _ => panic!(),
289 }
290 }
291
292 #[test]
293 fn test_simple_items() {
294 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='coucou'><item id='test' node='huh?' publisher='test@coucou'/></items></event>".parse().unwrap();
295 let event = PubSubEvent::try_from(elem).unwrap();
296 match event {
297 PubSubEvent::PublishedItems { node, items } => {
298 assert_eq!(node, String::from("coucou"));
299 assert_eq!(items[0].id, Some(String::from("test")));
300 assert_eq!(items[0].node, Some(String::from("huh?")));
301 assert_eq!(items[0].publisher, Some(Jid::from_str("test@coucou").unwrap()));
302 assert_eq!(items[0].payload, None);
303 },
304 _ => panic!(),
305 }
306 }
307
308 #[test]
309 fn test_simple_pep() {
310 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='something'><item><foreign xmlns='example:namespace'/></item></items></event>".parse().unwrap();
311 let event = PubSubEvent::try_from(elem).unwrap();
312 match event {
313 PubSubEvent::PublishedItems { node, items } => {
314 assert_eq!(node, String::from("something"));
315 assert_eq!(items[0].id, None);
316 assert_eq!(items[0].node, None);
317 assert_eq!(items[0].publisher, None);
318 match items[0].payload {
319 Some(ref elem) => assert!(elem.is("foreign", "example:namespace")),
320 _ => panic!(),
321 }
322 },
323 _ => panic!(),
324 }
325 }
326
327 #[test]
328 fn test_simple_retract() {
329 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><items node='something'><retract id='coucou'/><retract id='test'/></items></event>".parse().unwrap();
330 let event = PubSubEvent::try_from(elem).unwrap();
331 match event {
332 PubSubEvent::RetractedItems { node, items } => {
333 assert_eq!(node, String::from("something"));
334 assert_eq!(items[0], String::from("coucou"));
335 assert_eq!(items[1], String::from("test"));
336 },
337 _ => panic!(),
338 }
339 }
340
341 #[test]
342 fn test_simple_delete() {
343 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><delete node='coucou'><redirect uri='hello'/></delete></event>".parse().unwrap();
344 let event = PubSubEvent::try_from(elem).unwrap();
345 match event {
346 PubSubEvent::Delete { node, redirect } => {
347 assert_eq!(node, String::from("coucou"));
348 assert_eq!(redirect, Some(String::from("hello")));
349 },
350 _ => panic!(),
351 }
352 }
353
354 #[test]
355 fn test_simple_purge() {
356 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><purge node='coucou'/></event>".parse().unwrap();
357 let event = PubSubEvent::try_from(elem).unwrap();
358 match event {
359 PubSubEvent::Purge { node } => {
360 assert_eq!(node, String::from("coucou"));
361 },
362 _ => panic!(),
363 }
364 }
365
366 #[test]
367 fn test_simple_configure() {
368 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><configuration node='coucou'><x xmlns='jabber:x:data' type='result'><field var='FORM_TYPE' type='hidden'><value>http://jabber.org/protocol/pubsub#node_config</value></field></x></configuration></event>".parse().unwrap();
369 let event = PubSubEvent::try_from(elem).unwrap();
370 match event {
371 PubSubEvent::Configuration { node, form: _ } => {
372 assert_eq!(node, String::from("coucou"));
373 //assert_eq!(form.type_, Result_);
374 },
375 _ => panic!(),
376 }
377 }
378
379 #[test]
380 fn test_invalid() {
381 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event'><coucou node='test'/></event>".parse().unwrap();
382 let error = PubSubEvent::try_from(elem).unwrap_err();
383 let message = match error {
384 Error::ParseError(string) => string,
385 _ => panic!(),
386 };
387 assert_eq!(message, "Unknown child in event element.");
388 }
389
390 #[test]
391 fn test_invalid_attribute() {
392 let elem: Element = "<event xmlns='http://jabber.org/protocol/pubsub#event' coucou=''/>".parse().unwrap();
393 let error = PubSubEvent::try_from(elem).unwrap_err();
394 let message = match error {
395 Error::ParseError(string) => string,
396 _ => panic!(),
397 };
398 assert_eq!(message, "Unknown attribute in event element.");
399 }
400
401 #[test]
402 fn test_ex221_subscription() {
403 let elem: Element = r#"
404<event xmlns='http://jabber.org/protocol/pubsub#event'>
405 <subscription
406 expiry='2006-02-28T23:59:59+00:00'
407 jid='francisco@denmark.lit'
408 node='princely_musings'
409 subid='ba49252aaa4f5d320c24d3766f0bdcade78c78d3'
410 subscription='subscribed'/>
411</event>
412"#.parse().unwrap();
413 let event = PubSubEvent::try_from(elem.clone()).unwrap();
414 match event.clone() {
415 PubSubEvent::Subscription { node, expiry, jid, subid, subscription } => {
416 assert_eq!(node, String::from("princely_musings"));
417 assert_eq!(subid, Some(String::from("ba49252aaa4f5d320c24d3766f0bdcade78c78d3")));
418 assert_eq!(subscription, Some(Subscription::Subscribed));
419 assert_eq!(jid, Some(Jid::from_str("francisco@denmark.lit").unwrap()));
420 assert_eq!(expiry, Some("2006-02-28T23:59:59Z".parse().unwrap()));
421 },
422 _ => panic!(),
423 }
424
425 let elem2: Element = event.into();
426 assert_eq!(elem, elem2);
427 }
428}