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}