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