1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2// Copyright (c) 2017 Maxime “pep” Buquet <pep@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 crate::util::error::Error;
9use crate::ns;
10use jid::Jid;
11use minidom::{Element, IntoAttributeValue, Node};
12use std::collections::BTreeMap;
13use std::str::FromStr;
14use std::convert::TryFrom;
15
16/// Should be implemented on every known payload of a `<presence/>`.
17pub trait PresencePayload: TryFrom<Element> + Into<Element> {}
18
19/// Specifies the availability of an entity or resource.
20#[derive(Debug, Clone, PartialEq)]
21pub enum Show {
22 /// The entity or resource is temporarily away.
23 Away,
24
25 /// The entity or resource is actively interested in chatting.
26 Chat,
27
28 /// The entity or resource is busy (dnd = "Do Not Disturb").
29 Dnd,
30
31 /// The entity or resource is away for an extended period (xa = "eXtended
32 /// Away").
33 Xa,
34}
35
36impl FromStr for Show {
37 type Err = Error;
38
39 fn from_str(s: &str) -> Result<Show, Error> {
40 Ok(match s {
41 "away" => Show::Away,
42 "chat" => Show::Chat,
43 "dnd" => Show::Dnd,
44 "xa" => Show::Xa,
45
46 _ => return Err(Error::ParseError("Invalid value for show.")),
47 })
48 }
49}
50
51impl Into<Node> for Show {
52 fn into(self) -> Node {
53 Element::builder("show")
54 .append(match self {
55 Show::Away => "away",
56 Show::Chat => "chat",
57 Show::Dnd => "dnd",
58 Show::Xa => "xa",
59 })
60 .build()
61 .into()
62 }
63}
64
65type Lang = String;
66type Status = String;
67
68type Priority = i8;
69
70///
71#[derive(Debug, Clone, PartialEq)]
72pub enum Type {
73 /// This value is not an acceptable 'type' attribute, it is only used
74 /// internally to signal the absence of 'type'.
75 None,
76
77 /// An error has occurred regarding processing of a previously sent
78 /// presence stanza; if the presence stanza is of type "error", it MUST
79 /// include an <error/> child element (refer to [XMPP‑CORE]).
80 Error,
81
82 /// A request for an entity's current presence; SHOULD be generated only by
83 /// a server on behalf of a user.
84 Probe,
85
86 /// The sender wishes to subscribe to the recipient's presence.
87 Subscribe,
88
89 /// The sender has allowed the recipient to receive their presence.
90 Subscribed,
91
92 /// The sender is no longer available for communication.
93 Unavailable,
94
95 /// The sender is unsubscribing from the receiver's presence.
96 Unsubscribe,
97
98 /// The subscription request has been denied or a previously granted
99 /// subscription has been canceled.
100 Unsubscribed,
101}
102
103impl Default for Type {
104 fn default() -> Type {
105 Type::None
106 }
107}
108
109impl FromStr for Type {
110 type Err = Error;
111
112 fn from_str(s: &str) -> Result<Type, Error> {
113 Ok(match s {
114 "error" => Type::Error,
115 "probe" => Type::Probe,
116 "subscribe" => Type::Subscribe,
117 "subscribed" => Type::Subscribed,
118 "unavailable" => Type::Unavailable,
119 "unsubscribe" => Type::Unsubscribe,
120 "unsubscribed" => Type::Unsubscribed,
121
122 _ => {
123 return Err(Error::ParseError(
124 "Invalid 'type' attribute on presence element.",
125 ));
126 }
127 })
128 }
129}
130
131impl IntoAttributeValue for Type {
132 fn into_attribute_value(self) -> Option<String> {
133 Some(
134 match self {
135 Type::None => return None,
136
137 Type::Error => "error",
138 Type::Probe => "probe",
139 Type::Subscribe => "subscribe",
140 Type::Subscribed => "subscribed",
141 Type::Unavailable => "unavailable",
142 Type::Unsubscribe => "unsubscribe",
143 Type::Unsubscribed => "unsubscribed",
144 }
145 .to_owned(),
146 )
147 }
148}
149
150/// The main structure representing the `<presence/>` stanza.
151#[derive(Debug, Clone)]
152pub struct Presence {
153 /// The sender of this presence.
154 pub from: Option<Jid>,
155
156 /// The recipient of this presence.
157 pub to: Option<Jid>,
158
159 /// The identifier, unique on this stream, of this stanza.
160 pub id: Option<String>,
161
162 /// The type of this presence stanza.
163 pub type_: Type,
164
165 /// The availability of the sender of this presence.
166 pub show: Option<Show>,
167
168 /// A localised list of statuses defined in this presence.
169 pub statuses: BTreeMap<Lang, Status>,
170
171 /// The sender’s resource priority, if negative it won’t receive messages
172 /// that haven’t been directed to it.
173 pub priority: Priority,
174
175 /// A list of payloads contained in this presence.
176 pub payloads: Vec<Element>,
177}
178
179impl Presence {
180 /// Create a new presence of this type.
181 pub fn new(type_: Type) -> Presence {
182 Presence {
183 from: None,
184 to: None,
185 id: None,
186 type_,
187 show: None,
188 statuses: BTreeMap::new(),
189 priority: 0i8,
190 payloads: vec![],
191 }
192 }
193
194 /// Set the emitter of this presence, this should only be useful for
195 /// servers and components, as clients can only send presences from their
196 /// own resource (which is implicit).
197 pub fn with_from<J: Into<Jid>>(mut self, from: J) -> Presence {
198 self.from = Some(from.into());
199 self
200 }
201
202 /// Set the recipient of this presence, this is only useful for directed
203 /// presences.
204 pub fn with_to<J: Into<Jid>>(mut self, to: J) -> Presence {
205 self.to = Some(to.into());
206 self
207 }
208
209 /// Set the identifier for this presence.
210 pub fn with_id(mut self, id: String) -> Presence {
211 self.id = Some(id);
212 self
213 }
214
215 /// Set the availability information of this presence.
216 pub fn with_show(mut self, show: Show) -> Presence {
217 self.show = Some(show);
218 self
219 }
220
221 /// Set the priority of this presence.
222 pub fn with_priority(mut self, priority: i8) -> Presence {
223 self.priority = priority;
224 self
225 }
226
227 /// Set the payloads of this presence.
228 pub fn with_payloads(mut self, payloads: Vec<Element>) -> Presence {
229 self.payloads = payloads;
230 self
231 }
232
233 /// Set the availability information of this presence.
234 pub fn set_status<L, S>(&mut self, lang: L, status: S)
235 where L: Into<Lang>,
236 S: Into<Status>,
237 {
238 self.statuses.insert(lang.into(), status.into());
239 }
240
241 /// Add a payload to this presence.
242 pub fn add_payload<P: PresencePayload>(&mut self, payload: P) {
243 self.payloads.push(payload.into());
244 }
245}
246
247impl TryFrom<Element> for Presence {
248 type Error = 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", Option),
256 to: get_attr!(root, "to", Option),
257 id: get_attr!(root, "id", Option),
258 type_: get_attr!(root, "type", Default),
259 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(
268 "More than one show element in a presence.",
269 ));
270 }
271 check_no_attributes!(elem, "show");
272 check_no_children!(elem, "show");
273 show = Some(Show::from_str(elem.text().as_ref())?);
274 } else if elem.is("status", ns::DEFAULT_NS) {
275 check_no_unknown_attributes!(elem, "status", ["xml:lang"]);
276 check_no_children!(elem, "status");
277 let lang = get_attr!(elem, "xml:lang", Default);
278 if presence.statuses.insert(lang, elem.text()).is_some() {
279 return Err(Error::ParseError(
280 "Status element present twice for the same xml:lang.",
281 ));
282 }
283 } else if elem.is("priority", ns::DEFAULT_NS) {
284 if priority.is_some() {
285 return Err(Error::ParseError(
286 "More than one priority element in a presence.",
287 ));
288 }
289 check_no_attributes!(elem, "priority");
290 check_no_children!(elem, "priority");
291 priority = Some(Priority::from_str(elem.text().as_ref())?);
292 } else {
293 presence.payloads.push(elem.clone());
294 }
295 }
296 presence.show = show;
297 if let Some(priority) = priority {
298 presence.priority = priority;
299 }
300 Ok(presence)
301 }
302}
303
304impl From<Presence> for Element {
305 fn from(presence: Presence) -> Element {
306 Element::builder("presence")
307 .ns(ns::DEFAULT_NS)
308 .attr("from", presence.from)
309 .attr("to", presence.to)
310 .attr("id", presence.id)
311 .attr("type", presence.type_)
312 .append_all(presence.show.into_iter())
313 .append_all(
314 presence
315 .statuses
316 .into_iter()
317 .map(|(lang, status)| {
318 Element::builder("status")
319 .attr(
320 "xml:lang",
321 match lang.as_ref() {
322 "" => None,
323 lang => Some(lang),
324 },
325 )
326 .append(status)
327 })
328 )
329 .append_all(if presence.priority == 0 {
330 None
331 } else {
332 Some(Element::builder("priority")
333 .append(format!("{}", presence.priority)))
334 })
335 .append_all(presence.payloads.into_iter())
336 .build()
337 }
338}
339
340#[cfg(test)]
341mod tests {
342 use super::*;
343 use crate::util::compare_elements::NamespaceAwareCompare;
344 use jid::{BareJid, FullJid};
345
346 #[cfg(target_pointer_width = "32")]
347 #[test]
348 fn test_size() {
349 assert_size!(Show, 1);
350 assert_size!(Type, 1);
351 assert_size!(Presence, 120);
352 }
353
354 #[cfg(target_pointer_width = "64")]
355 #[test]
356 fn test_size() {
357 assert_size!(Show, 1);
358 assert_size!(Type, 1);
359 assert_size!(Presence, 240);
360 }
361
362 #[test]
363 fn test_simple() {
364 #[cfg(not(feature = "component"))]
365 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
366 #[cfg(feature = "component")]
367 let elem: Element = "<presence xmlns='jabber:component:accept'/>"
368 .parse()
369 .unwrap();
370 let presence = Presence::try_from(elem).unwrap();
371 assert_eq!(presence.from, None);
372 assert_eq!(presence.to, None);
373 assert_eq!(presence.id, None);
374 assert_eq!(presence.type_, Type::None);
375 assert!(presence.payloads.is_empty());
376 }
377
378 #[test]
379 fn test_serialise() {
380 #[cfg(not(feature = "component"))]
381 let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>/>"
382 .parse()
383 .unwrap();
384 #[cfg(feature = "component")]
385 let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>/>"
386 .parse()
387 .unwrap();
388 let presence = Presence::new(Type::Unavailable);
389 let elem2 = presence.into();
390 assert!(elem.compare_to(&elem2));
391 }
392
393 #[test]
394 fn test_show() {
395 #[cfg(not(feature = "component"))]
396 let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>"
397 .parse()
398 .unwrap();
399 #[cfg(feature = "component")]
400 let elem: Element =
401 "<presence xmlns='jabber:component:accept'><show>chat</show></presence>"
402 .parse()
403 .unwrap();
404 let presence = Presence::try_from(elem).unwrap();
405 assert_eq!(presence.payloads.len(), 0);
406 assert_eq!(presence.show, Some(Show::Chat));
407 }
408
409 #[test]
410 fn test_empty_show_value() {
411 #[cfg(not(feature = "component"))]
412 let elem: Element = "<presence xmlns='jabber:client'/>"
413 .parse()
414 .unwrap();
415 #[cfg(feature = "component")]
416 let elem: Element = "<presence xmlns='jabber:component:accept'/>"
417 .parse()
418 .unwrap();
419 let presence = Presence::try_from(elem).unwrap();
420 assert_eq!(presence.show, None);
421 }
422
423 #[test]
424 fn test_missing_show_value() {
425 #[cfg(not(feature = "component"))]
426 let elem: Element = "<presence xmlns='jabber:client'><show/></presence>"
427 .parse()
428 .unwrap();
429 #[cfg(feature = "component")]
430 let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>"
431 .parse()
432 .unwrap();
433 let error = Presence::try_from(elem).unwrap_err();
434 let message = match error {
435 Error::ParseError(string) => string,
436 _ => panic!(),
437 };
438 assert_eq!(message, "Invalid value for show.");
439 }
440
441 #[test]
442 fn test_invalid_show() {
443 // "online" used to be a pretty common mistake.
444 #[cfg(not(feature = "component"))]
445 let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>"
446 .parse()
447 .unwrap();
448 #[cfg(feature = "component")]
449 let elem: Element =
450 "<presence xmlns='jabber:component:accept'><show>online</show></presence>"
451 .parse()
452 .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, "Invalid value for show.");
459 }
460
461 #[test]
462 fn test_empty_status() {
463 #[cfg(not(feature = "component"))]
464 let elem: Element = "<presence xmlns='jabber:client'><status/></presence>"
465 .parse()
466 .unwrap();
467 #[cfg(feature = "component")]
468 let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>"
469 .parse()
470 .unwrap();
471 let presence = Presence::try_from(elem).unwrap();
472 assert_eq!(presence.payloads.len(), 0);
473 assert_eq!(presence.statuses.len(), 1);
474 assert_eq!(presence.statuses[""], "");
475 }
476
477 #[test]
478 fn test_status() {
479 #[cfg(not(feature = "component"))]
480 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>"
481 .parse()
482 .unwrap();
483 #[cfg(feature = "component")]
484 let elem: Element =
485 "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>"
486 .parse()
487 .unwrap();
488 let presence = Presence::try_from(elem).unwrap();
489 assert_eq!(presence.payloads.len(), 0);
490 assert_eq!(presence.statuses.len(), 1);
491 assert_eq!(presence.statuses[""], "Here!");
492 }
493
494 #[test]
495 fn test_multiple_statuses() {
496 #[cfg(not(feature = "component"))]
497 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
498 #[cfg(feature = "component")]
499 let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
500 let presence = Presence::try_from(elem).unwrap();
501 assert_eq!(presence.payloads.len(), 0);
502 assert_eq!(presence.statuses.len(), 2);
503 assert_eq!(presence.statuses[""], "Here!");
504 assert_eq!(presence.statuses["fr"], "LĂ !");
505 }
506
507 #[test]
508 fn test_invalid_multiple_statuses() {
509 #[cfg(not(feature = "component"))]
510 let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
511 #[cfg(feature = "component")]
512 let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
513 let error = Presence::try_from(elem).unwrap_err();
514 let message = match error {
515 Error::ParseError(string) => string,
516 _ => panic!(),
517 };
518 assert_eq!(
519 message,
520 "Status element present twice for the same xml:lang."
521 );
522 }
523
524 #[test]
525 fn test_priority() {
526 #[cfg(not(feature = "component"))]
527 let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>"
528 .parse()
529 .unwrap();
530 #[cfg(feature = "component")]
531 let elem: Element =
532 "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>"
533 .parse()
534 .unwrap();
535 let presence = Presence::try_from(elem).unwrap();
536 assert_eq!(presence.payloads.len(), 0);
537 assert_eq!(presence.priority, -1i8);
538 }
539
540 #[test]
541 fn test_invalid_priority() {
542 #[cfg(not(feature = "component"))]
543 let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>"
544 .parse()
545 .unwrap();
546 #[cfg(feature = "component")]
547 let elem: Element =
548 "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>"
549 .parse()
550 .unwrap();
551 let error = Presence::try_from(elem).unwrap_err();
552 match error {
553 Error::ParseIntError(_) => (),
554 _ => panic!(),
555 };
556 }
557
558 #[test]
559 fn test_unknown_child() {
560 #[cfg(not(feature = "component"))]
561 let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>"
562 .parse()
563 .unwrap();
564 #[cfg(feature = "component")]
565 let elem: Element =
566 "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>"
567 .parse()
568 .unwrap();
569 let presence = Presence::try_from(elem).unwrap();
570 let payload = &presence.payloads[0];
571 assert!(payload.is("test", "invalid"));
572 }
573
574 #[cfg(not(feature = "disable-validation"))]
575 #[test]
576 fn test_invalid_status_child() {
577 #[cfg(not(feature = "component"))]
578 let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>"
579 .parse()
580 .unwrap();
581 #[cfg(feature = "component")]
582 let elem: Element =
583 "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>"
584 .parse()
585 .unwrap();
586 let error = Presence::try_from(elem).unwrap_err();
587 let message = match error {
588 Error::ParseError(string) => string,
589 _ => panic!(),
590 };
591 assert_eq!(message, "Unknown child in status element.");
592 }
593
594 #[cfg(not(feature = "disable-validation"))]
595 #[test]
596 fn test_invalid_attribute() {
597 #[cfg(not(feature = "component"))]
598 let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>"
599 .parse()
600 .unwrap();
601 #[cfg(feature = "component")]
602 let elem: Element =
603 "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>"
604 .parse()
605 .unwrap();
606 let error = Presence::try_from(elem).unwrap_err();
607 let message = match error {
608 Error::ParseError(string) => string,
609 _ => panic!(),
610 };
611 assert_eq!(message, "Unknown attribute in status element.");
612 }
613
614 #[test]
615 fn test_serialise_status() {
616 let status = Status::from("Hello world!");
617 let mut presence = Presence::new(Type::Unavailable);
618 presence.statuses.insert(String::from(""), status);
619 let elem: Element = presence.into();
620 assert!(elem.is("presence", ns::DEFAULT_NS));
621 assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
622 }
623
624 #[test]
625 fn test_serialise_priority() {
626 let presence = Presence::new(Type::None)
627 .with_priority(42);
628 let elem: Element = presence.into();
629 assert!(elem.is("presence", ns::DEFAULT_NS));
630 let priority = elem.children().next().unwrap();
631 assert!(priority.is("priority", ns::DEFAULT_NS));
632 assert_eq!(priority.text(), "42");
633 }
634
635 #[test]
636 fn presence_with_to() {
637 let presence = Presence::new(Type::None);
638 let elem: Element = presence.into();
639 assert_eq!(elem.attr("to"), None);
640
641 let presence = Presence::new(Type::None)
642 .with_to(Jid::Bare(BareJid::domain("localhost")));
643 let elem: Element = presence.into();
644 assert_eq!(elem.attr("to"), Some("localhost"));
645
646 let presence = Presence::new(Type::None)
647 .with_to(BareJid::domain("localhost"));
648 let elem: Element = presence.into();
649 assert_eq!(elem.attr("to"), Some("localhost"));
650
651 let presence = Presence::new(Type::None)
652 .with_to(Jid::Full(FullJid::new("test", "localhost", "coucou")));
653 let elem: Element = presence.into();
654 assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
655
656 let presence = Presence::new(Type::None)
657 .with_to(FullJid::new("test", "localhost", "coucou"));
658 let elem: Element = presence.into();
659 assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
660 }
661}