ibb.rs

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