1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8use try_from::TryFrom;
9use std::str::FromStr;
10use std::collections::BTreeMap;
11
12use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
13
14use jid::Jid;
15
16use error::Error;
17
18use ns;
19
20use stanza_error::StanzaError;
21use muc::Muc;
22use caps::Caps;
23use delay::Delay;
24use idle::Idle;
25use ecaps2::ECaps2;
26
27#[derive(Debug, Clone, PartialEq)]
28pub enum Show {
29 None,
30 Away,
31 Chat,
32 Dnd,
33 Xa,
34}
35
36impl Default for Show {
37 fn default() -> Show {
38 Show::None
39 }
40}
41
42impl FromStr for Show {
43 type Err = Error;
44
45 fn from_str(s: &str) -> Result<Show, Error> {
46 Ok(match s {
47 "away" => Show::Away,
48 "chat" => Show::Chat,
49 "dnd" => Show::Dnd,
50 "xa" => Show::Xa,
51
52 _ => return Err(Error::ParseError("Invalid value for show.")),
53 })
54 }
55}
56
57impl IntoElements for Show {
58 fn into_elements(self, emitter: &mut ElementEmitter) {
59 if self == Show::None {
60 return;
61 }
62 emitter.append_child(
63 Element::builder("show")
64 .append(match self {
65 Show::None => unreachable!(),
66 Show::Away => Some("away"),
67 Show::Chat => Some("chat"),
68 Show::Dnd => Some("dnd"),
69 Show::Xa => Some("xa"),
70 })
71 .build())
72 }
73}
74
75pub type Lang = String;
76pub type Status = String;
77
78pub type Priority = i8;
79
80/// Lists every known payload of a `<presence/>`.
81#[derive(Debug, Clone)]
82pub enum PresencePayload {
83 StanzaError(StanzaError),
84 Muc(Muc),
85 Caps(Caps),
86 Delay(Delay),
87 Idle(Idle),
88 ECaps2(ECaps2),
89
90 Unknown(Element),
91}
92
93impl TryFrom<Element> for PresencePayload {
94 type Err = Error;
95
96 fn try_from(elem: Element) -> Result<PresencePayload, Error> {
97 Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
98 ("error", ns::DEFAULT_NS) => PresencePayload::StanzaError(StanzaError::try_from(elem)?),
99
100 // XEP-0045
101 ("x", ns::MUC) => PresencePayload::Muc(Muc::try_from(elem)?),
102
103 // XEP-0115
104 ("c", ns::CAPS) => PresencePayload::Caps(Caps::try_from(elem)?),
105
106 // XEP-0203
107 ("delay", ns::DELAY) => PresencePayload::Delay(Delay::try_from(elem)?),
108
109 // XEP-0319
110 ("idle", ns::IDLE) => PresencePayload::Idle(Idle::try_from(elem)?),
111
112 // XEP-0390
113 ("c", ns::ECAPS2) => PresencePayload::ECaps2(ECaps2::try_from(elem)?),
114
115 _ => PresencePayload::Unknown(elem),
116 })
117 }
118}
119
120impl From<PresencePayload> for Element {
121 fn from(payload: PresencePayload) -> Element {
122 match payload {
123 PresencePayload::StanzaError(stanza_error) => stanza_error.into(),
124 PresencePayload::Muc(muc) => muc.into(),
125 PresencePayload::Caps(caps) => caps.into(),
126 PresencePayload::Delay(delay) => delay.into(),
127 PresencePayload::Idle(idle) => idle.into(),
128 PresencePayload::ECaps2(ecaps2) => ecaps2.into(),
129
130 PresencePayload::Unknown(elem) => elem,
131 }
132 }
133}
134
135#[derive(Debug, Clone, PartialEq)]
136pub enum Type {
137 /// This value is not an acceptable 'type' attribute, it is only used
138 /// internally to signal the absence of 'type'.
139 None,
140 Error,
141 Probe,
142 Subscribe,
143 Subscribed,
144 Unavailable,
145 Unsubscribe,
146 Unsubscribed,
147}
148
149impl Default for Type {
150 fn default() -> Type {
151 Type::None
152 }
153}
154
155impl FromStr for Type {
156 type Err = Error;
157
158 fn from_str(s: &str) -> Result<Type, Error> {
159 Ok(match s {
160 "error" => Type::Error,
161 "probe" => Type::Probe,
162 "subscribe" => Type::Subscribe,
163 "subscribed" => Type::Subscribed,
164 "unavailable" => Type::Unavailable,
165 "unsubscribe" => Type::Unsubscribe,
166 "unsubscribed" => Type::Unsubscribed,
167
168 _ => return Err(Error::ParseError("Invalid 'type' attribute on presence element.")),
169 })
170 }
171}
172
173impl IntoAttributeValue for Type {
174 fn into_attribute_value(self) -> Option<String> {
175 Some(match self {
176 Type::None => return None,
177
178 Type::Error => "error",
179 Type::Probe => "probe",
180 Type::Subscribe => "subscribe",
181 Type::Subscribed => "subscribed",
182 Type::Unavailable => "unavailable",
183 Type::Unsubscribe => "unsubscribe",
184 Type::Unsubscribed => "unsubscribed",
185 }.to_owned())
186 }
187}
188
189/// The main structure representing the `<presence/>` stanza.
190#[derive(Debug, Clone)]
191pub struct Presence {
192 pub from: Option<Jid>,
193 pub to: Option<Jid>,
194 pub id: Option<String>,
195 pub type_: Type,
196 pub show: Show,
197 pub statuses: BTreeMap<Lang, Status>,
198 pub priority: Priority,
199 pub payloads: Vec<Element>,
200}
201
202impl Presence {
203 pub fn new(type_: Type) -> Presence {
204 Presence {
205 from: None,
206 to: None,
207 id: None,
208 type_: type_,
209 show: Show::None,
210 statuses: BTreeMap::new(),
211 priority: 0i8,
212 payloads: vec!(),
213 }
214 }
215
216 pub fn with_from(mut self, from: Option<Jid>) -> Presence {
217 self.from = from;
218 self
219 }
220
221 pub fn with_to(mut self, to: Option<Jid>) -> Presence {
222 self.to = to;
223 self
224 }
225
226 pub fn with_id(mut self, id: Option<String>) -> Presence {
227 self.id = id;
228 self
229 }
230
231 pub fn with_show(mut self, show: Show) -> Presence {
232 self.show = show;
233 self
234 }
235
236 pub fn with_priority(mut self, priority: i8) -> Presence {
237 self.priority = priority;
238 self
239 }
240
241 pub fn with_payloads(mut self, payloads: Vec<Element>) -> Presence {
242 self.payloads = payloads;
243 self
244 }
245}
246
247impl TryFrom<Element> for Presence {
248 type Err = Error;
249
250 fn try_from(root: Element) -> Result<Presence, Error> {
251 if !root.is("presence", ns::DEFAULT_NS) {
252 return Err(Error::ParseError("This is not a presence element."));
253 }
254 let mut show = None;
255 let mut priority = None;
256 let mut presence = Presence {
257 from: get_attr!(root, "from", optional),
258 to: get_attr!(root, "to", optional),
259 id: get_attr!(root, "id", optional),
260 type_: get_attr!(root, "type", default),
261 show: Show::None,
262 statuses: BTreeMap::new(),
263 priority: 0i8,
264 payloads: vec!(),
265 };
266 for elem in root.children() {
267 if elem.is("show", ns::DEFAULT_NS) {
268 if show.is_some() {
269 return Err(Error::ParseError("More than one show element in a presence."));
270 }
271 for _ in elem.children() {
272 return Err(Error::ParseError("Unknown child in show element."));
273 }
274 for _ in elem.attrs() {
275 return Err(Error::ParseError("Unknown attribute in show element."));
276 }
277 show = Some(Show::from_str(elem.text().as_ref())?);
278 } else if elem.is("status", ns::DEFAULT_NS) {
279 for _ in elem.children() {
280 return Err(Error::ParseError("Unknown child in status element."));
281 }
282 for (attr, _) in elem.attrs() {
283 if attr != "xml:lang" {
284 return Err(Error::ParseError("Unknown attribute in status element."));
285 }
286 }
287 let lang = get_attr!(elem, "xml:lang", default);
288 if presence.statuses.insert(lang, elem.text()).is_some() {
289 return Err(Error::ParseError("Status element present twice for the same xml:lang."));
290 }
291 } else if elem.is("priority", ns::DEFAULT_NS) {
292 if priority.is_some() {
293 return Err(Error::ParseError("More than one priority element in a presence."));
294 }
295 for _ in elem.children() {
296 return Err(Error::ParseError("Unknown child in priority element."));
297 }
298 for _ in elem.attrs() {
299 return Err(Error::ParseError("Unknown attribute in priority element."));
300 }
301 priority = Some(Priority::from_str(elem.text().as_ref())?);
302 } else {
303 presence.payloads.push(elem.clone());
304 }
305 }
306 if let Some(show) = show {
307 presence.show = show;
308 }
309 if let Some(priority) = priority {
310 presence.priority = priority;
311 }
312 Ok(presence)
313 }
314}
315
316impl From<Presence> for Element {
317 fn from(presence: Presence) -> Element {
318 Element::builder("presence")
319 .ns(ns::DEFAULT_NS)
320 .attr("from", presence.from)
321 .attr("to", presence.to)
322 .attr("id", presence.id)
323 .attr("type", presence.type_)
324 .append(presence.show)
325 .append(presence.statuses.into_iter().map(|(lang, status)| {
326 Element::builder("status")
327 .attr("xml:lang", match lang.as_ref() {
328 "" => None,
329 lang => Some(lang),
330 })
331 .append(status)
332 .build()
333 }).collect::<Vec<_>>())
334 .append(if presence.priority == 0 { None } else { Some(format!("{}", presence.priority)) })
335 .append(presence.payloads)
336 .build()
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use compare_elements::NamespaceAwareCompare;
344
345 #[test]
346 fn test_simple() {
347 #[cfg(not(feature = "component"))]
348 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
349 #[cfg(feature = "component")]
350 let elem: Element = "<presence xmlns='jabber:component:accept'/>".parse().unwrap();
351 let presence = Presence::try_from(elem).unwrap();
352 assert_eq!(presence.from, None);
353 assert_eq!(presence.to, None);
354 assert_eq!(presence.id, None);
355 assert_eq!(presence.type_, Type::None);
356 assert!(presence.payloads.is_empty());
357 }
358
359 #[test]
360 fn test_serialise() {
361 #[cfg(not(feature = "component"))]
362 let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>/>".parse().unwrap();
363 #[cfg(feature = "component")]
364 let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>/>".parse().unwrap();
365 let presence = Presence::new(Type::Unavailable);
366 let elem2 = presence.into();
367 assert!(elem.compare_to(&elem2));
368 }
369
370 #[test]
371 fn test_show() {
372 #[cfg(not(feature = "component"))]
373 let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>".parse().unwrap();
374 #[cfg(feature = "component")]
375 let elem: Element = "<presence xmlns='jabber:component:accept'><show>chat</show></presence>".parse().unwrap();
376 let presence = Presence::try_from(elem).unwrap();
377 assert_eq!(presence.payloads.len(), 0);
378 assert_eq!(presence.show, Show::Chat);
379 }
380
381 #[test]
382 fn test_missing_show_value() {
383 #[cfg(not(feature = "component"))]
384 let elem: Element = "<presence xmlns='jabber:client'><show/></presence>".parse().unwrap();
385 #[cfg(feature = "component")]
386 let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>".parse().unwrap();
387 let error = Presence::try_from(elem).unwrap_err();
388 let message = match error {
389 Error::ParseError(string) => string,
390 _ => panic!(),
391 };
392 assert_eq!(message, "Invalid value for show.");
393 }
394
395 #[test]
396 fn test_invalid_show() {
397 // "online" used to be a pretty common mistake.
398 #[cfg(not(feature = "component"))]
399 let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>".parse().unwrap();
400 #[cfg(feature = "component")]
401 let elem: Element = "<presence xmlns='jabber:component:accept'><show>online</show></presence>".parse().unwrap();
402 let error = Presence::try_from(elem).unwrap_err();
403 let message = match error {
404 Error::ParseError(string) => string,
405 _ => panic!(),
406 };
407 assert_eq!(message, "Invalid value for show.");
408 }
409
410 #[test]
411 fn test_empty_status() {
412 #[cfg(not(feature = "component"))]
413 let elem: Element = "<presence xmlns='jabber:client'><status/></presence>".parse().unwrap();
414 #[cfg(feature = "component")]
415 let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>".parse().unwrap();
416 let presence = Presence::try_from(elem).unwrap();
417 assert_eq!(presence.payloads.len(), 0);
418 assert_eq!(presence.statuses.len(), 1);
419 assert_eq!(presence.statuses[""], "");
420 }
421
422 #[test]
423 fn test_status() {
424 #[cfg(not(feature = "component"))]
425 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>".parse().unwrap();
426 #[cfg(feature = "component")]
427 let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>".parse().unwrap();
428 let presence = Presence::try_from(elem).unwrap();
429 assert_eq!(presence.payloads.len(), 0);
430 assert_eq!(presence.statuses.len(), 1);
431 assert_eq!(presence.statuses[""], "Here!");
432 }
433
434 #[test]
435 fn test_multiple_statuses() {
436 #[cfg(not(feature = "component"))]
437 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
438 #[cfg(feature = "component")]
439 let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
440 let presence = Presence::try_from(elem).unwrap();
441 assert_eq!(presence.payloads.len(), 0);
442 assert_eq!(presence.statuses.len(), 2);
443 assert_eq!(presence.statuses[""], "Here!");
444 assert_eq!(presence.statuses["fr"], "LĂ !");
445 }
446
447 #[test]
448 fn test_invalid_multiple_statuses() {
449 #[cfg(not(feature = "component"))]
450 let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
451 #[cfg(feature = "component")]
452 let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
453 let error = Presence::try_from(elem).unwrap_err();
454 let message = match error {
455 Error::ParseError(string) => string,
456 _ => panic!(),
457 };
458 assert_eq!(message, "Status element present twice for the same xml:lang.");
459 }
460
461 #[test]
462 fn test_priority() {
463 #[cfg(not(feature = "component"))]
464 let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>".parse().unwrap();
465 #[cfg(feature = "component")]
466 let elem: Element = "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>".parse().unwrap();
467 let presence = Presence::try_from(elem).unwrap();
468 assert_eq!(presence.payloads.len(), 0);
469 assert_eq!(presence.priority, -1i8);
470 }
471
472 #[test]
473 fn test_invalid_priority() {
474 #[cfg(not(feature = "component"))]
475 let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>".parse().unwrap();
476 #[cfg(feature = "component")]
477 let elem: Element = "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>".parse().unwrap();
478 let error = Presence::try_from(elem).unwrap_err();
479 match error {
480 Error::ParseIntError(_) => (),
481 _ => panic!(),
482 };
483 }
484
485 #[test]
486 fn test_unknown_child() {
487 #[cfg(not(feature = "component"))]
488 let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>".parse().unwrap();
489 #[cfg(feature = "component")]
490 let elem: Element = "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>".parse().unwrap();
491 let presence = Presence::try_from(elem).unwrap();
492 let payload = &presence.payloads[0];
493 assert!(payload.is("test", "invalid"));
494 }
495
496 #[test]
497 fn test_invalid_status_child() {
498 #[cfg(not(feature = "component"))]
499 let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>".parse().unwrap();
500 #[cfg(feature = "component")]
501 let elem: Element = "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>".parse().unwrap();
502 let error = Presence::try_from(elem).unwrap_err();
503 let message = match error {
504 Error::ParseError(string) => string,
505 _ => panic!(),
506 };
507 assert_eq!(message, "Unknown child in status element.");
508 }
509
510 #[test]
511 fn test_invalid_attribute() {
512 #[cfg(not(feature = "component"))]
513 let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>".parse().unwrap();
514 #[cfg(feature = "component")]
515 let elem: Element = "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>".parse().unwrap();
516 let error = Presence::try_from(elem).unwrap_err();
517 let message = match error {
518 Error::ParseError(string) => string,
519 _ => panic!(),
520 };
521 assert_eq!(message, "Unknown attribute in status element.");
522 }
523
524 #[test]
525 fn test_serialise_status() {
526 let status = Status::from("Hello world!");
527 let mut presence = Presence::new(Type::Unavailable);
528 presence.statuses.insert(String::from(""), status);
529 let elem: Element = presence.into();
530 assert!(elem.is("presence", ns::DEFAULT_NS));
531 assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
532 }
533}