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