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