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 check_self!(root, "presence", DEFAULT_NS);
252 let mut show = None;
253 let mut priority = None;
254 let mut presence = Presence {
255 from: get_attr!(root, "from", optional),
256 to: get_attr!(root, "to", optional),
257 id: get_attr!(root, "id", optional),
258 type_: get_attr!(root, "type", default),
259 show: Show::None,
260 statuses: BTreeMap::new(),
261 priority: 0i8,
262 payloads: vec!(),
263 };
264 for elem in root.children() {
265 if elem.is("show", ns::DEFAULT_NS) {
266 if show.is_some() {
267 return Err(Error::ParseError("More than one show element in a presence."));
268 }
269 check_no_attributes!(elem, "show");
270 check_no_children!(elem, "show");
271 show = Some(Show::from_str(elem.text().as_ref())?);
272 } else if elem.is("status", ns::DEFAULT_NS) {
273 check_no_unknown_attributes!(elem, "status", ["xml:lang"]);
274 check_no_children!(elem, "status");
275 let lang = get_attr!(elem, "xml:lang", default);
276 if presence.statuses.insert(lang, elem.text()).is_some() {
277 return Err(Error::ParseError("Status element present twice for the same xml:lang."));
278 }
279 } else if elem.is("priority", ns::DEFAULT_NS) {
280 if priority.is_some() {
281 return Err(Error::ParseError("More than one priority element in a presence."));
282 }
283 check_no_attributes!(elem, "priority");
284 check_no_children!(elem, "priority");
285 priority = Some(Priority::from_str(elem.text().as_ref())?);
286 } else {
287 presence.payloads.push(elem.clone());
288 }
289 }
290 if let Some(show) = show {
291 presence.show = show;
292 }
293 if let Some(priority) = priority {
294 presence.priority = priority;
295 }
296 Ok(presence)
297 }
298}
299
300impl From<Presence> for Element {
301 fn from(presence: Presence) -> Element {
302 Element::builder("presence")
303 .ns(ns::DEFAULT_NS)
304 .attr("from", presence.from)
305 .attr("to", presence.to)
306 .attr("id", presence.id)
307 .attr("type", presence.type_)
308 .append(presence.show)
309 .append(presence.statuses.into_iter().map(|(lang, status)| {
310 Element::builder("status")
311 .attr("xml:lang", match lang.as_ref() {
312 "" => None,
313 lang => Some(lang),
314 })
315 .append(status)
316 .build()
317 }).collect::<Vec<_>>())
318 .append(if presence.priority == 0 { None } else { Some(format!("{}", presence.priority)) })
319 .append(presence.payloads)
320 .build()
321 }
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327 use compare_elements::NamespaceAwareCompare;
328
329 #[test]
330 fn test_simple() {
331 #[cfg(not(feature = "component"))]
332 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
333 #[cfg(feature = "component")]
334 let elem: Element = "<presence xmlns='jabber:component:accept'/>".parse().unwrap();
335 let presence = Presence::try_from(elem).unwrap();
336 assert_eq!(presence.from, None);
337 assert_eq!(presence.to, None);
338 assert_eq!(presence.id, None);
339 assert_eq!(presence.type_, Type::None);
340 assert!(presence.payloads.is_empty());
341 }
342
343 #[test]
344 fn test_serialise() {
345 #[cfg(not(feature = "component"))]
346 let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>/>".parse().unwrap();
347 #[cfg(feature = "component")]
348 let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>/>".parse().unwrap();
349 let presence = Presence::new(Type::Unavailable);
350 let elem2 = presence.into();
351 assert!(elem.compare_to(&elem2));
352 }
353
354 #[test]
355 fn test_show() {
356 #[cfg(not(feature = "component"))]
357 let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>".parse().unwrap();
358 #[cfg(feature = "component")]
359 let elem: Element = "<presence xmlns='jabber:component:accept'><show>chat</show></presence>".parse().unwrap();
360 let presence = Presence::try_from(elem).unwrap();
361 assert_eq!(presence.payloads.len(), 0);
362 assert_eq!(presence.show, Show::Chat);
363 }
364
365 #[test]
366 fn test_missing_show_value() {
367 #[cfg(not(feature = "component"))]
368 let elem: Element = "<presence xmlns='jabber:client'><show/></presence>".parse().unwrap();
369 #[cfg(feature = "component")]
370 let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>".parse().unwrap();
371 let error = Presence::try_from(elem).unwrap_err();
372 let message = match error {
373 Error::ParseError(string) => string,
374 _ => panic!(),
375 };
376 assert_eq!(message, "Invalid value for show.");
377 }
378
379 #[test]
380 fn test_invalid_show() {
381 // "online" used to be a pretty common mistake.
382 #[cfg(not(feature = "component"))]
383 let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>".parse().unwrap();
384 #[cfg(feature = "component")]
385 let elem: Element = "<presence xmlns='jabber:component:accept'><show>online</show></presence>".parse().unwrap();
386 let error = Presence::try_from(elem).unwrap_err();
387 let message = match error {
388 Error::ParseError(string) => string,
389 _ => panic!(),
390 };
391 assert_eq!(message, "Invalid value for show.");
392 }
393
394 #[test]
395 fn test_empty_status() {
396 #[cfg(not(feature = "component"))]
397 let elem: Element = "<presence xmlns='jabber:client'><status/></presence>".parse().unwrap();
398 #[cfg(feature = "component")]
399 let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>".parse().unwrap();
400 let presence = Presence::try_from(elem).unwrap();
401 assert_eq!(presence.payloads.len(), 0);
402 assert_eq!(presence.statuses.len(), 1);
403 assert_eq!(presence.statuses[""], "");
404 }
405
406 #[test]
407 fn test_status() {
408 #[cfg(not(feature = "component"))]
409 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>".parse().unwrap();
410 #[cfg(feature = "component")]
411 let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>".parse().unwrap();
412 let presence = Presence::try_from(elem).unwrap();
413 assert_eq!(presence.payloads.len(), 0);
414 assert_eq!(presence.statuses.len(), 1);
415 assert_eq!(presence.statuses[""], "Here!");
416 }
417
418 #[test]
419 fn test_multiple_statuses() {
420 #[cfg(not(feature = "component"))]
421 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
422 #[cfg(feature = "component")]
423 let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
424 let presence = Presence::try_from(elem).unwrap();
425 assert_eq!(presence.payloads.len(), 0);
426 assert_eq!(presence.statuses.len(), 2);
427 assert_eq!(presence.statuses[""], "Here!");
428 assert_eq!(presence.statuses["fr"], "LĂ !");
429 }
430
431 #[test]
432 fn test_invalid_multiple_statuses() {
433 #[cfg(not(feature = "component"))]
434 let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
435 #[cfg(feature = "component")]
436 let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
437 let error = Presence::try_from(elem).unwrap_err();
438 let message = match error {
439 Error::ParseError(string) => string,
440 _ => panic!(),
441 };
442 assert_eq!(message, "Status element present twice for the same xml:lang.");
443 }
444
445 #[test]
446 fn test_priority() {
447 #[cfg(not(feature = "component"))]
448 let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>".parse().unwrap();
449 #[cfg(feature = "component")]
450 let elem: Element = "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>".parse().unwrap();
451 let presence = Presence::try_from(elem).unwrap();
452 assert_eq!(presence.payloads.len(), 0);
453 assert_eq!(presence.priority, -1i8);
454 }
455
456 #[test]
457 fn test_invalid_priority() {
458 #[cfg(not(feature = "component"))]
459 let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>".parse().unwrap();
460 #[cfg(feature = "component")]
461 let elem: Element = "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>".parse().unwrap();
462 let error = Presence::try_from(elem).unwrap_err();
463 match error {
464 Error::ParseIntError(_) => (),
465 _ => panic!(),
466 };
467 }
468
469 #[test]
470 fn test_unknown_child() {
471 #[cfg(not(feature = "component"))]
472 let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>".parse().unwrap();
473 #[cfg(feature = "component")]
474 let elem: Element = "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>".parse().unwrap();
475 let presence = Presence::try_from(elem).unwrap();
476 let payload = &presence.payloads[0];
477 assert!(payload.is("test", "invalid"));
478 }
479
480 #[test]
481 fn test_invalid_status_child() {
482 #[cfg(not(feature = "component"))]
483 let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>".parse().unwrap();
484 #[cfg(feature = "component")]
485 let elem: Element = "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>".parse().unwrap();
486 let error = Presence::try_from(elem).unwrap_err();
487 let message = match error {
488 Error::ParseError(string) => string,
489 _ => panic!(),
490 };
491 assert_eq!(message, "Unknown child in status element.");
492 }
493
494 #[test]
495 fn test_invalid_attribute() {
496 #[cfg(not(feature = "component"))]
497 let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>".parse().unwrap();
498 #[cfg(feature = "component")]
499 let elem: Element = "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>".parse().unwrap();
500 let error = Presence::try_from(elem).unwrap_err();
501 let message = match error {
502 Error::ParseError(string) => string,
503 _ => panic!(),
504 };
505 assert_eq!(message, "Unknown attribute in status element.");
506 }
507
508 #[test]
509 fn test_serialise_status() {
510 let status = Status::from("Hello world!");
511 let mut presence = Presence::new(Type::Unavailable);
512 presence.statuses.insert(String::from(""), status);
513 let elem: Element = presence.into();
514 assert!(elem.is("presence", ns::DEFAULT_NS));
515 assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
516 }
517}