1// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7use std::convert::TryFrom;
8use std::str::FromStr;
9use std::collections::BTreeMap;
10
11use minidom::{Element, IntoAttributeValue};
12
13use jid::Jid;
14
15use error::Error;
16
17use ns;
18
19use stanza_error::StanzaError;
20use delay::Delay;
21use idle::Idle;
22use ecaps2::ECaps2;
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum Show {
26 Away,
27 Chat,
28 Dnd,
29 Xa,
30}
31
32impl FromStr for Show {
33 type Err = Error;
34
35 fn from_str(s: &str) -> Result<Show, Error> {
36 Ok(match s {
37 "away" => Show::Away,
38 "chat" => Show::Chat,
39 "dnd" => Show::Dnd,
40 "xa" => Show::Xa,
41
42 _ => return Err(Error::ParseError("Invalid value for show.")),
43 })
44 }
45}
46
47impl Into<Element> for Show {
48 fn into(self) -> Element {
49 Element::builder("show")
50 .append(match self {
51 Show::Away => "away",
52 Show::Chat => "chat",
53 Show::Dnd => "dnd",
54 Show::Xa => "xa",
55 })
56 .build()
57 }
58}
59
60pub type Lang = String;
61pub type Status = String;
62
63pub type Priority = i8;
64
65/// Lists every known payload of a `<presence/>`.
66#[derive(Debug, Clone)]
67pub enum PresencePayload {
68 StanzaError(StanzaError),
69 Delay(Delay),
70 Idle(Idle),
71 ECaps2(ECaps2),
72
73 Unknown(Element),
74}
75
76impl TryFrom<Element> for PresencePayload {
77 type Error = Error;
78
79 fn try_from(elem: Element) -> Result<PresencePayload, Error> {
80 Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
81 ("error", ns::JABBER_CLIENT) => PresencePayload::StanzaError(StanzaError::try_from(elem)?),
82
83 // XEP-0203
84 ("delay", ns::DELAY) => PresencePayload::Delay(Delay::try_from(elem)?),
85
86 // XEP-0319
87 ("idle", ns::IDLE) => PresencePayload::Idle(Idle::try_from(elem)?),
88
89 // XEP-0390
90 ("c", ns::ECAPS2) => PresencePayload::ECaps2(ECaps2::try_from(elem)?),
91
92 _ => PresencePayload::Unknown(elem),
93 })
94 }
95}
96
97impl Into<Element> for PresencePayload {
98 fn into(self) -> Element {
99 match self {
100 PresencePayload::StanzaError(stanza_error) => stanza_error.into(),
101 PresencePayload::Delay(delay) => delay.into(),
102 PresencePayload::Idle(idle) => idle.into(),
103 PresencePayload::ECaps2(ecaps2) => ecaps2.into(),
104
105 PresencePayload::Unknown(elem) => elem,
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq)]
111pub enum PresenceType {
112 /// This value is not an acceptable 'type' attribute, it is only used
113 /// internally to signal the absence of 'type'.
114 Available,
115 Error,
116 Probe,
117 Subscribe,
118 Subscribed,
119 Unavailable,
120 Unsubscribe,
121 Unsubscribed,
122}
123
124impl Default for PresenceType {
125 fn default() -> PresenceType {
126 PresenceType::Available
127 }
128}
129
130impl FromStr for PresenceType {
131 type Err = Error;
132
133 fn from_str(s: &str) -> Result<PresenceType, Error> {
134 Ok(match s {
135 "error" => PresenceType::Error,
136 "probe" => PresenceType::Probe,
137 "subscribe" => PresenceType::Subscribe,
138 "subscribed" => PresenceType::Subscribed,
139 "unavailable" => PresenceType::Unavailable,
140 "unsubscribe" => PresenceType::Unsubscribe,
141 "unsubscribed" => PresenceType::Unsubscribed,
142
143 _ => return Err(Error::ParseError("Invalid 'type' attribute on presence element.")),
144 })
145 }
146}
147
148impl IntoAttributeValue for PresenceType {
149 fn into_attribute_value(self) -> Option<String> {
150 Some(match self {
151 PresenceType::Available => return None,
152
153 PresenceType::Error => "error",
154 PresenceType::Probe => "probe",
155 PresenceType::Subscribe => "subscribe",
156 PresenceType::Subscribed => "subscribed",
157 PresenceType::Unavailable => "unavailable",
158 PresenceType::Unsubscribe => "unsubscribe",
159 PresenceType::Unsubscribed => "unsubscribed",
160 }.to_owned())
161 }
162}
163
164#[derive(Debug, Clone)]
165pub struct Presence {
166 pub from: Option<Jid>,
167 pub to: Option<Jid>,
168 pub id: Option<String>,
169 pub type_: PresenceType,
170 pub show: Option<Show>,
171 pub statuses: BTreeMap<Lang, Status>,
172 pub priority: Priority,
173 pub payloads: Vec<Element>,
174}
175
176impl TryFrom<Element> for Presence {
177 type Error = Error;
178
179 fn try_from(root: Element) -> Result<Presence, Error> {
180 if !root.is("presence", ns::JABBER_CLIENT) {
181 return Err(Error::ParseError("This is not a presence element."));
182 }
183 let mut priority = None;
184 let mut presence = Presence {
185 from: get_attr!(root, "from", optional),
186 to: get_attr!(root, "to", optional),
187 id: get_attr!(root, "id", optional),
188 type_: get_attr!(root, "type", default),
189 show: None,
190 statuses: BTreeMap::new(),
191 priority: 0i8,
192 payloads: vec!(),
193 };
194 for elem in root.children() {
195 if elem.is("show", ns::JABBER_CLIENT) {
196 if presence.show.is_some() {
197 return Err(Error::ParseError("More than one show element in a presence."));
198 }
199 for _ in elem.children() {
200 return Err(Error::ParseError("Unknown child in show element."));
201 }
202 for _ in elem.attrs() {
203 return Err(Error::ParseError("Unknown attribute in show element."));
204 }
205 presence.show = Some(Show::from_str(elem.text().as_ref())?);
206 } else if elem.is("status", ns::JABBER_CLIENT) {
207 for _ in elem.children() {
208 return Err(Error::ParseError("Unknown child in status element."));
209 }
210 for (attr, _) in elem.attrs() {
211 if attr != "xml:lang" {
212 return Err(Error::ParseError("Unknown attribute in status element."));
213 }
214 }
215 let lang = get_attr!(elem, "xml:lang", default);
216 if presence.statuses.insert(lang, elem.text()).is_some() {
217 return Err(Error::ParseError("Status element present twice for the same xml:lang."));
218 }
219 } else if elem.is("priority", ns::JABBER_CLIENT) {
220 if priority.is_some() {
221 return Err(Error::ParseError("More than one priority element in a presence."));
222 }
223 for _ in elem.children() {
224 return Err(Error::ParseError("Unknown child in priority element."));
225 }
226 for _ in elem.attrs() {
227 return Err(Error::ParseError("Unknown attribute in priority element."));
228 }
229 priority = Some(Priority::from_str(elem.text().as_ref())?);
230 } else {
231 presence.payloads.push(elem.clone());
232 }
233 }
234 if let Some(priority) = priority {
235 presence.priority = priority;
236 }
237 Ok(presence)
238 }
239}
240
241impl Into<Element> for Presence {
242 fn into(self) -> Element {
243 Element::builder("presence")
244 .ns(ns::JABBER_CLIENT)
245 .attr("from", self.from.and_then(|value| Some(String::from(value))))
246 .attr("to", self.to.and_then(|value| Some(String::from(value))))
247 .attr("id", self.id)
248 .attr("type", self.type_)
249 .append(match self.show {
250 Some(show) => Some({ let elem: Element = show.into(); elem }),
251 None => None
252 })
253 .append(self.statuses.iter().map(|(lang, status)| {
254 Element::builder("status")
255 .attr("xml:lang", match lang.as_ref() {
256 "" => None,
257 lang => Some(lang),
258 })
259 .append(status)
260 .build()
261 }).collect::<Vec<_>>())
262 .append(if self.priority == 0 { None } else { Some(format!("{}", self.priority)) })
263 .append(self.payloads)
264 .build()
265 }
266}
267
268#[cfg(test)]
269mod tests {
270 use std::collections::BTreeMap;
271 use super::*;
272
273 #[test]
274 fn test_simple() {
275 let elem: Element = "<presence xmlns='jabber:client'/>".parse().unwrap();
276 let presence = Presence::try_from(elem).unwrap();
277 assert_eq!(presence.from, None);
278 assert_eq!(presence.to, None);
279 assert_eq!(presence.id, None);
280 assert_eq!(presence.type_, PresenceType::Available);
281 assert!(presence.payloads.is_empty());
282 }
283
284 #[test]
285 fn test_serialise() {
286 let elem: Element = "<presence xmlns='jabber:client' type='unavailable'/>".parse().unwrap();
287 let presence = Presence {
288 from: None,
289 to: None,
290 id: None,
291 type_: PresenceType::Unavailable,
292 show: None,
293 statuses: BTreeMap::new(),
294 priority: 0i8,
295 payloads: vec!(),
296 };
297 let elem2 = presence.into();
298 assert_eq!(elem, elem2);
299 }
300
301 #[test]
302 fn test_show() {
303 let elem: Element = "<presence xmlns='jabber:client'><show>chat</show></presence>".parse().unwrap();
304 let presence = Presence::try_from(elem).unwrap();
305 assert_eq!(presence.payloads.len(), 0);
306 assert_eq!(presence.show, Some(Show::Chat));
307 }
308
309 #[test]
310 fn test_missing_show_value() {
311 // "online" used to be a pretty common mistake.
312 let elem: Element = "<presence xmlns='jabber:client'><show/></presence>".parse().unwrap();
313 let error = Presence::try_from(elem).unwrap_err();
314 let message = match error {
315 Error::ParseError(string) => string,
316 _ => panic!(),
317 };
318 assert_eq!(message, "Invalid value for show.");
319 }
320
321 #[test]
322 fn test_invalid_show() {
323 // "online" used to be a pretty common mistake.
324 let elem: Element = "<presence xmlns='jabber:client'><show>online</show></presence>".parse().unwrap();
325 let error = Presence::try_from(elem).unwrap_err();
326 let message = match error {
327 Error::ParseError(string) => string,
328 _ => panic!(),
329 };
330 assert_eq!(message, "Invalid value for show.");
331 }
332
333 #[test]
334 fn test_empty_status() {
335 let elem: Element = "<presence xmlns='jabber:client'><status/></presence>".parse().unwrap();
336 let presence = Presence::try_from(elem).unwrap();
337 assert_eq!(presence.payloads.len(), 0);
338 assert_eq!(presence.statuses.len(), 1);
339 assert_eq!(presence.statuses[""], "");
340 }
341
342 #[test]
343 fn test_status() {
344 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status></presence>".parse().unwrap();
345 let presence = Presence::try_from(elem).unwrap();
346 assert_eq!(presence.payloads.len(), 0);
347 assert_eq!(presence.statuses.len(), 1);
348 assert_eq!(presence.statuses[""], "Here!");
349 }
350
351 #[test]
352 fn test_multiple_statuses() {
353 let elem: Element = "<presence xmlns='jabber:client'><status>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
354 let presence = Presence::try_from(elem).unwrap();
355 assert_eq!(presence.payloads.len(), 0);
356 assert_eq!(presence.statuses.len(), 2);
357 assert_eq!(presence.statuses[""], "Here!");
358 assert_eq!(presence.statuses["fr"], "Là!");
359 }
360
361 #[test]
362 fn test_invalid_multiple_statuses() {
363 let elem: Element = "<presence xmlns='jabber:client'><status xml:lang='fr'>Here!</status><status xml:lang='fr'>Là!</status></presence>".parse().unwrap();
364 let error = Presence::try_from(elem).unwrap_err();
365 let message = match error {
366 Error::ParseError(string) => string,
367 _ => panic!(),
368 };
369 assert_eq!(message, "Status element present twice for the same xml:lang.");
370 }
371
372 #[test]
373 fn test_priority() {
374 let elem: Element = "<presence xmlns='jabber:client'><priority>-1</priority></presence>".parse().unwrap();
375 let presence = Presence::try_from(elem).unwrap();
376 assert_eq!(presence.payloads.len(), 0);
377 assert_eq!(presence.priority, -1i8);
378 }
379
380 #[test]
381 fn test_invalid_priority() {
382 let elem: Element = "<presence xmlns='jabber:client'><priority>128</priority></presence>".parse().unwrap();
383 let error = Presence::try_from(elem).unwrap_err();
384 match error {
385 Error::ParseIntError(_) => (),
386 _ => panic!(),
387 };
388 }
389
390 #[test]
391 fn test_unknown_child() {
392 let elem: Element = "<presence xmlns='jabber:client'><test xmlns='invalid'/></presence>".parse().unwrap();
393 let presence = Presence::try_from(elem).unwrap();
394 let payload = &presence.payloads[0];
395 assert!(payload.is("test", "invalid"));
396 }
397
398 #[test]
399 fn test_invalid_status_child() {
400 let elem: Element = "<presence xmlns='jabber:client'><status><coucou/></status></presence>".parse().unwrap();
401 let error = Presence::try_from(elem).unwrap_err();
402 let message = match error {
403 Error::ParseError(string) => string,
404 _ => panic!(),
405 };
406 assert_eq!(message, "Unknown child in status element.");
407 }
408
409 #[test]
410 fn test_invalid_attribute() {
411 let elem: Element = "<presence xmlns='jabber:client'><status coucou=''/></presence>".parse().unwrap();
412 let error = Presence::try_from(elem).unwrap_err();
413 let message = match error {
414 Error::ParseError(string) => string,
415 _ => panic!(),
416 };
417 assert_eq!(message, "Unknown attribute in status element.");
418 }
419
420 #[test]
421 fn test_serialise_status() {
422 let status = Status::from("Hello world!");
423 let mut statuses = BTreeMap::new();
424 statuses.insert(String::from(""), status);
425 let presence = Presence {
426 from: None,
427 to: None,
428 id: None,
429 type_: PresenceType::Unavailable,
430 show: None,
431 statuses: statuses,
432 priority: 0i8,
433 payloads: vec!(),
434 };
435 let elem: Element = presence.into();
436 assert!(elem.is("presence", ns::JABBER_CLIENT));
437 assert!(elem.children().collect::<Vec<_>>()[0].is("status", ns::JABBER_CLIENT));
438 }
439}