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;
90
91#[derive(Debug, Clone)]
92pub struct Message {
93 pub from: Option<Jid>,
94 pub to: Option<Jid>,
95 pub id: Option<String>,
96 pub type_: MessageType,
97 pub bodies: BTreeMap<Lang, Body>,
98 pub payloads: Vec<MessagePayloadType>,
99}
100
101impl<'a> TryFrom<&'a Element> for Message {
102 type Error = Error;
103
104 fn try_from(root: &'a Element) -> Result<Message, Error> {
105 if !root.is("message", ns::JABBER_CLIENT) {
106 return Err(Error::ParseError("This is not a message element."));
107 }
108 let from = root.attr("from")
109 .and_then(|value| value.parse().ok());
110 let to = root.attr("to")
111 .and_then(|value| value.parse().ok());
112 let id = root.attr("id")
113 .and_then(|value| value.parse().ok());
114 let type_ = match root.attr("type") {
115 Some(type_) => type_.parse()?,
116 None => Default::default(),
117 };
118 let mut bodies = BTreeMap::new();
119 let mut payloads = vec!();
120 for elem in root.children() {
121 if elem.is("body", ns::JABBER_CLIENT) {
122 for _ in elem.children() {
123 return Err(Error::ParseError("Unknown child in body element."));
124 }
125 let lang = elem.attr("xml:lang").unwrap_or("").to_owned();
126 if let Some(_) = bodies.insert(lang, elem.text()) {
127 return Err(Error::ParseError("Body element present twice for the same xml:lang."));
128 }
129 } else {
130 let payload = if let Ok(stanza_error) = StanzaError::try_from(elem) {
131 Some(MessagePayload::StanzaError(stanza_error))
132 } else if let Ok(chatstate) = ChatState::try_from(elem) {
133 Some(MessagePayload::ChatState(chatstate))
134 } else if let Ok(receipt) = Receipt::try_from(elem) {
135 Some(MessagePayload::Receipt(receipt))
136 } else if let Ok(delay) = Delay::try_from(elem) {
137 Some(MessagePayload::Delay(delay))
138 } else if let Ok(attention) = Attention::try_from(elem) {
139 Some(MessagePayload::Attention(attention))
140 } else if let Ok(replace) = Replace::try_from(elem) {
141 Some(MessagePayload::MessageCorrect(replace))
142 } else if let Ok(eme) = ExplicitMessageEncryption::try_from(elem) {
143 Some(MessagePayload::ExplicitMessageEncryption(eme))
144 } else {
145 None
146 };
147 payloads.push(match payload {
148 Some(payload) => MessagePayloadType::Parsed(payload),
149 None => MessagePayloadType::XML(elem.clone()),
150 });
151 }
152 }
153 Ok(Message {
154 from: from,
155 to: to,
156 id: id,
157 type_: type_,
158 bodies: BTreeMap::new(),
159 payloads: payloads,
160 })
161 }
162}
163
164impl<'a> Into<Element> for &'a MessagePayload {
165 fn into(self) -> Element {
166 match *self {
167 MessagePayload::StanzaError(ref stanza_error) => stanza_error.into(),
168 MessagePayload::Attention(ref attention) => attention.into(),
169 MessagePayload::ChatState(ref chatstate) => chatstate.into(),
170 MessagePayload::Receipt(ref receipt) => receipt.into(),
171 MessagePayload::Delay(ref delay) => delay.into(),
172 MessagePayload::MessageCorrect(ref replace) => replace.into(),
173 MessagePayload::ExplicitMessageEncryption(ref eme) => eme.into(),
174 }
175 }
176}
177
178impl<'a> Into<Element> for &'a Message {
179 fn into(self) -> Element {
180 let mut stanza = Element::builder("message")
181 .ns(ns::JABBER_CLIENT)
182 .attr("from", self.from.clone().and_then(|value| Some(String::from(value))))
183 .attr("to", self.to.clone().and_then(|value| Some(String::from(value))))
184 .attr("id", self.id.clone())
185 .attr("type", self.type_.clone())
186 .append(self.bodies.iter()
187 .map(|(lang, body)| {
188 Element::builder("body")
189 .ns(ns::JABBER_CLIENT)
190 .attr("xml:lang", match lang.as_ref() {
191 "" => None,
192 lang => Some(lang),
193 })
194 .append(body.clone())
195 .build() })
196 .collect::<Vec<_>>())
197 .build();
198 for child in self.payloads.clone() {
199 let elem = match child {
200 MessagePayloadType::XML(elem) => elem,
201 MessagePayloadType::Parsed(payload) => (&payload).into(),
202 };
203 stanza.append_child(elem);
204 }
205 stanza
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn test_simple() {
215 let elem: Element = "<message xmlns='jabber:client'/>".parse().unwrap();
216 let message = Message::try_from(&elem).unwrap();
217 assert_eq!(message.from, None);
218 assert_eq!(message.to, None);
219 assert_eq!(message.id, None);
220 assert_eq!(message.type_, MessageType::Normal);
221 assert!(message.payloads.is_empty());
222 }
223
224 #[test]
225 fn test_serialise() {
226 let elem: Element = "<message xmlns='jabber:client' type='normal'/>".parse().unwrap();
227 let message = Message {
228 from: None,
229 to: None,
230 id: None,
231 type_: MessageType::Normal,
232 bodies: BTreeMap::new(),
233 payloads: vec!(),
234 };
235 let elem2 = (&message).into();
236 assert_eq!(elem, elem2);
237 }
238
239 #[test]
240 fn test_body() {
241 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
242 Message::try_from(&elem).unwrap();
243 }
244
245 #[test]
246 fn test_serialise_body() {
247 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><body>Hello world!</body></message>".parse().unwrap();
248 let mut bodies = BTreeMap::new();
249 bodies.insert(String::from(""), String::from("Hello world!"));
250 let message = Message {
251 from: None,
252 to: Some(Jid::from_str("coucou@example.org").unwrap()),
253 id: None,
254 type_: MessageType::Chat,
255 bodies: bodies,
256 payloads: vec!(),
257 };
258 let elem2 = (&message).into();
259 assert_eq!(elem, elem2);
260 }
261
262 #[test]
263 fn test_attention() {
264 let elem: Element = "<message xmlns='jabber:client' to='coucou@example.org' type='chat'><attention xmlns='urn:xmpp:attention:0'/></message>".parse().unwrap();
265 let message = Message::try_from(&elem).unwrap();
266 let elem2 = (&message).into();
267 assert_eq!(elem, elem2);
268 }
269}