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::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}