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
105generate_attribute!(MessageType, "type", {
106 Chat => "chat",
107 Error => "error",
108 Groupchat => "groupchat",
109 Headline => "headline",
110 Normal => "normal",
111}, Default = Normal);
112
113type Lang = String;
114type Body = String;
115type Subject = String;
116type Thread = String;
117
118#[derive(Debug, Clone)]
119pub struct Message {
120 pub from: Option<Jid>,
121 pub to: Option<Jid>,
122 pub id: Option<String>,
123 pub type_: MessageType,
124 pub bodies: BTreeMap<Lang, Body>,
125 pub subjects: BTreeMap<Lang, Subject>,
126 pub thread: Option<Thread>,
127 pub payloads: Vec<Element>,
128}
129
130impl TryFrom<Element> for Message {
131 type Error = Error;
132
133 fn try_from(root: Element) -> Result<Message, Error> {
134 if !root.is("message", ns::JABBER_CLIENT) {
135 return Err(Error::ParseError("This is not a message element."));
136 }
137 let from = get_attr!(root, "from", optional);
138 let to = get_attr!(root, "to", optional);
139 let id = get_attr!(root, "id", optional);
140 let type_ = get_attr!(root, "type", default);
141 let mut bodies = BTreeMap::new();
142 let mut subjects = BTreeMap::new();
143 let mut thread = None;
144 let mut payloads = vec!();
145 for elem in root.children() {
146 if elem.is("body", ns::JABBER_CLIENT) {
147 for _ in elem.children() {
148 return Err(Error::ParseError("Unknown child in body element."));
149 }
150 let lang = get_attr!(root, "xml:lang", default);
151 if bodies.insert(lang, elem.text()).is_some() {
152 return Err(Error::ParseError("Body element present twice for the same xml:lang."));
153 }
154 } else if elem.is("subject", ns::JABBER_CLIENT) {
155 for _ in elem.children() {
156 return Err(Error::ParseError("Unknown child in subject element."));
157 }
158 let lang = get_attr!(root, "xml:lang", default);
159 if subjects.insert(lang, elem.text()).is_some() {
160 return Err(Error::ParseError("Subject element present twice for the same xml:lang."));
161 }
162 } else if elem.is("thread", ns::JABBER_CLIENT) {
163 if thread.is_some() {
164 return Err(Error::ParseError("Thread element present twice."));
165 }
166 for _ in elem.children() {
167 return Err(Error::ParseError("Unknown child in thread element."));
168 }
169 thread = Some(elem.text());
170 } else {
171 payloads.push(elem.clone())
172 }
173 }
174 Ok(Message {
175 from: from,
176 to: to,
177 id: id,
178 type_: type_,
179 bodies: bodies,
180 subjects: subjects,
181 thread: thread,
182 payloads: payloads,
183 })
184 }
185}
186
187impl Into<Element> for Message {
188 fn into(self) -> Element {
189 Element::builder("message")
190 .ns(ns::JABBER_CLIENT)
191 .attr("from", self.from.and_then(|value| Some(String::from(value))))
192 .attr("to", self.to.and_then(|value| Some(String::from(value))))
193 .attr("id", self.id)
194 .attr("type", self.type_)
195 .append(self.subjects.iter()
196 .map(|(lang, subject)| {
197 Element::builder("subject")
198 .ns(ns::JABBER_CLIENT)
199 .attr("xml:lang", match lang.as_ref() {
200 "" => None,
201 lang => Some(lang),
202 })
203 .append(subject)
204 .build() })
205 .collect::<Vec<_>>())
206 .append(self.bodies.iter()
207 .map(|(lang, body)| {
208 Element::builder("body")
209 .ns(ns::JABBER_CLIENT)
210 .attr("xml:lang", match lang.as_ref() {
211 "" => None,
212 lang => Some(lang),
213 })
214 .append(body)
215 .build() })
216 .collect::<Vec<_>>())
217 .append(self.payloads)
218 .build()
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225
226 #[test]
227 fn test_simple() {
228 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
229 let message = Message::try_from(elem).unwrap();
230 assert_eq!(message.from, None);
231 assert_eq!(message.to, None);
232 assert_eq!(message.id, None);
233 assert_eq!(message.type_, MessageType::Normal);
234 assert!(message.payloads.is_empty());
235 }
236
237 #[test]
238 fn test_serialise() {
239 let elem: Element = "<message xmlns='jabber:client' type='normal'/>".parse().unwrap();
240 let message = Message {
241 from: None,
242 to: None,
243 id: None,
244 type_: MessageType::Normal,
245 bodies: BTreeMap::new(),
246 subjects: BTreeMap::new(),
247 thread: None,
248 payloads: vec!(),
249 };
250 let elem2 = message.into();
251 assert_eq!(elem, elem2);
252 }
253
254 #[test]
255 fn test_body() {
256 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
257 let elem1 = elem.clone();
258 let message = Message::try_from(elem).unwrap();
259 assert_eq!(message.bodies[""], "Hello world!");
260
261 let elem2 = message.into();
262 assert_eq!(elem1, elem2);
263 }
264
265 #[test]
266 fn test_serialise_body() {
267 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
268 let mut bodies = BTreeMap::new();
269 bodies.insert(String::from(""), String::from("Hello world!"));
270 let message = Message {
271 from: None,
272 to: Some(Jid::from_str("coucou@example.org").unwrap()),
273 id: None,
274 type_: MessageType::Chat,
275 bodies: bodies,
276 subjects: BTreeMap::new(),
277 thread: None,
278 payloads: vec!(),
279 };
280 let elem2 = message.into();
281 assert_eq!(elem, elem2);
282 }
283
284 #[test]
285 fn test_subject() {
286 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><subject>Hello world!</subject></message>".parse().unwrap();
287 let elem1 = elem.clone();
288 let message = Message::try_from(elem).unwrap();
289 assert_eq!(message.subjects[""], "Hello world!");
290
291 let elem2 = message.into();
292 assert_eq!(elem1, elem2);
293 }
294
295 #[test]
296 fn test_attention() {
297 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
298 let elem1 = elem.clone();
299 let message = Message::try_from(elem).unwrap();
300 let elem2 = message.into();
301 assert_eq!(elem1, elem2);
302 }
303}