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::str::FromStr;
8
9use minidom::{Element, IntoAttributeValue};
10use base64;
11
12use error::Error;
13
14use ns;
15
16#[derive(Debug, Clone, PartialEq)]
17pub enum Stanza {
18 Iq,
19 Message,
20}
21
22impl Default for Stanza {
23 fn default() -> Stanza {
24 Stanza::Iq
25 }
26}
27
28impl FromStr for Stanza {
29 type Err = Error;
30
31 fn from_str(s: &str) -> Result<Stanza, Error> {
32 Ok(match s {
33 "iq" => Stanza::Iq,
34 "message" => Stanza::Message,
35
36 _ => return Err(Error::ParseError("Invalid 'stanza' attribute.")),
37 })
38 }
39}
40
41impl IntoAttributeValue for Stanza {
42 fn into_attribute_value(self) -> Option<String> {
43 match self {
44 Stanza::Iq => None,
45 Stanza::Message => Some(String::from("message")),
46 }
47 }
48}
49
50#[derive(Debug, Clone)]
51pub enum IBB {
52 Open {
53 block_size: u16,
54 sid: String,
55 stanza: Stanza,
56 },
57 Data {
58 seq: u16,
59 sid: String,
60 data: Vec<u8>,
61 },
62 Close {
63 sid: String,
64 },
65}
66
67fn required_attr<T: FromStr>(root: &Element, attr: &str, err: Error) -> Result<T, Error> {
68 root.attr(attr)
69 .and_then(|value| value.parse().ok())
70 .ok_or(err)
71}
72
73pub fn parse_ibb(root: &Element) -> Result<IBB, Error> {
74 if root.is("open", ns::IBB) {
75 for _ in root.children() {
76 return Err(Error::ParseError("Unknown child in open element."));
77 }
78 let block_size = required_attr(root, "block-size", Error::ParseError("Required attribute 'block-size' missing in open element."))?;
79 let sid = required_attr(root, "sid", Error::ParseError("Required attribute 'sid' missing in open element."))?;
80 let stanza = match root.attr("stanza") {
81 Some(stanza) => stanza.parse()?,
82 None => Default::default(),
83 };
84 Ok(IBB::Open {
85 block_size: block_size,
86 sid: sid,
87 stanza: stanza
88 })
89 } else if root.is("data", ns::IBB) {
90 for _ in root.children() {
91 return Err(Error::ParseError("Unknown child in data element."));
92 }
93 let seq = required_attr(root, "seq", Error::ParseError("Required attribute 'seq' missing in data element."))?;
94 let sid = required_attr(root, "sid", Error::ParseError("Required attribute 'sid' missing in data element."))?;
95 let data = base64::decode(&root.text())?;
96 Ok(IBB::Data {
97 seq: seq,
98 sid: sid,
99 data: data
100 })
101 } else if root.is("close", ns::IBB) {
102 let sid = required_attr(root, "sid", Error::ParseError("Required attribute 'sid' missing in data element."))?;
103 for _ in root.children() {
104 return Err(Error::ParseError("Unknown child in close element."));
105 }
106 Ok(IBB::Close {
107 sid: sid,
108 })
109 } else {
110 Err(Error::ParseError("This is not an ibb element."))
111 }
112}
113
114pub fn serialise(ibb: &IBB) -> Element {
115 match *ibb {
116 IBB::Open { ref block_size, ref sid, ref stanza } => {
117 Element::builder("open")
118 .ns(ns::IBB)
119 .attr("block-size", format!("{}", block_size))
120 .attr("sid", sid.to_owned())
121 .attr("stanza", stanza.to_owned())
122 .build()
123 },
124 IBB::Data { ref seq, ref sid, ref data } => {
125 Element::builder("data")
126 .ns(ns::IBB)
127 .attr("seq", format!("{}", seq))
128 .attr("sid", sid.to_owned())
129 .append(base64::encode(&data))
130 .build()
131 },
132 IBB::Close { ref sid } => {
133 Element::builder("close")
134 .ns(ns::IBB)
135 .attr("sid", sid.to_owned())
136 .build()
137 },
138 }
139}
140
141#[cfg(test)]
142mod tests {
143 use minidom::Element;
144 use error::Error;
145 use ibb;
146
147 #[test]
148 fn test_simple() {
149 let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='3' sid='coucou'/>".parse().unwrap();
150 let open = ibb::parse_ibb(&elem).unwrap();
151 match open {
152 ibb::IBB::Open { block_size, sid, stanza } => {
153 assert_eq!(block_size, 3);
154 assert_eq!(sid, "coucou");
155 assert_eq!(stanza, ibb::Stanza::Iq);
156 },
157 _ => panic!(),
158 }
159
160 let elem: Element = "<data xmlns='http://jabber.org/protocol/ibb' seq='0' sid='coucou'>AAAA</data>".parse().unwrap();
161 let data = ibb::parse_ibb(&elem).unwrap();
162 match data {
163 ibb::IBB::Data { seq, sid, data } => {
164 assert_eq!(seq, 0);
165 assert_eq!(sid, "coucou");
166 assert_eq!(data, vec!(0, 0, 0));
167 },
168 _ => panic!(),
169 }
170
171 let elem: Element = "<close xmlns='http://jabber.org/protocol/ibb' sid='coucou'/>".parse().unwrap();
172 let close = ibb::parse_ibb(&elem).unwrap();
173 match close {
174 ibb::IBB::Close { sid } => {
175 assert_eq!(sid, "coucou");
176 },
177 _ => panic!(),
178 }
179 }
180
181 #[test]
182 fn test_invalid() {
183 let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb'/>".parse().unwrap();
184 let error = ibb::parse_ibb(&elem).unwrap_err();
185 let message = match error {
186 Error::ParseError(string) => string,
187 _ => panic!(),
188 };
189 assert_eq!(message, "Required attribute 'block-size' missing in open element.");
190
191 // TODO: maybe make a better error message here.
192 let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='-5'/>".parse().unwrap();
193 let error = ibb::parse_ibb(&elem).unwrap_err();
194 let message = match error {
195 Error::ParseError(string) => string,
196 _ => panic!(),
197 };
198 assert_eq!(message, "Required attribute 'block-size' missing in open element.");
199
200 let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='128'/>".parse().unwrap();
201 let error = ibb::parse_ibb(&elem).unwrap_err();
202 let message = match error {
203 Error::ParseError(string) => string,
204 _ => panic!(),
205 };
206 assert_eq!(message, "Required attribute 'sid' missing in open element.");
207 }
208
209 #[test]
210 fn test_invalid_stanza() {
211 let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='128' sid='coucou' stanza='fdsq'/>".parse().unwrap();
212 let error = ibb::parse_ibb(&elem).unwrap_err();
213 let message = match error {
214 Error::ParseError(string) => string,
215 _ => panic!(),
216 };
217 assert_eq!(message, "Invalid 'stanza' attribute.");
218 }
219}