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