1extern crate minidom;
2
3use hashes::{Hash, parse_hash};
4
5use minidom::Element;
6
7use error::Error;
8use ns;
9
10#[derive(Debug, Clone, PartialEq)]
11pub struct Range {
12 pub offset: u64,
13 pub length: Option<u64>,
14 pub hashes: Vec<Hash>,
15}
16
17#[derive(Debug, Clone)]
18pub struct File {
19 pub date: Option<String>,
20 pub media_type: Option<String>,
21 pub name: Option<String>,
22 pub size: Option<String>,
23 pub range: Option<Range>,
24 pub hashes: Vec<Hash>,
25}
26
27#[derive(Debug, Clone)]
28pub struct Description {
29 pub file: File,
30}
31
32#[derive(Debug, Clone)]
33pub struct Creator {
34 pub creator: String,
35}
36
37#[derive(Debug, Clone)]
38pub struct Checksum {
39 pub name: String,
40 pub creator: Creator,
41 pub file: File,
42}
43
44pub fn parse_jingle_ft(root: &Element) -> Result<Description, Error> {
45 if !root.is("description", ns::JINGLE_FT) {
46 return Err(Error::ParseError("This is not a JingleFT description element."));
47 }
48 if root.children().collect::<Vec<_>>().len() != 1 {
49 return Err(Error::ParseError("JingleFT description element must have exactly one child."));
50 }
51
52 let mut date = None;
53 let mut media_type = None;
54 let mut name = None;
55 let mut size = None;
56 let mut range = None;
57 let mut hashes = vec!();
58 for description_payload in root.children() {
59 if !description_payload.is("file", ns::JINGLE_FT) {
60 return Err(Error::ParseError("Unknown element in JingleFT description."));
61 }
62 for file_payload in description_payload.children() {
63 if file_payload.is("date", ns::JINGLE_FT) {
64 if date.is_some() {
65 return Err(Error::ParseError("File must not have more than one date."));
66 }
67 date = Some(file_payload.text());
68 } else if file_payload.is("media-type", ns::JINGLE_FT) {
69 if media_type.is_some() {
70 return Err(Error::ParseError("File must not have more than one media-type."));
71 }
72 media_type = Some(file_payload.text());
73 } else if file_payload.is("name", ns::JINGLE_FT) {
74 if name.is_some() {
75 return Err(Error::ParseError("File must not have more than one name."));
76 }
77 name = Some(file_payload.text());
78 } else if file_payload.is("size", ns::JINGLE_FT) {
79 if size.is_some() {
80 return Err(Error::ParseError("File must not have more than one size."));
81 }
82 size = Some(file_payload.text());
83 } else if file_payload.is("range", ns::JINGLE_FT) {
84 if range.is_some() {
85 return Err(Error::ParseError("File must not have more than one range."));
86 }
87 let offset = file_payload.attr("offset").unwrap_or("0").parse()?;
88 let length = match file_payload.attr("length") {
89 Some(length) => Some(length.parse()?),
90 None => None,
91 };
92 let mut range_hashes = vec!();
93 for hash_element in file_payload.children() {
94 if !hash_element.is("hash", ns::HASHES) {
95 return Err(Error::ParseError("Unknown element in JingleFT range."));
96 }
97 range_hashes.push(parse_hash(hash_element)?);
98 }
99 range = Some(Range {
100 offset: offset,
101 length: length,
102 hashes: range_hashes,
103 });
104 } else if file_payload.is("hash", ns::HASHES) {
105 hashes.push(parse_hash(file_payload)?);
106 } else {
107 return Err(Error::ParseError("Unknown element in JingleFT file."));
108 }
109 }
110 }
111
112 Ok(Description {
113 file: File {
114 date: date,
115 media_type: media_type,
116 name: name,
117 size: size,
118 range: range,
119 hashes: hashes,
120 },
121 })
122}
123
124#[cfg(test)]
125mod tests {
126 use minidom::Element;
127 use jingle_ft;
128
129 #[test]
130 fn test_description() {
131 let elem: Element = r#"
132<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
133 <file>
134 <media-type>text/plain</media-type>
135 <name>test.txt</name>
136 <date>2015-07-26T21:46:00</date>
137 <size>6144</size>
138 <hash xmlns='urn:xmpp:hashes:2'
139 algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash>
140 </file>
141</description>
142"#.parse().unwrap();
143
144 let desc = jingle_ft::parse_jingle_ft(&elem).unwrap();
145 assert_eq!(desc.file.media_type, Some(String::from("text/plain")));
146 assert_eq!(desc.file.name, Some(String::from("test.txt")));
147 assert_eq!(desc.file.date, Some(String::from("2015-07-26T21:46:00")));
148 assert_eq!(desc.file.size, Some(String::from("6144")));
149 assert_eq!(desc.file.range, None);
150 assert_eq!(desc.file.hashes[0].algo, "sha-1");
151 assert_eq!(desc.file.hashes[0].hash, "w0mcJylzCn+AfvuGdqkty2+KP48=");
152 }
153
154 #[test]
155 fn test_request() {
156 let elem: Element = r#"
157<description xmlns='urn:xmpp:jingle:apps:file-transfer:5'>
158 <file>
159 <hash xmlns='urn:xmpp:hashes:2'
160 algo='sha-1'>w0mcJylzCn+AfvuGdqkty2+KP48=</hash>
161 </file>
162</description>
163"#.parse().unwrap();
164
165 let desc = jingle_ft::parse_jingle_ft(&elem).unwrap();
166 assert_eq!(desc.file.media_type, None);
167 assert_eq!(desc.file.name, None);
168 assert_eq!(desc.file.date, None);
169 assert_eq!(desc.file.size, None);
170 assert_eq!(desc.file.range, None);
171 assert_eq!(desc.file.hashes[0].algo, "sha-1");
172 assert_eq!(desc.file.hashes[0].hash, "w0mcJylzCn+AfvuGdqkty2+KP48=");
173 }
174}