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