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