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