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 chatstates::ChatState;
21use receipts::Receipt;
22use delay::Delay;
23use attention::Attention;
24use message_correct::Replace;
25use eme::ExplicitMessageEncryption;
26use stanza_id::StanzaId;
27use mam::Result_ as MamResult;
28
29/// Lists every known payload of a `<message/>`.
30#[derive(Debug, Clone)]
31pub enum MessagePayload {
32 StanzaError(StanzaError),
33 ChatState(ChatState),
34 Receipt(Receipt),
35 Delay(Delay),
36 Attention(Attention),
37 MessageCorrect(Replace),
38 ExplicitMessageEncryption(ExplicitMessageEncryption),
39 StanzaId(StanzaId),
40 MamResult(MamResult),
41
42 Unknown(Element),
43}
44
45impl<'a> TryFrom<&'a Element> for MessagePayload {
46 type Error = Error;
47
48 fn try_from(elem: &'a Element) -> Result<MessagePayload, Error> {
49 Ok(match (elem.name().as_ref(), elem.ns().unwrap().as_ref()) {
50 ("error", ns::JABBER_CLIENT) => MessagePayload::StanzaError(StanzaError::try_from(elem)?),
51
52 // XEP-0085
53 ("active", ns::CHATSTATES)
54 | ("inactive", ns::CHATSTATES)
55 | ("composing", ns::CHATSTATES)
56 | ("paused", ns::CHATSTATES)
57 | ("gone", ns::CHATSTATES) => MessagePayload::ChatState(ChatState::try_from(elem)?),
58
59 // XEP-0184
60 ("request", ns::RECEIPTS)
61 | ("received", ns::RECEIPTS) => MessagePayload::Receipt(Receipt::try_from(elem)?),
62
63 // XEP-0203
64 ("delay", ns::DELAY) => MessagePayload::Delay(Delay::try_from(elem)?),
65
66 // XEP-0224
67 ("attention", ns::ATTENTION) => MessagePayload::Attention(Attention::try_from(elem)?),
68
69 // XEP-0308
70 ("replace", ns::MESSAGE_CORRECT) => MessagePayload::MessageCorrect(Replace::try_from(elem)?),
71
72 // XEP-0313
73 ("result", ns::MAM) => MessagePayload::MamResult(MamResult::try_from(elem)?),
74
75 // XEP-0359
76 ("stanza-id", ns::SID)
77 | ("origin-id", ns::SID) => MessagePayload::StanzaId(StanzaId::try_from(elem)?),
78
79 // XEP-0380
80 ("encryption", ns::EME) => MessagePayload::ExplicitMessageEncryption(ExplicitMessageEncryption::try_from(elem)?),
81
82 _ => MessagePayload::Unknown(elem.clone()),
83 })
84 }
85}
86
87impl<'a> Into<Element> for &'a MessagePayload {
88 fn into(self) -> Element {
89 match *self {
90 MessagePayload::StanzaError(ref stanza_error) => stanza_error.into(),
91 MessagePayload::Attention(ref attention) => attention.into(),
92 MessagePayload::ChatState(ref chatstate) => chatstate.into(),
93 MessagePayload::Receipt(ref receipt) => receipt.into(),
94 MessagePayload::Delay(ref delay) => delay.into(),
95 MessagePayload::MessageCorrect(ref replace) => replace.into(),
96 MessagePayload::ExplicitMessageEncryption(ref eme) => eme.into(),
97 MessagePayload::StanzaId(ref stanza_id) => stanza_id.into(),
98 MessagePayload::MamResult(ref result) => result.into(),
99
100 MessagePayload::Unknown(ref elem) => elem.clone(),
101 }
102 }
103}
104
105#[derive(Debug, Clone, PartialEq)]
106pub enum MessageType {
107 Chat,
108 Error,
109 Groupchat,
110 Headline,
111 Normal,
112}
113
114impl Default for MessageType {
115 fn default() -> MessageType {
116 MessageType::Normal
117 }
118}
119
120impl FromStr for MessageType {
121 type Err = Error;
122
123 fn from_str(s: &str) -> Result<MessageType, Error> {
124 Ok(match s {
125 "chat" => MessageType::Chat,
126 "error" => MessageType::Error,
127 "groupchat" => MessageType::Groupchat,
128 "headline" => MessageType::Headline,
129 "normal" => MessageType::Normal,
130
131 _ => return Err(Error::ParseError("Invalid 'type' attribute on message element.")),
132 })
133 }
134}
135
136impl IntoAttributeValue for MessageType {
137 fn into_attribute_value(self) -> Option<String> {
138 Some(match self {
139 MessageType::Chat => "chat",
140 MessageType::Error => "error",
141 MessageType::Groupchat => "groupchat",
142 MessageType::Headline => "headline",
143 MessageType::Normal => "normal",
144 }.to_owned())
145 }
146}
147
148type Lang = String;
149type Body = String;
150type Subject = String;
151type Thread = String;
152
153#[derive(Debug, Clone)]
154pub struct Message {
155 pub from: Option<Jid>,
156 pub to: Option<Jid>,
157 pub id: Option<String>,
158 pub type_: MessageType,
159 pub bodies: BTreeMap<Lang, Body>,
160 pub subjects: BTreeMap<Lang, Subject>,
161 pub thread: Option<Thread>,
162 pub payloads: Vec<Element>,
163}
164
165impl<'a> TryFrom<&'a Element> for Message {
166 type Error = Error;
167
168 fn try_from(root: &'a Element) -> Result<Message, Error> {
169 if !root.is("message", ns::JABBER_CLIENT) {
170 return Err(Error::ParseError("This is not a message element."));
171 }
172 let from = root.attr("from")
173 .and_then(|value| value.parse().ok());
174 let to = root.attr("to")
175 .and_then(|value| value.parse().ok());
176 let id = root.attr("id")
177 .and_then(|value| value.parse().ok());
178 let type_ = match root.attr("type") {
179 Some(type_) => type_.parse()?,
180 None => Default::default(),
181 };
182 let mut bodies = BTreeMap::new();
183 let mut subjects = BTreeMap::new();
184 let mut thread = None;
185 let mut payloads = vec!();
186 for elem in root.children() {
187 if elem.is("body", ns::JABBER_CLIENT) {
188 for _ in elem.children() {
189 return Err(Error::ParseError("Unknown child in body element."));
190 }
191 let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
192 if bodies.insert(lang, elem.text()).is_some() {
193 return Err(Error::ParseError("Body element present twice for the same xml:lang."));
194 }
195 } else if elem.is("subject", ns::JABBER_CLIENT) {
196 for _ in elem.children() {
197 return Err(Error::ParseError("Unknown child in subject element."));
198 }
199 let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
200 if subjects.insert(lang, elem.text()).is_some() {
201 return Err(Error::ParseError("Subject element present twice for the same xml:lang."));
202 }
203 } else if elem.is("thread", ns::JABBER_CLIENT) {
204 if thread.is_some() {
205 return Err(Error::ParseError("Thread element present twice."));
206 }
207 for _ in elem.children() {
208 return Err(Error::ParseError("Unknown child in thread element."));
209 }
210 thread = Some(elem.text());
211 } else {
212 payloads.push(elem.clone())
213 }
214 }
215 Ok(Message {
216 from: from,
217 to: to,
218 id: id,
219 type_: type_,
220 bodies: bodies,
221 subjects: subjects,
222 thread: thread,
223 payloads: payloads,
224 })
225 }
226}
227
228impl<'a> Into<Element> for &'a Message {
229 fn into(self) -> Element {
230 let mut stanza = Element::builder("message")
231 .ns(ns::JABBER_CLIENT)
232 .attr("from", self.from.clone().and_then(|value| Some(String::from(value))))
233 .attr("to", self.to.clone().and_then(|value| Some(String::from(value))))
234 .attr("id", self.id.clone())
235 .attr("type", self.type_.clone())
236 .append(self.subjects.iter()
237 .map(|(lang, subject)| {
238 Element::builder("subject")
239 .ns(ns::JABBER_CLIENT)
240 .attr("xml:lang", match lang.as_ref() {
241 "" => None,
242 lang => Some(lang),
243 })
244 .append(subject.clone())
245 .build() })
246 .collect::<Vec<_>>())
247 .append(self.bodies.iter()
248 .map(|(lang, body)| {
249 Element::builder("body")
250 .ns(ns::JABBER_CLIENT)
251 .attr("xml:lang", match lang.as_ref() {
252 "" => None,
253 lang => Some(lang),
254 })
255 .append(body.clone())
256 .build() })
257 .collect::<Vec<_>>())
258 .build();
259 for child in self.payloads.clone() {
260 stanza.append_child(child);
261 }
262 stanza
263 }
264}
265
266#[cfg(test)]
267mod tests {
268 use super::*;
269
270 #[test]
271 fn test_simple() {
272 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
273 let message = Message::try_from(&elem).unwrap();
274 assert_eq!(message.from, None);
275 assert_eq!(message.to, None);
276 assert_eq!(message.id, None);
277 assert_eq!(message.type_, MessageType::Normal);
278 assert!(message.payloads.is_empty());
279 }
280
281 #[test]
282 fn test_serialise() {
283 let elem: Element = "<message xmlns='jabber:client' type='normal'/>".parse().unwrap();
284 let message = Message {
285 from: None,
286 to: None,
287 id: None,
288 type_: MessageType::Normal,
289 bodies: BTreeMap::new(),
290 subjects: BTreeMap::new(),
291 thread: None,
292 payloads: vec!(),
293 };
294 let elem2 = (&message).into();
295 assert_eq!(elem, elem2);
296 }
297
298 #[test]
299 fn test_body() {
300 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
301 let message = Message::try_from(&elem).unwrap();
302 assert_eq!(message.bodies[""], "Hello world!");
303
304 let elem2 = (&message).into();
305 assert_eq!(elem, elem2);
306 }
307
308 #[test]
309 fn test_serialise_body() {
310 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
311 let mut bodies = BTreeMap::new();
312 bodies.insert(String::from(""), String::from("Hello world!"));
313 let message = Message {
314 from: None,
315 to: Some(Jid::from_str("coucou@example.org").unwrap()),
316 id: None,
317 type_: MessageType::Chat,
318 bodies: bodies,
319 subjects: BTreeMap::new(),
320 thread: None,
321 payloads: vec!(),
322 };
323 let elem2 = (&message).into();
324 assert_eq!(elem, elem2);
325 }
326
327 #[test]
328 fn test_subject() {
329 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
330 let message = Message::try_from(&elem).unwrap();
331 assert_eq!(message.subjects[""], "Hello world!");
332
333 let elem2 = (&message).into();
334 assert_eq!(elem, elem2);
335 }
336
337 #[test]
338 fn test_attention() {
339 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
340 let message = Message::try_from(&elem).unwrap();
341 let elem2 = (&message).into();
342 assert_eq!(elem, elem2);
343 }
344}