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::ns;
9use crate::util::error::Error;
10use jid::Jid;
11use minidom::{Element, IntoAttributeValue};
12use std::collections::BTreeMap;
13use std::convert::TryFrom;
14use std::str::FromStr;
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 From<Show> for Element {
52 fn from(show: Show) -> Element {
53 Element::builder("show", ns::DEFAULT_NS)
54 .append(match show {
55 Show::Away => "away",
56 Show::Chat => "chat",
57 Show::Dnd => "dnd",
58 Show::Xa => "xa",
59 })
60 .build()
61 }
62}
63
64type Lang = String;
65type Status = String;
66
67type Priority = i8;
68
69///
70#[derive(Debug, Clone, PartialEq)]
71pub enum Type {
72 /// This value is not an acceptable 'type' attribute, it is only used
73 /// internally to signal the absence of 'type'.
74 None,
75
76 /// An error has occurred regarding processing of a previously sent
77 /// presence stanza; if the presence stanza is of type "error", it MUST
78 /// include an \<error/\> child element (refer to
79 /// [XMPP‑CORE](https://xmpp.org/rfcs/rfc6120.html)).
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, PartialEq)]
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 /// Create a presence without a type, which means available
195 pub fn available() -> Presence {
196 Self::new(Type::None)
197 }
198
199 /// Builds a presence of type Error
200 pub fn error() -> Presence {
201 Self::new(Type::Error)
202 }
203
204 /// Builds a presence of type Probe
205 pub fn probe() -> Presence {
206 Self::new(Type::Probe)
207 }
208
209 /// Builds a presence of type Subscribe
210 pub fn subscribe() -> Presence {
211 Self::new(Type::Subscribe)
212 }
213
214 /// Builds a presence of type Subscribed
215 pub fn subscribed() -> Presence {
216 Self::new(Type::Subscribed)
217 }
218
219 /// Builds a presence of type Unavailable
220 pub fn unavailable() -> Presence {
221 Self::new(Type::Unavailable)
222 }
223
224 /// Builds a presence of type Unsubscribe
225 pub fn unsubscribe() -> Presence {
226 Self::new(Type::Unsubscribe)
227 }
228
229 /// Set the emitter of this presence, this should only be useful for
230 /// servers and components, as clients can only send presences from their
231 /// own resource (which is implicit).
232 pub fn with_from<J: Into<Jid>>(mut self, from: J) -> Presence {
233 self.from = Some(from.into());
234 self
235 }
236
237 /// Set the recipient of this presence, this is only useful for directed
238 /// presences.
239 pub fn with_to<J: Into<Jid>>(mut self, to: J) -> Presence {
240 self.to = Some(to.into());
241 self
242 }
243
244 /// Set the identifier for this presence.
245 pub fn with_id(mut self, id: String) -> Presence {
246 self.id = Some(id);
247 self
248 }
249
250 /// Set the availability information of this presence.
251 pub fn with_show(mut self, show: Show) -> Presence {
252 self.show = Some(show);
253 self
254 }
255
256 /// Set the priority of this presence.
257 pub fn with_priority(mut self, priority: i8) -> Presence {
258 self.priority = priority;
259 self
260 }
261
262 /// Set a payload inside this presence.
263 pub fn with_payload<P: PresencePayload>(mut self, payload: P) -> Presence {
264 self.payloads.push(payload.into());
265 self
266 }
267
268 /// Set the payloads of this presence.
269 pub fn with_payloads(mut self, payloads: Vec<Element>) -> Presence {
270 self.payloads = payloads;
271 self
272 }
273
274 /// Set the availability information of this presence.
275 pub fn set_status<L, S>(&mut self, lang: L, status: S)
276 where
277 L: Into<Lang>,
278 S: Into<Status>,
279 {
280 self.statuses.insert(lang.into(), status.into());
281 }
282
283 /// Add a payload to this presence.
284 pub fn add_payload<P: PresencePayload>(&mut self, payload: P) {
285 self.payloads.push(payload.into());
286 }
287}
288
289impl TryFrom<Element> for Presence {
290 type Error = Error;
291
292 fn try_from(root: Element) -> Result<Presence, Error> {
293 check_self!(root, "presence", DEFAULT_NS);
294 let mut show = None;
295 let mut priority = None;
296 let mut presence = Presence {
297 from: get_attr!(root, "from", Option),
298 to: get_attr!(root, "to", Option),
299 id: get_attr!(root, "id", Option),
300 type_: get_attr!(root, "type", Default),
301 show: None,
302 statuses: BTreeMap::new(),
303 priority: 0i8,
304 payloads: vec![],
305 };
306 for elem in root.children() {
307 if elem.is("show", ns::DEFAULT_NS) {
308 if show.is_some() {
309 return Err(Error::ParseError(
310 "More than one show element in a presence.",
311 ));
312 }
313 check_no_attributes!(elem, "show");
314 check_no_children!(elem, "show");
315 show = Some(Show::from_str(elem.text().as_ref())?);
316 } else if elem.is("status", ns::DEFAULT_NS) {
317 check_no_unknown_attributes!(elem, "status", ["xml:lang"]);
318 check_no_children!(elem, "status");
319 let lang = get_attr!(elem, "xml:lang", Default);
320 if presence.statuses.insert(lang, elem.text()).is_some() {
321 return Err(Error::ParseError(
322 "Status element present twice for the same xml:lang.",
323 ));
324 }
325 } else if elem.is("priority", ns::DEFAULT_NS) {
326 if priority.is_some() {
327 return Err(Error::ParseError(
328 "More than one priority element in a presence.",
329 ));
330 }
331 check_no_attributes!(elem, "priority");
332 check_no_children!(elem, "priority");
333 priority = Some(Priority::from_str(elem.text().as_ref())?);
334 } else {
335 presence.payloads.push(elem.clone());
336 }
337 }
338 presence.show = show;
339 if let Some(priority) = priority {
340 presence.priority = priority;
341 }
342 Ok(presence)
343 }
344}
345
346impl From<Presence> for Element {
347 fn from(presence: Presence) -> Element {
348 Element::builder("presence", ns::DEFAULT_NS)
349 .attr("from", presence.from)
350 .attr("to", presence.to)
351 .attr("id", presence.id)
352 .attr("type", presence.type_)
353 .append_all(presence.show.into_iter())
354 .append_all(presence.statuses.into_iter().map(|(lang, status)| {
355 Element::builder("status", ns::DEFAULT_NS)
356 .attr(
357 "xml:lang",
358 match lang.as_ref() {
359 "" => None,
360 lang => Some(lang),
361 },
362 )
363 .append(status)
364 }))
365 .append_all(if presence.priority == 0 {
366 None
367 } else {
368 Some(
369 Element::builder("priority", ns::DEFAULT_NS)
370 .append(format!("{}", presence.priority)),
371 )
372 })
373 .append_all(presence.payloads.into_iter())
374 .build()
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use super::*;
381 use jid::{BareJid, FullJid};
382
383 #[cfg(target_pointer_width = "32")]
384 #[test]
385 fn test_size() {
386 assert_size!(Show, 1);
387 assert_size!(Type, 1);
388 assert_size!(Presence, 120);
389 }
390
391 #[cfg(target_pointer_width = "64")]
392 #[test]
393 fn test_size() {
394 assert_size!(Show, 1);
395 assert_size!(Type, 1);
396 assert_size!(Presence, 240);
397 }
398
399 #[test]
400 fn test_simple() {
401 #[cfg(not(feature = "component"))]
402 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
403 #[cfg(feature = "component")]
404 let elem: Element = "<presence xmlns='jabber:component:accept'/>"
405 .parse()
406 .unwrap();
407 let presence = Presence::try_from(elem).unwrap();
408 assert_eq!(presence.from, None);
409 assert_eq!(presence.to, None);
410 assert_eq!(presence.id, None);
411 assert_eq!(presence.type_, Type::None);
412 assert!(presence.payloads.is_empty());
413 }
414
415 #[test]
416 fn test_serialise() {
417 #[cfg(not(feature = "component"))]
418 let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>/>"
419 .parse()
420 .unwrap();
421 #[cfg(feature = "component")]
422 let elem: Element = "<presence xmlns='jabber:component:accept' type='unavailable'/>/>"
423 .parse()
424 .unwrap();
425 let presence = Presence::new(Type::Unavailable);
426 let elem2 = presence.into();
427 assert_eq!(elem, elem2);
428 }
429
430 #[test]
431 fn test_show() {
432 #[cfg(not(feature = "component"))]
433 let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>"
434 .parse()
435 .unwrap();
436 #[cfg(feature = "component")]
437 let elem: Element =
438 "<presence xmlns='jabber:component:accept'><show>chat</show></presence>"
439 .parse()
440 .unwrap();
441 let presence = Presence::try_from(elem).unwrap();
442 assert_eq!(presence.payloads.len(), 0);
443 assert_eq!(presence.show, Some(Show::Chat));
444 }
445
446 #[test]
447 fn test_empty_show_value() {
448 #[cfg(not(feature = "component"))]
449 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
450 #[cfg(feature = "component")]
451 let elem: Element = "<presence xmlns='jabber:component:accept'/>"
452 .parse()
453 .unwrap();
454 let presence = Presence::try_from(elem).unwrap();
455 assert_eq!(presence.show, None);
456 }
457
458 #[test]
459 fn test_missing_show_value() {
460 #[cfg(not(feature = "component"))]
461 let elem: Element = "<presence xmlns='jabber:client'><show/></presence>"
462 .parse()
463 .unwrap();
464 #[cfg(feature = "component")]
465 let elem: Element = "<presence xmlns='jabber:component:accept'><show/></presence>"
466 .parse()
467 .unwrap();
468 let error = Presence::try_from(elem).unwrap_err();
469 let message = match error {
470 Error::ParseError(string) => string,
471 _ => panic!(),
472 };
473 assert_eq!(message, "Invalid value for show.");
474 }
475
476 #[test]
477 fn test_invalid_show() {
478 // "online" used to be a pretty common mistake.
479 #[cfg(not(feature = "component"))]
480 let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>"
481 .parse()
482 .unwrap();
483 #[cfg(feature = "component")]
484 let elem: Element =
485 "<presence xmlns='jabber:component:accept'><show>online</show></presence>"
486 .parse()
487 .unwrap();
488 let error = Presence::try_from(elem).unwrap_err();
489 let message = match error {
490 Error::ParseError(string) => string,
491 _ => panic!(),
492 };
493 assert_eq!(message, "Invalid value for show.");
494 }
495
496 #[test]
497 fn test_empty_status() {
498 #[cfg(not(feature = "component"))]
499 let elem: Element = "<presence xmlns='jabber:client'><status/></presence>"
500 .parse()
501 .unwrap();
502 #[cfg(feature = "component")]
503 let elem: Element = "<presence xmlns='jabber:component:accept'><status/></presence>"
504 .parse()
505 .unwrap();
506 let presence = Presence::try_from(elem).unwrap();
507 assert_eq!(presence.payloads.len(), 0);
508 assert_eq!(presence.statuses.len(), 1);
509 assert_eq!(presence.statuses[""], "");
510 }
511
512 #[test]
513 fn test_status() {
514 #[cfg(not(feature = "component"))]
515 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>"
516 .parse()
517 .unwrap();
518 #[cfg(feature = "component")]
519 let elem: Element =
520 "<presence xmlns='jabber:component:accept'><status>Here!</status></presence>"
521 .parse()
522 .unwrap();
523 let presence = Presence::try_from(elem).unwrap();
524 assert_eq!(presence.payloads.len(), 0);
525 assert_eq!(presence.statuses.len(), 1);
526 assert_eq!(presence.statuses[""], "Here!");
527 }
528
529 #[test]
530 fn test_multiple_statuses() {
531 #[cfg(not(feature = "component"))]
532 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
533 #[cfg(feature = "component")]
534 let elem: Element = "<presence xmlns='jabber:component:accept'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
535 let presence = Presence::try_from(elem).unwrap();
536 assert_eq!(presence.payloads.len(), 0);
537 assert_eq!(presence.statuses.len(), 2);
538 assert_eq!(presence.statuses[""], "Here!");
539 assert_eq!(presence.statuses["fr"], "LĂ !");
540 }
541
542 #[test]
543 fn test_invalid_multiple_statuses() {
544 #[cfg(not(feature = "component"))]
545 let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
546 #[cfg(feature = "component")]
547 let elem: Element = "<presence xmlns='jabber:component:accept'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
548 let error = Presence::try_from(elem).unwrap_err();
549 let message = match error {
550 Error::ParseError(string) => string,
551 _ => panic!(),
552 };
553 assert_eq!(
554 message,
555 "Status element present twice for the same xml:lang."
556 );
557 }
558
559 #[test]
560 fn test_priority() {
561 #[cfg(not(feature = "component"))]
562 let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>"
563 .parse()
564 .unwrap();
565 #[cfg(feature = "component")]
566 let elem: Element =
567 "<presence xmlns='jabber:component:accept'><priority>-1</priority></presence>"
568 .parse()
569 .unwrap();
570 let presence = Presence::try_from(elem).unwrap();
571 assert_eq!(presence.payloads.len(), 0);
572 assert_eq!(presence.priority, -1i8);
573 }
574
575 #[test]
576 fn test_invalid_priority() {
577 #[cfg(not(feature = "component"))]
578 let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>"
579 .parse()
580 .unwrap();
581 #[cfg(feature = "component")]
582 let elem: Element =
583 "<presence xmlns='jabber:component:accept'><priority>128</priority></presence>"
584 .parse()
585 .unwrap();
586 let error = Presence::try_from(elem).unwrap_err();
587 match error {
588 Error::ParseIntError(_) => (),
589 _ => panic!(),
590 };
591 }
592
593 #[test]
594 fn test_unknown_child() {
595 #[cfg(not(feature = "component"))]
596 let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>"
597 .parse()
598 .unwrap();
599 #[cfg(feature = "component")]
600 let elem: Element =
601 "<presence xmlns='jabber:component:accept'><test xmlns='invalid'/></presence>"
602 .parse()
603 .unwrap();
604 let presence = Presence::try_from(elem).unwrap();
605 let payload = &presence.payloads[0];
606 assert!(payload.is("test", "invalid"));
607 }
608
609 #[cfg(not(feature = "disable-validation"))]
610 #[test]
611 fn test_invalid_status_child() {
612 #[cfg(not(feature = "component"))]
613 let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>"
614 .parse()
615 .unwrap();
616 #[cfg(feature = "component")]
617 let elem: Element =
618 "<presence xmlns='jabber:component:accept'><status><coucou/></status></presence>"
619 .parse()
620 .unwrap();
621 let error = Presence::try_from(elem).unwrap_err();
622 let message = match error {
623 Error::ParseError(string) => string,
624 _ => panic!(),
625 };
626 assert_eq!(message, "Unknown child in status element.");
627 }
628
629 #[cfg(not(feature = "disable-validation"))]
630 #[test]
631 fn test_invalid_attribute() {
632 #[cfg(not(feature = "component"))]
633 let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>"
634 .parse()
635 .unwrap();
636 #[cfg(feature = "component")]
637 let elem: Element =
638 "<presence xmlns='jabber:component:accept'><status coucou=''/></presence>"
639 .parse()
640 .unwrap();
641 let error = Presence::try_from(elem).unwrap_err();
642 let message = match error {
643 Error::ParseError(string) => string,
644 _ => panic!(),
645 };
646 assert_eq!(message, "Unknown attribute in status element.");
647 }
648
649 #[test]
650 fn test_serialise_status() {
651 let status = Status::from("Hello world!");
652 let mut presence = Presence::new(Type::Unavailable);
653 presence.statuses.insert(String::from(""), status);
654 let elem: Element = presence.into();
655 assert!(elem.is("presence", ns::DEFAULT_NS));
656 assert!(elem.children().next().unwrap().is("status", ns::DEFAULT_NS));
657 }
658
659 #[test]
660 fn test_serialise_priority() {
661 let presence = Presence::new(Type::None).with_priority(42);
662 let elem: Element = presence.into();
663 assert!(elem.is("presence", ns::DEFAULT_NS));
664 let priority = elem.children().next().unwrap();
665 assert!(priority.is("priority", ns::DEFAULT_NS));
666 assert_eq!(priority.text(), "42");
667 }
668
669 #[test]
670 fn presence_with_to() {
671 let presence = Presence::new(Type::None);
672 let elem: Element = presence.into();
673 assert_eq!(elem.attr("to"), None);
674
675 let presence = Presence::new(Type::None).with_to(Jid::Bare(BareJid::domain("localhost")));
676 let elem: Element = presence.into();
677 assert_eq!(elem.attr("to"), Some("localhost"));
678
679 let presence = Presence::new(Type::None).with_to(BareJid::domain("localhost"));
680 let elem: Element = presence.into();
681 assert_eq!(elem.attr("to"), Some("localhost"));
682
683 let presence = Presence::new(Type::None).with_to(Jid::Full(FullJid::new(
684 "test",
685 "localhost",
686 "coucou",
687 )));
688 let elem: Element = presence.into();
689 assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
690
691 let presence =
692 Presence::new(Type::None).with_to(FullJid::new("test", "localhost", "coucou"));
693 let elem: Element = presence.into();
694 assert_eq!(elem.attr("to"), Some("test@localhost/coucou"));
695 }
696}