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