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::JABBER_CLIENT) => 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#[derive(Debug, Clone)]
190pub struct Presence {
191 pub from: Option<Jid>,
192 pub to: Option<Jid>,
193 pub id: Option<String>,
194 pub type_: Type,
195 pub show: Show,
196 pub statuses: BTreeMap<Lang, Status>,
197 pub priority: Priority,
198 pub payloads: Vec<Element>,
199}
200
201impl Presence {
202 pub fn new(type_: Type) -> Presence {
203 Presence {
204 from: None,
205 to: None,
206 id: None,
207 type_: type_,
208 show: Show::None,
209 statuses: BTreeMap::new(),
210 priority: 0i8,
211 payloads: vec!(),
212 }
213 }
214
215 pub fn with_from(mut self, from: Option<Jid>) -> Presence {
216 self.from = from;
217 self
218 }
219
220 pub fn with_to(mut self, to: Option<Jid>) -> Presence {
221 self.to = to;
222 self
223 }
224
225 pub fn with_id(mut self, id: Option<String>) -> Presence {
226 self.id = id;
227 self
228 }
229
230 pub fn with_show(mut self, show: Show) -> Presence {
231 self.show = show;
232 self
233 }
234
235 pub fn with_priority(mut self, priority: i8) -> Presence {
236 self.priority = priority;
237 self
238 }
239
240 pub fn with_payloads(mut self, payloads: Vec<Element>) -> Presence {
241 self.payloads = payloads;
242 self
243 }
244}
245
246impl TryFrom<Element> for Presence {
247 type Err = Error;
248
249 fn try_from(root: Element) -> Result<Presence, Error> {
250 if !root.is("presence", ns::JABBER_CLIENT) {
251 return Err(Error::ParseError("This is not a presence element."));
252 }
253 let mut show = None;
254 let mut priority = None;
255 let mut presence = Presence {
256 from: get_attr!(root, "from", optional),
257 to: get_attr!(root, "to", optional),
258 id: get_attr!(root, "id", optional),
259 type_: get_attr!(root, "type", default),
260 show: 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::JABBER_CLIENT) {
267 if show.is_some() {
268 return Err(Error::ParseError("More than one show element in a presence."));
269 }
270 for _ in elem.children() {
271 return Err(Error::ParseError("Unknown child in show element."));
272 }
273 for _ in elem.attrs() {
274 return Err(Error::ParseError("Unknown attribute in show element."));
275 }
276 show = Some(Show::from_str(elem.text().as_ref())?);
277 } else if elem.is("status", ns::JABBER_CLIENT) {
278 for _ in elem.children() {
279 return Err(Error::ParseError("Unknown child in status element."));
280 }
281 for (attr, _) in elem.attrs() {
282 if attr != "xml:lang" {
283 return Err(Error::ParseError("Unknown attribute in status element."));
284 }
285 }
286 let lang = get_attr!(elem, "xml:lang", default);
287 if presence.statuses.insert(lang, elem.text()).is_some() {
288 return Err(Error::ParseError("Status element present twice for the same xml:lang."));
289 }
290 } else if elem.is("priority", ns::JABBER_CLIENT) {
291 if priority.is_some() {
292 return Err(Error::ParseError("More than one priority element in a presence."));
293 }
294 for _ in elem.children() {
295 return Err(Error::ParseError("Unknown child in priority element."));
296 }
297 for _ in elem.attrs() {
298 return Err(Error::ParseError("Unknown attribute in priority element."));
299 }
300 priority = Some(Priority::from_str(elem.text().as_ref())?);
301 } else {
302 presence.payloads.push(elem.clone());
303 }
304 }
305 if let Some(show) = show {
306 presence.show = show;
307 }
308 if let Some(priority) = priority {
309 presence.priority = priority;
310 }
311 Ok(presence)
312 }
313}
314
315impl From<Presence> for Element {
316 fn from(presence: Presence) -> Element {
317 Element::builder("presence")
318 .ns(ns::JABBER_CLIENT)
319 .attr("from", presence.from.and_then(|value| Some(String::from(value))))
320 .attr("to", presence.to.and_then(|value| Some(String::from(value))))
321 .attr("id", presence.id)
322 .attr("type", presence.type_)
323 .append(presence.show)
324 .append(presence.statuses.iter().map(|(lang, status)| {
325 Element::builder("status")
326 .attr("xml:lang", match lang.as_ref() {
327 "" => None,
328 lang => Some(lang),
329 })
330 .append(status)
331 .build()
332 }).collect::<Vec<_>>())
333 .append(if presence.priority == 0 { None } else { Some(format!("{}", presence.priority)) })
334 .append(presence.payloads)
335 .build()
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342
343 #[test]
344 fn test_simple() {
345 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
346 let presence = Presence::try_from(elem).unwrap();
347 assert_eq!(presence.from, None);
348 assert_eq!(presence.to, None);
349 assert_eq!(presence.id, None);
350 assert_eq!(presence.type_, Type::None);
351 assert!(presence.payloads.is_empty());
352 }
353
354 #[test]
355 fn test_serialise() {
356 let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>".parse().unwrap();
357 let presence = Presence::new(Type::Unavailable);
358 let elem2 = presence.into();
359 assert_eq!(elem, elem2);
360 }
361
362 #[test]
363 fn test_show() {
364 let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>".parse().unwrap();
365 let presence = Presence::try_from(elem).unwrap();
366 assert_eq!(presence.payloads.len(), 0);
367 assert_eq!(presence.show, Show::Chat);
368 }
369
370 #[test]
371 fn test_missing_show_value() {
372 // "online" used to be a pretty common mistake.
373 let elem: Element = "<presence xmlns='jabber:client'><show/></presence>".parse().unwrap();
374 let error = Presence::try_from(elem).unwrap_err();
375 let message = match error {
376 Error::ParseError(string) => string,
377 _ => panic!(),
378 };
379 assert_eq!(message, "Invalid value for show.");
380 }
381
382 #[test]
383 fn test_invalid_show() {
384 // "online" used to be a pretty common mistake.
385 let elem: Element = "<presence xmlns='jabber:client'><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 let elem: Element = "<presence xmlns='jabber:client'><status/></presence>".parse().unwrap();
397 let presence = Presence::try_from(elem).unwrap();
398 assert_eq!(presence.payloads.len(), 0);
399 assert_eq!(presence.statuses.len(), 1);
400 assert_eq!(presence.statuses[""], "");
401 }
402
403 #[test]
404 fn test_status() {
405 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>".parse().unwrap();
406 let presence = Presence::try_from(elem).unwrap();
407 assert_eq!(presence.payloads.len(), 0);
408 assert_eq!(presence.statuses.len(), 1);
409 assert_eq!(presence.statuses[""], "Here!");
410 }
411
412 #[test]
413 fn test_multiple_statuses() {
414 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
415 let presence = Presence::try_from(elem).unwrap();
416 assert_eq!(presence.payloads.len(), 0);
417 assert_eq!(presence.statuses.len(), 2);
418 assert_eq!(presence.statuses[""], "Here!");
419 assert_eq!(presence.statuses["fr"], "LĂ !");
420 }
421
422 #[test]
423 fn test_invalid_multiple_statuses() {
424 let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>LĂ !</status></presence>".parse().unwrap();
425 let error = Presence::try_from(elem).unwrap_err();
426 let message = match error {
427 Error::ParseError(string) => string,
428 _ => panic!(),
429 };
430 assert_eq!(message, "Status element present twice for the same xml:lang.");
431 }
432
433 #[test]
434 fn test_priority() {
435 let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>".parse().unwrap();
436 let presence = Presence::try_from(elem).unwrap();
437 assert_eq!(presence.payloads.len(), 0);
438 assert_eq!(presence.priority, -1i8);
439 }
440
441 #[test]
442 fn test_invalid_priority() {
443 let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>".parse().unwrap();
444 let error = Presence::try_from(elem).unwrap_err();
445 match error {
446 Error::ParseIntError(_) => (),
447 _ => panic!(),
448 };
449 }
450
451 #[test]
452 fn test_unknown_child() {
453 let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>".parse().unwrap();
454 let presence = Presence::try_from(elem).unwrap();
455 let payload = &presence.payloads[0];
456 assert!(payload.is("test", "invalid"));
457 }
458
459 #[test]
460 fn test_invalid_status_child() {
461 let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>".parse().unwrap();
462 let error = Presence::try_from(elem).unwrap_err();
463 let message = match error {
464 Error::ParseError(string) => string,
465 _ => panic!(),
466 };
467 assert_eq!(message, "Unknown child in status element.");
468 }
469
470 #[test]
471 fn test_invalid_attribute() {
472 let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>".parse().unwrap();
473 let error = Presence::try_from(elem).unwrap_err();
474 let message = match error {
475 Error::ParseError(string) => string,
476 _ => panic!(),
477 };
478 assert_eq!(message, "Unknown attribute in status element.");
479 }
480
481 #[test]
482 fn test_serialise_status() {
483 let status = Status::from("Hello world!");
484 let mut presence = Presence::new(Type::Unavailable);
485 presence.statuses.insert(String::from(""), status);
486 let elem: Element = presence.into();
487 assert!(elem.is("presence", ns::JABBER_CLIENT));
488 assert!(elem.children().next().unwrap().is("status", ns::JABBER_CLIENT));
489 }
490}