ibb.rs

  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
 68fn required_attr<T: FromStr>(elem: &Element, attr: &str, err: Error) -> Result<T, Error> {
 69    elem.attr(attr)
 70        .and_then(|value| value.parse().ok())
 71        .ok_or(err)
 72}
 73
 74impl<'a> TryFrom<&'a Element> for IBB {
 75    type Error = Error;
 76
 77    fn try_from(elem: &'a Element) -> Result<IBB, Error> {
 78        if elem.is("open", ns::IBB) {
 79            for _ in elem.children() {
 80                return Err(Error::ParseError("Unknown child in open element."));
 81            }
 82            let block_size = required_attr(elem, "block-size", Error::ParseError("Required attribute 'block-size' missing in open element."))?;
 83            let sid = required_attr(elem, "sid", Error::ParseError("Required attribute 'sid' missing in open element."))?;
 84            let stanza = match elem.attr("stanza") {
 85                Some(stanza) => stanza.parse()?,
 86                None => Default::default(),
 87            };
 88            Ok(IBB::Open {
 89                block_size: block_size,
 90                sid: sid,
 91                stanza: stanza
 92            })
 93        } else if elem.is("data", ns::IBB) {
 94            for _ in elem.children() {
 95                return Err(Error::ParseError("Unknown child in data element."));
 96            }
 97            let seq = required_attr(elem, "seq", Error::ParseError("Required attribute 'seq' missing in data element."))?;
 98            let sid = required_attr(elem, "sid", Error::ParseError("Required attribute 'sid' missing in data element."))?;
 99            let data = base64::decode(&elem.text())?;
100            Ok(IBB::Data {
101                seq: seq,
102                sid: sid,
103                data: data
104            })
105        } else if elem.is("close", ns::IBB) {
106            let sid = required_attr(elem, "sid", Error::ParseError("Required attribute 'sid' missing in data element."))?;
107            for _ in elem.children() {
108                return Err(Error::ParseError("Unknown child in close element."));
109            }
110            Ok(IBB::Close {
111                sid: sid,
112            })
113        } else {
114            Err(Error::ParseError("This is not an ibb element."))
115        }
116    }
117}
118
119impl<'a> Into<Element> for &'a IBB {
120    fn into(self) -> Element {
121        match *self {
122            IBB::Open { ref block_size, ref sid, ref stanza } => {
123                Element::builder("open")
124                        .ns(ns::IBB)
125                        .attr("block-size", format!("{}", block_size))
126                        .attr("sid", sid.to_owned())
127                        .attr("stanza", stanza.to_owned())
128                        .build()
129            },
130            IBB::Data { ref seq, ref sid, ref data } => {
131                Element::builder("data")
132                        .ns(ns::IBB)
133                        .attr("seq", format!("{}", seq))
134                        .attr("sid", sid.to_owned())
135                        .append(base64::encode(&data))
136                        .build()
137            },
138            IBB::Close { ref sid } => {
139                Element::builder("close")
140                        .ns(ns::IBB)
141                        .attr("sid", sid.to_owned())
142                        .build()
143            },
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use super::*;
151
152    #[test]
153    fn test_simple() {
154        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='3' sid='coucou'/>".parse().unwrap();
155        let open = IBB::try_from(&elem).unwrap();
156        match open {
157            IBB::Open { block_size, sid, stanza } => {
158                assert_eq!(block_size, 3);
159                assert_eq!(sid, "coucou");
160                assert_eq!(stanza, Stanza::Iq);
161            },
162            _ => panic!(),
163        }
164
165        let elem: Element = "<data xmlns='http://jabber.org/protocol/ibb' seq='0' sid='coucou'>AAAA</data>".parse().unwrap();
166        let data = IBB::try_from(&elem).unwrap();
167        match data {
168            IBB::Data { seq, sid, data } => {
169                assert_eq!(seq, 0);
170                assert_eq!(sid, "coucou");
171                assert_eq!(data, vec!(0, 0, 0));
172            },
173            _ => panic!(),
174        }
175
176        let elem: Element = "<close xmlns='http://jabber.org/protocol/ibb' sid='coucou'/>".parse().unwrap();
177        let close = IBB::try_from(&elem).unwrap();
178        match close {
179            IBB::Close { sid } => {
180                assert_eq!(sid, "coucou");
181            },
182            _ => panic!(),
183        }
184    }
185
186    #[test]
187    fn test_invalid() {
188        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb'/>".parse().unwrap();
189        let error = IBB::try_from(&elem).unwrap_err();
190        let message = match error {
191            Error::ParseError(string) => string,
192            _ => panic!(),
193        };
194        assert_eq!(message, "Required attribute 'block-size' missing in open element.");
195
196        // TODO: maybe make a better error message here.
197        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='-5'/>".parse().unwrap();
198        let error = IBB::try_from(&elem).unwrap_err();
199        let message = match error {
200            Error::ParseError(string) => string,
201            _ => panic!(),
202        };
203        assert_eq!(message, "Required attribute 'block-size' missing in open element.");
204
205        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='128'/>".parse().unwrap();
206        let error = IBB::try_from(&elem).unwrap_err();
207        let message = match error {
208            Error::ParseError(string) => string,
209            _ => panic!(),
210        };
211        assert_eq!(message, "Required attribute 'sid' missing in open element.");
212    }
213
214    #[test]
215    fn test_invalid_stanza() {
216        let elem: Element = "<open xmlns='http://jabber.org/protocol/ibb' block-size='128' sid='coucou' stanza='fdsq'/>".parse().unwrap();
217        let error = IBB::try_from(&elem).unwrap_err();
218        let message = match error {
219            Error::ParseError(string) => string,
220            _ => panic!(),
221        };
222        assert_eq!(message, "Invalid 'stanza' attribute.");
223    }
224}