jingle_ft.rs

  1extern crate minidom;
  2
  3use hashes;
  4use hashes::{Hash, parse_hash};
  5
  6use minidom::{Element, IntoElements};
  7use minidom::convert::ElementEmitter;
  8
  9use error::Error;
 10use ns;
 11
 12#[derive(Debug, Clone, PartialEq)]
 13pub struct Range {
 14    pub offset: u64,
 15    pub length: Option<u64>,
 16    pub hashes: Vec<Hash>,
 17}
 18
 19impl IntoElements for Range {
 20    fn into_elements(self, emitter: &mut ElementEmitter) {
 21        let mut elem = Element::builder("range")
 22                               .ns(ns::JINGLE_FT)
 23                               .attr("offset", format!("{}", self.offset))
 24                               .attr("length", match self.length {
 25                                    Some(length) => Some(format!("{}", length)),
 26                                    None => None
 27                                })
 28                               .build();
 29        for hash in self.hashes {
 30            elem.append_child(hashes::serialise(&hash));
 31        }
 32        emitter.append_child(elem);
 33    }
 34}
 35
 36#[derive(Debug, Clone)]
 37pub struct File {
 38    pub date: Option<String>,
 39    pub media_type: Option<String>,
 40    pub name: Option<String>,
 41    pub desc: Option<String>,
 42    pub size: Option<u64>,
 43    pub range: Option<Range>,
 44    pub hashes: Vec<Hash>,
 45}
 46
 47#[derive(Debug, Clone)]
 48pub struct Description {
 49    pub file: File,
 50}
 51
 52#[derive(Debug, Clone)]
 53pub enum Creator {
 54    Initiator,
 55    Responder,
 56}
 57
 58#[derive(Debug, Clone)]
 59pub struct Checksum {
 60    pub name: String,
 61    pub creator: Creator,
 62    pub file: File,
 63}
 64
 65#[derive(Debug, Clone)]
 66pub struct Received {
 67    pub name: String,
 68    pub creator: Creator,
 69}
 70
 71impl IntoElements for Received {
 72    fn into_elements(self, emitter: &mut ElementEmitter) {
 73        let elem = Element::builder("received")
 74                           .ns(ns::JINGLE_FT)
 75                           .attr("name", self.name)
 76                           .attr("creator", match self.creator {
 77                                Creator::Initiator => "initiator",
 78                                Creator::Responder => "responder",
 79                            })
 80                           .build();
 81        emitter.append_child(elem);
 82    }
 83}
 84
 85pub fn parse_jingle_ft(root: &Element) -> Result<Description, Error> {
 86    if !root.is("description", ns::JINGLE_FT) {
 87        return Err(Error::ParseError("This is not a JingleFT description element."));
 88    }
 89    if root.children().collect::<Vec<_>>().len() != 1 {
 90        return Err(Error::ParseError("JingleFT description element must have exactly one child."));
 91    }
 92
 93    let mut date = None;
 94    let mut media_type = None;
 95    let mut name = None;
 96    let mut desc = None;
 97    let mut size = None;
 98    let mut range = None;
 99    let mut hashes = vec!();
100    for description_payload in root.children() {
101        if !description_payload.is("file", ns::JINGLE_FT) {
102            return Err(Error::ParseError("Unknown element in JingleFT description."));
103        }
104        for file_payload in description_payload.children() {
105            if file_payload.is("date", ns::JINGLE_FT) {
106                if date.is_some() {
107                    return Err(Error::ParseError("File must not have more than one date."));
108                }
109                date = Some(file_payload.text());
110            } else if file_payload.is("media-type", ns::JINGLE_FT) {
111                if media_type.is_some() {
112                    return Err(Error::ParseError("File must not have more than one media-type."));
113                }
114                media_type = Some(file_payload.text());
115            } else if file_payload.is("name", ns::JINGLE_FT) {
116                if name.is_some() {
117                    return Err(Error::ParseError("File must not have more than one name."));
118                }
119                name = Some(file_payload.text());
120            } else if file_payload.is("desc", ns::JINGLE_FT) {
121                if desc.is_some() {
122                    return Err(Error::ParseError("File must not have more than one desc."));
123                }
124                desc = Some(file_payload.text());
125            } else if file_payload.is("size", ns::JINGLE_FT) {
126                if size.is_some() {
127                    return Err(Error::ParseError("File must not have more than one size."));
128                }
129                size = Some(file_payload.text().parse()?);
130            } else if file_payload.is("range", ns::JINGLE_FT) {
131                if range.is_some() {
132                    return Err(Error::ParseError("File must not have more than one range."));
133                }
134                let offset = file_payload.attr("offset").unwrap_or("0").parse()?;
135                let length = match file_payload.attr("length") {
136                    Some(length) => Some(length.parse()?),
137                    None => None,
138                };
139                let mut range_hashes = vec!();
140                for hash_element in file_payload.children() {
141                    if !hash_element.is("hash", ns::HASHES) {
142                        return Err(Error::ParseError("Unknown element in JingleFT range."));
143                    }
144                    range_hashes.push(parse_hash(hash_element)?);
145                }
146                range = Some(Range {
147                    offset: offset,
148                    length: length,
149                    hashes: range_hashes,
150                });
151            } else if file_payload.is("hash", ns::HASHES) {
152                hashes.push(parse_hash(file_payload)?);
153            } else {
154                return Err(Error::ParseError("Unknown element in JingleFT file."));
155            }
156        }
157    }
158
159    Ok(Description {
160        file: File {
161            date: date,
162            media_type: media_type,
163            name: name,
164            desc: desc,
165            size: size,
166            range: range,
167            hashes: hashes,
168        },
169    })
170}
171
172pub fn serialise_file(file: &File) -> Element {
173    let mut root = Element::builder("file")
174                           .ns(ns::JINGLE_FT)
175                           .build();
176    println!("{:#?}", file);
177    if let Some(ref date) = file.date {
178        root.append_child(Element::builder("date")
179                                  .ns(ns::JINGLE_FT)
180                                  .append(date.clone())
181                                  .build());
182    }
183    if let Some(ref media_type) = file.media_type {
184        root.append_child(Element::builder("media-type")
185                                  .ns(ns::JINGLE_FT)
186                                  .append(media_type.clone())
187                                  .build());
188    }
189    if let Some(ref name) = file.name {
190        root.append_child(Element::builder("name")
191                                  .ns(ns::JINGLE_FT)
192                                  .append(name.clone())
193                                  .build());
194    }
195    if let Some(ref desc) = file.desc {
196        root.append_child(Element::builder("desc")
197                                  .ns(ns::JINGLE_FT)
198                                  .append(desc.clone())
199                                  .build());
200    }
201    if let Some(ref size) = file.size {
202        root.append_child(Element::builder("size")
203                                  .ns(ns::JINGLE_FT)
204                                  .append(format!("{}", size))
205                                  .build());
206    }
207    if let Some(ref range) = file.range {
208        root.append_child(Element::builder("range")
209                                  .ns(ns::JINGLE_FT)
210                                  .append(range.clone())
211                                  .build());
212    }
213    for hash in file.hashes.clone() {
214        root.append_child(hashes::serialise(&hash));
215    }
216    root
217}
218
219pub fn serialise(desc: &Description) -> Element {
220    Element::builder("description")
221            .ns(ns::JINGLE_FT)
222            .append(serialise_file(&desc.file))
223            .build()
224}
225
226#[cfg(test)]
227mod tests {
228    use minidom::Element;
229    use jingle_ft;
230
231    #[test]
232    fn test_description() {
233        let elem: Element = r#"
234<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
235  <file>
236    <media-type>text/plain</media-type>
237    <name>test.txt</name>
238    <date>2015-07-26T21:46:00</date>
239    <size>6144</size>
240    <hash xmlns='urn:xmpp:hashes:2'
241          algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash>
242  </file>
243</description>
244"#.parse().unwrap();
245
246        let desc = jingle_ft::parse_jingle_ft(&elem).unwrap();
247        assert_eq!(desc.file.media_type, Some(String::from("text/plain")));
248        assert_eq!(desc.file.name, Some(String::from("test.txt")));
249        assert_eq!(desc.file.desc, None);
250        assert_eq!(desc.file.date, Some(String::from("2015-07-26T21:46:00")));
251        assert_eq!(desc.file.size, Some(6144u64));
252        assert_eq!(desc.file.range, None);
253        assert_eq!(desc.file.hashes[0].algo, "sha-1");
254        assert_eq!(desc.file.hashes[0].hash, "w0mcJylzCn+AfvuGdqkty2+KP48=");
255    }
256
257    #[test]
258    fn test_request() {
259        let elem: Element = r#"
260<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
261  <file>
262    <hash xmlns='urn:xmpp:hashes:2'
263          algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash>
264  </file>
265</description>
266"#.parse().unwrap();
267
268        let desc = jingle_ft::parse_jingle_ft(&elem).unwrap();
269        assert_eq!(desc.file.media_type, None);
270        assert_eq!(desc.file.name, None);
271        assert_eq!(desc.file.desc, None);
272        assert_eq!(desc.file.date, None);
273        assert_eq!(desc.file.size, None);
274        assert_eq!(desc.file.range, None);
275        assert_eq!(desc.file.hashes[0].algo, "sha-1");
276        assert_eq!(desc.file.hashes[0].hash, "w0mcJylzCn+AfvuGdqkty2+KP48=");
277    }
278}