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