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