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
7#![allow(missing_docs)]
8
9use try_from::TryFrom;
10use std::collections::BTreeMap;
11
12use minidom::Element;
13
14use jid::Jid;
15
16use error::Error;
17
18use ns;
19
20use stanza_error::StanzaError;
21use chatstates::ChatState;
22use receipts::{Request as ReceiptRequest, Received as ReceiptReceived};
23use delay::Delay;
24use attention::Attention;
25use message_correct::Replace;
26use eme::ExplicitMessageEncryption;
27use stanza_id::{StanzaId, OriginId};
28use mam::Result_ as MamResult;
29
30/// Lists every known payload of a `<message/>`.
31#[derive(Debug, Clone)]
32pub enum MessagePayload {
33 StanzaError(StanzaError),
34 ChatState(ChatState),
35 ReceiptRequest(ReceiptRequest),
36 ReceiptReceived(ReceiptReceived),
37 Delay(Delay),
38 Attention(Attention),
39 MessageCorrect(Replace),
40 ExplicitMessageEncryption(ExplicitMessageEncryption),
41 StanzaId(StanzaId),
42 OriginId(OriginId),
43 MamResult(MamResult),
44
45 Unknown(Element),
46}
47
48impl TryFrom<Element> for MessagePayload {
49 type Err = Error;
50
51 fn try_from(elem: Element) -> Result<MessagePayload, Error> {
52 Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
53 ("error", ns::DEFAULT_NS) => MessagePayload::StanzaError(StanzaError::try_from(elem)?),
54
55 // XEP-0085
56 ("active", ns::CHATSTATES)
57 | ("inactive", ns::CHATSTATES)
58 | ("composing", ns::CHATSTATES)
59 | ("paused", ns::CHATSTATES)
60 | ("gone", ns::CHATSTATES) => MessagePayload::ChatState(ChatState::try_from(elem)?),
61
62 // XEP-0184
63 ("request", ns::RECEIPTS) => MessagePayload::ReceiptRequest(ReceiptRequest::try_from(elem)?),
64 ("received", ns::RECEIPTS) => MessagePayload::ReceiptReceived(ReceiptReceived::try_from(elem)?),
65
66 // XEP-0203
67 ("delay", ns::DELAY) => MessagePayload::Delay(Delay::try_from(elem)?),
68
69 // XEP-0224
70 ("attention", ns::ATTENTION) => MessagePayload::Attention(Attention::try_from(elem)?),
71
72 // XEP-0308
73 ("replace", ns::MESSAGE_CORRECT) => MessagePayload::MessageCorrect(Replace::try_from(elem)?),
74
75 // XEP-0313
76 ("result", ns::MAM) => MessagePayload::MamResult(MamResult::try_from(elem)?),
77
78 // XEP-0359
79 ("stanza-id", ns::SID) => MessagePayload::StanzaId(StanzaId::try_from(elem)?),
80 ("origin-id", ns::SID) => MessagePayload::OriginId(OriginId::try_from(elem)?),
81
82 // XEP-0380
83 ("encryption", ns::EME) => MessagePayload::ExplicitMessageEncryption(ExplicitMessageEncryption::try_from(elem)?),
84
85 _ => MessagePayload::Unknown(elem),
86 })
87 }
88}
89
90impl From<MessagePayload> for Element {
91 fn from(payload: MessagePayload) -> Element {
92 match payload {
93 MessagePayload::StanzaError(stanza_error) => stanza_error.into(),
94 MessagePayload::Attention(attention) => attention.into(),
95 MessagePayload::ChatState(chatstate) => chatstate.into(),
96 MessagePayload::ReceiptRequest(request) => request.into(),
97 MessagePayload::ReceiptReceived(received) => received.into(),
98 MessagePayload::Delay(delay) => delay.into(),
99 MessagePayload::MessageCorrect(replace) => replace.into(),
100 MessagePayload::ExplicitMessageEncryption(eme) => eme.into(),
101 MessagePayload::StanzaId(stanza_id) => stanza_id.into(),
102 MessagePayload::OriginId(origin_id) => origin_id.into(),
103 MessagePayload::MamResult(result) => result.into(),
104
105 MessagePayload::Unknown(elem) => elem,
106 }
107 }
108}
109
110generate_attribute!(
111 /// The type of a message.
112 MessageType, "type", {
113 /// Standard instant messaging message.
114 Chat => "chat",
115
116 /// Notifies that an error happened.
117 Error => "error",
118
119 /// Standard group instant messaging message.
120 Groupchat => "groupchat",
121
122 /// Used by servers to notify users when things happen.
123 Headline => "headline",
124
125 /// This is an email-like message, it usually contains a
126 /// [subject](struct.Subject.html).
127 Normal => "normal",
128 }, Default = Normal
129);
130
131type Lang = String;
132
133generate_elem_id!(
134 /// Represents one `<body/>` element, that is the free form text content of
135 /// a message.
136 Body, "body", DEFAULT_NS
137);
138
139generate_elem_id!(
140 /// Defines the subject of a room, or of an email-like normal message.
141 Subject, "subject", DEFAULT_NS
142);
143
144generate_elem_id!(
145 /// A thread identifier, so that other people can specify to which message
146 /// they are replying.
147 Thread, "thread", DEFAULT_NS
148);
149
150/// The main structure representing the `<message/>` stanza.
151#[derive(Debug, Clone)]
152pub struct Message {
153 /// The JID emitting this stanza.
154 pub from: Option<Jid>,
155
156 /// The recipient of this stanza.
157 pub to: Option<Jid>,
158
159 /// The @id attribute of this stanza, which is required in order to match a
160 /// request with its response.
161 pub id: Option<String>,
162
163 /// The type of this message.
164 pub type_: MessageType,
165
166 /// A list of bodies, sorted per language. Use
167 /// [get_best_body()](#method.get_best_body) to access them on reception.
168 pub bodies: BTreeMap<Lang, Body>,
169
170 /// A list of subjects, sorted per language. Use
171 /// [get_best_subject()](#method.get_best_subject) to access them on
172 /// reception.
173 pub subjects: BTreeMap<Lang, Subject>,
174
175 /// An optional thread identifier, so that other people can reply directly
176 /// to this message.
177 pub thread: Option<Thread>,
178
179 /// A list of the extension payloads contained in this stanza.
180 pub payloads: Vec<Element>,
181}
182
183impl Message {
184 /// Creates a new `<message/>` stanza for the given recipient.
185 pub fn new(to: Option<Jid>) -> Message {
186 Message {
187 from: None,
188 to: to,
189 id: None,
190 type_: MessageType::Chat,
191 bodies: BTreeMap::new(),
192 subjects: BTreeMap::new(),
193 thread: None,
194 payloads: vec!(),
195 }
196 }
197
198 fn get_best<'a, T>(map: &'a BTreeMap<Lang, T>, preferred_langs: Vec<&str>) -> Option<(Lang, &'a T)> {
199 if map.is_empty() {
200 return None;
201 }
202 for lang in preferred_langs {
203 if let Some(value) = map.get(lang) {
204 return Some((Lang::from(lang), value));
205 }
206 }
207 if let Some(value) = map.get("") {
208 return Some((Lang::new(), value));
209 }
210 map.iter().map(|(lang, value)| (lang.clone(), value)).next()
211 }
212
213 /// Returns the best matching body from a list of languages.
214 ///
215 /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
216 /// body without an xml:lang attribute, and you pass ["fr", "en"] as your preferred languages,
217 /// `Some(("fr", the_second_body))` will be returned.
218 ///
219 /// If no body matches, an undefined body will be returned.
220 pub fn get_best_body(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Body)> {
221 Message::get_best::<Body>(&self.bodies, preferred_langs)
222 }
223
224 /// Returns the best matching subject from a list of languages.
225 ///
226 /// For instance, if a message contains both an xml:lang='de', an xml:lang='fr' and an English
227 /// subject without an xml:lang attribute, and you pass ["fr", "en"] as your preferred
228 /// languages, `Some(("fr", the_second_subject))` will be returned.
229 ///
230 /// If no subject matches, an undefined subject will be returned.
231 pub fn get_best_subject(&self, preferred_langs: Vec<&str>) -> Option<(Lang, &Subject)> {
232 Message::get_best::<Subject>(&self.subjects, preferred_langs)
233 }
234}
235
236impl TryFrom<Element> for Message {
237 type Err = Error;
238
239 fn try_from(root: Element) -> Result<Message, Error> {
240 check_self!(root, "message", DEFAULT_NS);
241 let from = get_attr!(root, "from", optional);
242 let to = get_attr!(root, "to", optional);
243 let id = get_attr!(root, "id", optional);
244 let type_ = get_attr!(root, "type", default);
245 let mut bodies = BTreeMap::new();
246 let mut subjects = BTreeMap::new();
247 let mut thread = None;
248 let mut payloads = vec!();
249 for elem in root.children() {
250 if elem.is("body", ns::DEFAULT_NS) {
251 check_no_children!(elem, "body");
252 let lang = get_attr!(elem, "xml:lang", default);
253 let body = Body(elem.text());
254 if bodies.insert(lang, body).is_some() {
255 return Err(Error::ParseError("Body element present twice for the same xml:lang."));
256 }
257 } else if elem.is("subject", ns::DEFAULT_NS) {
258 check_no_children!(elem, "subject");
259 let lang = get_attr!(elem, "xml:lang", default);
260 let subject = Subject(elem.text());
261 if subjects.insert(lang, subject).is_some() {
262 return Err(Error::ParseError("Subject element present twice for the same xml:lang."));
263 }
264 } else if elem.is("thread", ns::DEFAULT_NS) {
265 if thread.is_some() {
266 return Err(Error::ParseError("Thread element present twice."));
267 }
268 check_no_children!(elem, "thread");
269 thread = Some(Thread(elem.text()));
270 } else {
271 payloads.push(elem.clone())
272 }
273 }
274 Ok(Message {
275 from: from,
276 to: to,
277 id: id,
278 type_: type_,
279 bodies: bodies,
280 subjects: subjects,
281 thread: thread,
282 payloads: payloads,
283 })
284 }
285}
286
287impl From<Message> for Element {
288 fn from(message: Message) -> Element {
289 Element::builder("message")
290 .ns(ns::DEFAULT_NS)
291 .attr("from", message.from)
292 .attr("to", message.to)
293 .attr("id", message.id)
294 .attr("type", message.type_)
295 .append(message.subjects.into_iter()
296 .map(|(lang, subject)| {
297 let mut subject = Element::from(subject);
298 subject.set_attr("xml:lang", match lang.as_ref() {
299 "" => None,
300 lang => Some(lang),
301 });
302 subject
303 })
304 .collect::<Vec<_>>())
305 .append(message.bodies.into_iter()
306 .map(|(lang, body)| {
307 let mut body = Element::from(body);
308 body.set_attr("xml:lang", match lang.as_ref() {
309 "" => None,
310 lang => Some(lang),
311 });
312 body
313 })
314 .collect::<Vec<_>>())
315 .append(message.payloads)
316 .build()
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use std::str::FromStr;
324 use compare_elements::NamespaceAwareCompare;
325
326 #[test]
327 fn test_simple() {
328 #[cfg(not(feature = "component"))]
329 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
330 #[cfg(feature = "component")]
331 let elem: Element = "<message xmlns='jabber:component:accept'/>".parse().unwrap();
332 let message = Message::try_from(elem).unwrap();
333 assert_eq!(message.from, None);
334 assert_eq!(message.to, None);
335 assert_eq!(message.id, None);
336 assert_eq!(message.type_, MessageType::Normal);
337 assert!(message.payloads.is_empty());
338 }
339
340 #[test]
341 fn test_serialise() {
342 #[cfg(not(feature = "component"))]
343 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
344 #[cfg(feature = "component")]
345 let elem: Element = "<message xmlns='jabber:component:accept'/>".parse().unwrap();
346 let mut message = Message::new(None);
347 message.type_ = MessageType::Normal;
348 let elem2 = message.into();
349 assert_eq!(elem, elem2);
350 }
351
352 #[test]
353 fn test_body() {
354 #[cfg(not(feature = "component"))]
355 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
356 #[cfg(feature = "component")]
357 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
358 let elem1 = elem.clone();
359 let message = Message::try_from(elem).unwrap();
360 assert_eq!(message.bodies[""], Body::from_str("Hello world!").unwrap());
361
362 {
363 let (lang, body) = message.get_best_body(vec!("en")).unwrap();
364 assert_eq!(lang, "");
365 assert_eq!(body, &Body::from_str("Hello world!").unwrap());
366 }
367
368 let elem2 = message.into();
369 assert!(elem1.compare_to(&elem2));
370 }
371
372 #[test]
373 fn test_serialise_body() {
374 #[cfg(not(feature = "component"))]
375 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
376 #[cfg(feature = "component")]
377 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
378 let mut message = Message::new(Some(Jid::from_str("coucou@example.org").unwrap()));
379 message.bodies.insert(String::from(""), Body::from_str("Hello world!").unwrap());
380 let elem2 = message.into();
381 assert!(elem.compare_to(&elem2));
382 }
383
384 #[test]
385 fn test_subject() {
386 #[cfg(not(feature = "component"))]
387 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
388 #[cfg(feature = "component")]
389 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
390 let elem1 = elem.clone();
391 let message = Message::try_from(elem).unwrap();
392 assert_eq!(message.subjects[""], Subject::from_str("Hello world!").unwrap());
393
394 {
395 let (lang, subject) = message.get_best_subject(vec!("en")).unwrap();
396 assert_eq!(lang, "");
397 assert_eq!(subject, &Subject::from_str("Hello world!").unwrap());
398 }
399
400 let elem2 = message.into();
401 assert!(elem1.compare_to(&elem2));
402 }
403
404 #[test]
405 fn get_best_body() {
406 #[cfg(not(feature = "component"))]
407 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body xml:lang='de'>Hallo Welt!</body><body xml:lang='fr'>Salut le monde !</body><body>Hello world!</body></message>".parse().unwrap();
408 #[cfg(feature = "component")]
409 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
410 let message = Message::try_from(elem).unwrap();
411
412 // Tests basic feature.
413 {
414 let (lang, body) = message.get_best_body(vec!("fr")).unwrap();
415 assert_eq!(lang, "fr");
416 assert_eq!(body, &Body::from_str("Salut le monde !").unwrap());
417 }
418
419 // Tests order.
420 {
421 let (lang, body) = message.get_best_body(vec!("en", "de")).unwrap();
422 assert_eq!(lang, "de");
423 assert_eq!(body, &Body::from_str("Hallo Welt!").unwrap());
424 }
425
426 // Tests fallback.
427 {
428 let (lang, body) = message.get_best_body(vec!()).unwrap();
429 assert_eq!(lang, "");
430 assert_eq!(body, &Body::from_str("Hello world!").unwrap());
431 }
432
433 // Tests fallback.
434 {
435 let (lang, body) = message.get_best_body(vec!("ja")).unwrap();
436 assert_eq!(lang, "");
437 assert_eq!(body, &Body::from_str("Hello world!").unwrap());
438 }
439
440 let message = Message::new(None);
441
442 // Tests without a body.
443 assert_eq!(message.get_best_body(vec!("ja")), None);
444 }
445
446 #[test]
447 fn test_attention() {
448 #[cfg(not(feature = "component"))]
449 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
450 #[cfg(feature = "component")]
451 let elem: Element = "<message xmlns='jabber:component:accept' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
452 let elem1 = elem.clone();
453 let message = Message::try_from(elem).unwrap();
454 let elem2 = message.into();
455 assert_eq!(elem1, elem2);
456 }
457}