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