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