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 TryFrom<Element> for MessagePayload {
46 type Error = Error;
47
48 fn try_from(elem: 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),
83 })
84 }
85}
86
87impl Into<Element> for MessagePayload {
88 fn into(self) -> Element {
89 match self {
90 MessagePayload::StanzaError(stanza_error) => stanza_error.into(),
91 MessagePayload::Attention(attention) => attention.into(),
92 MessagePayload::ChatState(chatstate) => chatstate.into(),
93 MessagePayload::Receipt(receipt) => receipt.into(),
94 MessagePayload::Delay(delay) => delay.into(),
95 MessagePayload::MessageCorrect(replace) => replace.into(),
96 MessagePayload::ExplicitMessageEncryption(eme) => eme.into(),
97 MessagePayload::StanzaId(stanza_id) => stanza_id.into(),
98 MessagePayload::MamResult(result) => result.into(),
99
100 MessagePayload::Unknown(elem) => elem,
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 TryFrom<Element> for Message {
166 type Error = Error;
167
168 fn try_from(root: 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 = get_attr!(root, "from", optional);
173 let to = get_attr!(root, "to", optional);
174 let id = get_attr!(root, "id", optional);
175 let type_ = get_attr!(root, "type", default);
176 let mut bodies = BTreeMap::new();
177 let mut subjects = BTreeMap::new();
178 let mut thread = None;
179 let mut payloads = vec!();
180 for elem in root.children() {
181 if elem.is("body", ns::JABBER_CLIENT) {
182 for _ in elem.children() {
183 return Err(Error::ParseError("Unknown child in body element."));
184 }
185 let lang = get_attr!(root, "xml:lang", default);
186 if bodies.insert(lang, elem.text()).is_some() {
187 return Err(Error::ParseError("Body element present twice for the same xml:lang."));
188 }
189 } else if elem.is("subject", ns::JABBER_CLIENT) {
190 for _ in elem.children() {
191 return Err(Error::ParseError("Unknown child in subject element."));
192 }
193 let lang = get_attr!(root, "xml:lang", default);
194 if subjects.insert(lang, elem.text()).is_some() {
195 return Err(Error::ParseError("Subject element present twice for the same xml:lang."));
196 }
197 } else if elem.is("thread", ns::JABBER_CLIENT) {
198 if thread.is_some() {
199 return Err(Error::ParseError("Thread element present twice."));
200 }
201 for _ in elem.children() {
202 return Err(Error::ParseError("Unknown child in thread element."));
203 }
204 thread = Some(elem.text());
205 } else {
206 payloads.push(elem.clone())
207 }
208 }
209 Ok(Message {
210 from: from,
211 to: to,
212 id: id,
213 type_: type_,
214 bodies: bodies,
215 subjects: subjects,
216 thread: thread,
217 payloads: payloads,
218 })
219 }
220}
221
222impl Into<Element> for Message {
223 fn into(self) -> Element {
224 Element::builder("message")
225 .ns(ns::JABBER_CLIENT)
226 .attr("from", self.from.and_then(|value| Some(String::from(value))))
227 .attr("to", self.to.and_then(|value| Some(String::from(value))))
228 .attr("id", self.id)
229 .attr("type", self.type_)
230 .append(self.subjects.iter()
231 .map(|(lang, subject)| {
232 Element::builder("subject")
233 .ns(ns::JABBER_CLIENT)
234 .attr("xml:lang", match lang.as_ref() {
235 "" => None,
236 lang => Some(lang),
237 })
238 .append(subject)
239 .build() })
240 .collect::<Vec<_>>())
241 .append(self.bodies.iter()
242 .map(|(lang, body)| {
243 Element::builder("body")
244 .ns(ns::JABBER_CLIENT)
245 .attr("xml:lang", match lang.as_ref() {
246 "" => None,
247 lang => Some(lang),
248 })
249 .append(body)
250 .build() })
251 .collect::<Vec<_>>())
252 .append(self.payloads)
253 .build()
254 }
255}
256
257#[cfg(test)]
258mod tests {
259 use super::*;
260
261 #[test]
262 fn test_simple() {
263 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
264 let message = Message::try_from(elem).unwrap();
265 assert_eq!(message.from, None);
266 assert_eq!(message.to, None);
267 assert_eq!(message.id, None);
268 assert_eq!(message.type_, MessageType::Normal);
269 assert!(message.payloads.is_empty());
270 }
271
272 #[test]
273 fn test_serialise() {
274 let elem: Element = "<message xmlns='jabber:client' type='normal'/>".parse().unwrap();
275 let message = Message {
276 from: None,
277 to: None,
278 id: None,
279 type_: MessageType::Normal,
280 bodies: BTreeMap::new(),
281 subjects: BTreeMap::new(),
282 thread: None,
283 payloads: vec!(),
284 };
285 let elem2 = message.into();
286 assert_eq!(elem, elem2);
287 }
288
289 #[test]
290 fn test_body() {
291 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
292 let elem1 = elem.clone();
293 let message = Message::try_from(elem).unwrap();
294 assert_eq!(message.bodies[""], "Hello world!");
295
296 let elem2 = message.into();
297 assert_eq!(elem1, elem2);
298 }
299
300 #[test]
301 fn test_serialise_body() {
302 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
303 let mut bodies = BTreeMap::new();
304 bodies.insert(String::from(""), String::from("Hello world!"));
305 let message = Message {
306 from: None,
307 to: Some(Jid::from_str("coucou@example.org").unwrap()),
308 id: None,
309 type_: MessageType::Chat,
310 bodies: bodies,
311 subjects: BTreeMap::new(),
312 thread: None,
313 payloads: vec!(),
314 };
315 let elem2 = message.into();
316 assert_eq!(elem, elem2);
317 }
318
319 #[test]
320 fn test_subject() {
321 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
322 let elem1 = elem.clone();
323 let message = Message::try_from(elem).unwrap();
324 assert_eq!(message.subjects[""], "Hello world!");
325
326 let elem2 = message.into();
327 assert_eq!(elem1, elem2);
328 }
329
330 #[test]
331 fn test_attention() {
332 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
333 let elem1 = elem.clone();
334 let message = Message::try_from(elem).unwrap();
335 let elem2 = message.into();
336 assert_eq!(elem1, elem2);
337 }
338}