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