1extern crate minidom;
2
3use std::str::FromStr;
4
5use minidom::Element;
6
7use error::Error;
8use ns;
9
10#[derive(Debug, Clone, PartialEq)]
11pub enum Action {
12 ContentAccept,
13 ContentAdd,
14 ContentModify,
15 ContentReject,
16 ContentRemove,
17 DescriptionInfo,
18 SecurityInfo,
19 SessionAccept,
20 SessionInfo,
21 SessionInitiate,
22 SessionTerminate,
23 TransportAccept,
24 TransportInfo,
25 TransportReject,
26 TransportReplace,
27}
28
29impl FromStr for Action {
30 type Err = Error;
31
32 fn from_str(s: &str) -> Result<Action, Error> {
33 Ok(match s {
34 "content-accept" => Action::ContentAccept,
35 "content-add" => Action::ContentAdd,
36 "content-modify" => Action::ContentModify,
37 "content-reject" => Action::ContentReject,
38 "content-remove" => Action::ContentRemove,
39 "description-info" => Action::DescriptionInfo,
40 "security-info" => Action::SecurityInfo,
41 "session-accept" => Action::SessionAccept,
42 "session-info" => Action::SessionInfo,
43 "session-initiate" => Action::SessionInitiate,
44 "session-terminate" => Action::SessionTerminate,
45 "transport-accept" => Action::TransportAccept,
46 "transport-info" => Action::TransportInfo,
47 "transport-reject" => Action::TransportReject,
48 "transport-replace" => Action::TransportReplace,
49
50 _ => return Err(Error::ParseError("Unknown action.")),
51 })
52 }
53}
54
55// TODO: use a real JID type.
56type Jid = String;
57
58#[derive(Debug, Clone, PartialEq)]
59pub enum Creator {
60 Initiator,
61 Responder,
62}
63
64impl FromStr for Creator {
65 type Err = Error;
66
67 fn from_str(s: &str) -> Result<Creator, Error> {
68 Ok(match s {
69 "initiator" => Creator::Initiator,
70 "responder" => Creator::Responder,
71
72 _ => return Err(Error::ParseError("Unknown creator.")),
73 })
74 }
75}
76
77#[derive(Debug, Clone, PartialEq)]
78pub enum Senders {
79 Both,
80 Initiator,
81 None_,
82 Responder,
83}
84
85impl FromStr for Senders {
86 type Err = Error;
87
88 fn from_str(s: &str) -> Result<Senders, Error> {
89 Ok(match s {
90 "both" => Senders::Both,
91 "initiator" => Senders::Initiator,
92 "none" => Senders::None_,
93 "responder" => Senders::Responder,
94
95 _ => return Err(Error::ParseError("Unknown senders.")),
96 })
97 }
98}
99
100#[derive(Debug, Clone)]
101pub struct Content {
102 pub creator: Creator,
103 pub disposition: String,
104 pub name: String,
105 pub senders: Senders,
106 pub description: (String, Element),
107 pub transport: (String, Element),
108 pub security: Option<(String, Element)>,
109}
110
111#[derive(Debug, Clone, PartialEq)]
112pub enum Reason {
113 AlternativeSession, //(String),
114 Busy,
115 Cancel,
116 ConnectivityError,
117 Decline,
118 Expired,
119 FailedApplication,
120 FailedTransport,
121 GeneralError,
122 Gone,
123 IncompatibleParameters,
124 MediaError,
125 SecurityError,
126 Success,
127 Timeout,
128 UnsupportedApplications,
129 UnsupportedTransports,
130}
131
132impl FromStr for Reason {
133 type Err = Error;
134
135 fn from_str(s: &str) -> Result<Reason, Error> {
136 Ok(match s {
137 "alternative-session" => Reason::AlternativeSession,
138 "busy" => Reason::Busy,
139 "cancel" => Reason::Cancel,
140 "connectivity-error" => Reason::ConnectivityError,
141 "decline" => Reason::Decline,
142 "expired" => Reason::Expired,
143 "failed-application" => Reason::FailedApplication,
144 "failed-transport" => Reason::FailedTransport,
145 "general-error" => Reason::GeneralError,
146 "gone" => Reason::Gone,
147 "incompatible-parameters" => Reason::IncompatibleParameters,
148 "media-error" => Reason::MediaError,
149 "security-error" => Reason::SecurityError,
150 "success" => Reason::Success,
151 "timeout" => Reason::Timeout,
152 "unsupported-applications" => Reason::UnsupportedApplications,
153 "unsupported-transports" => Reason::UnsupportedTransports,
154
155 _ => return Err(Error::ParseError("Unknown reason.")),
156 })
157 }
158}
159
160#[derive(Debug, Clone)]
161pub struct ReasonElement {
162 pub reason: Reason,
163 pub text: Option<String>,
164}
165
166#[derive(Debug, Clone)]
167pub struct Jingle {
168 pub action: Action,
169 pub initiator: Option<Jid>,
170 pub responder: Option<Jid>,
171 pub sid: String,
172 pub contents: Vec<Content>,
173 pub reason: Option<ReasonElement>,
174 //pub other: Vec<Element>,
175}
176
177pub fn parse_jingle(root: &Element) -> Result<Jingle, Error> {
178 if !root.is("jingle", ns::JINGLE) {
179 return Err(Error::ParseError("This is not a Jingle element."));
180 }
181
182 let mut contents: Vec<Content> = vec!();
183
184 let action = root.attr("action")
185 .ok_or(Error::ParseError("Jingle must have an 'action' attribute."))?
186 .parse()?;
187 let initiator = root.attr("initiator")
188 .and_then(|initiator| initiator.parse().ok());
189 let responder = root.attr("responder")
190 .and_then(|responder| responder.parse().ok());
191 let sid = root.attr("sid")
192 .ok_or(Error::ParseError("Jingle must have a 'sid' attribute."))?;
193 let mut reason_element = None;
194
195 for child in root.children() {
196 if child.is("content", ns::JINGLE) {
197 let creator = child.attr("creator")
198 .ok_or(Error::ParseError("Content must have a 'creator' attribute."))?
199 .parse()?;
200 let disposition = child.attr("disposition")
201 .unwrap_or("session");
202 let name = child.attr("name")
203 .ok_or(Error::ParseError("Content must have a 'name' attribute."))?;
204 let senders = child.attr("senders")
205 .unwrap_or("both")
206 .parse()?;
207 let mut description = None;
208 let mut transport = None;
209 let mut security = None;
210 for stuff in child.children() {
211 if stuff.name() == "description" {
212 if description.is_some() {
213 return Err(Error::ParseError("Content must not have more than one description."));
214 }
215 let namespace = stuff.ns()
216 .and_then(|ns| ns.parse().ok())
217 // TODO: is this even reachable?
218 .ok_or(Error::ParseError("Invalid namespace on description element."))?;
219 description = Some((
220 namespace,
221 stuff.clone(),
222 ));
223 } else if stuff.name() == "transport" {
224 if transport.is_some() {
225 return Err(Error::ParseError("Content must not have more than one transport."));
226 }
227 let namespace = stuff.ns()
228 .and_then(|ns| ns.parse().ok())
229 // TODO: is this even reachable?
230 .ok_or(Error::ParseError("Invalid namespace on transport element."))?;
231 transport = Some((
232 namespace,
233 stuff.clone(),
234 ));
235 } else if stuff.name() == "security" {
236 if security.is_some() {
237 return Err(Error::ParseError("Content must not have more than one security."));
238 }
239 let namespace = stuff.ns()
240 .and_then(|ns| ns.parse().ok())
241 // TODO: is this even reachable?
242 .ok_or(Error::ParseError("Invalid namespace on security element."))?;
243 security = Some((
244 namespace,
245 stuff.clone(),
246 ));
247 }
248 }
249 if description.is_none() {
250 return Err(Error::ParseError("Content must have one description."));
251 }
252 if transport.is_none() {
253 return Err(Error::ParseError("Content must have one transport."));
254 }
255 let description = description.unwrap().to_owned();
256 let transport = transport.unwrap().to_owned();
257 contents.push(Content {
258 creator: creator,
259 disposition: disposition.to_owned(),
260 name: name.to_owned(),
261 senders: senders,
262 description: description,
263 transport: transport,
264 security: security,
265 });
266 } else if child.is("reason", ns::JINGLE) {
267 if reason_element.is_some() {
268 return Err(Error::ParseError("Jingle must not have more than one reason."));
269 }
270 let mut reason = None;
271 let mut text = None;
272 for stuff in child.children() {
273 if stuff.ns() != Some(ns::JINGLE) {
274 return Err(Error::ParseError("Reason contains a foreign element."));
275 }
276 let name = stuff.name();
277 if name == "text" {
278 if text.is_some() {
279 return Err(Error::ParseError("Reason must not have more than one text."));
280 }
281 text = Some(stuff.text());
282 } else {
283 reason = Some(name.parse()?);
284 }
285 }
286 if reason.is_none() {
287 return Err(Error::ParseError("Reason doesn’t contain a valid reason."));
288 }
289 reason_element = Some(ReasonElement {
290 reason: reason.unwrap(),
291 text: text,
292 });
293 } else {
294 return Err(Error::ParseError("Unknown element in jingle."));
295 }
296 }
297
298 Ok(Jingle {
299 action: action,
300 initiator: initiator,
301 responder: responder,
302 sid: sid.to_owned(),
303 contents: contents,
304 reason: reason_element,
305 })
306}
307
308#[cfg(test)]
309mod tests {
310 use minidom::Element;
311 use error::Error;
312 use jingle;
313
314 #[test]
315 fn test_simple() {
316 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'/>".parse().unwrap();
317 let jingle = jingle::parse_jingle(&elem).unwrap();
318 assert_eq!(jingle.action, jingle::Action::SessionInitiate);
319 assert_eq!(jingle.sid, "coucou");
320 }
321
322 #[test]
323 fn test_invalid_jingle() {
324 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1'/>".parse().unwrap();
325 let error = jingle::parse_jingle(&elem).unwrap_err();
326 let message = match error {
327 Error::ParseError(string) => string,
328 _ => panic!(),
329 };
330 assert_eq!(message, "Jingle must have an 'action' attribute.");
331
332 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-info'/>".parse().unwrap();
333 let error = jingle::parse_jingle(&elem).unwrap_err();
334 let message = match error {
335 Error::ParseError(string) => string,
336 _ => panic!(),
337 };
338 assert_eq!(message, "Jingle must have a 'sid' attribute.");
339
340 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='coucou' sid='coucou'/>".parse().unwrap();
341 let error = jingle::parse_jingle(&elem).unwrap_err();
342 let message = match error {
343 Error::ParseError(string) => string,
344 _ => panic!(),
345 };
346 assert_eq!(message, "Unknown action.");
347
348 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-accept' sid='coucou'><coucou/></jingle>".parse().unwrap();
349 let error = jingle::parse_jingle(&elem).unwrap_err();
350 let message = match error {
351 Error::ParseError(string) => string,
352 _ => panic!(),
353 };
354 assert_eq!(message, "Unknown element in jingle.");
355 }
356
357 #[test]
358 fn test_content() {
359 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'><description/><transport/></content></jingle>".parse().unwrap();
360 let jingle = jingle::parse_jingle(&elem).unwrap();
361 assert_eq!(jingle.contents[0].creator, jingle::Creator::Initiator);
362 assert_eq!(jingle.contents[0].name, "coucou");
363 assert_eq!(jingle.contents[0].senders, jingle::Senders::Both);
364 assert_eq!(jingle.contents[0].disposition, "session");
365
366 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='both'><description/><transport/></content></jingle>".parse().unwrap();
367 let jingle = jingle::parse_jingle(&elem).unwrap();
368 assert_eq!(jingle.contents[0].senders, jingle::Senders::Both);
369
370 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' disposition='early-session'><description/><transport/></content></jingle>".parse().unwrap();
371 let jingle = jingle::parse_jingle(&elem).unwrap();
372 assert_eq!(jingle.contents[0].disposition, "early-session");
373 }
374
375 #[test]
376 fn test_invalid_content() {
377 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
378 let error = jingle::parse_jingle(&elem).unwrap_err();
379 let message = match error {
380 Error::ParseError(string) => string,
381 _ => panic!(),
382 };
383 assert_eq!(message, "Content must have a 'creator' attribute.");
384
385 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
386 let error = jingle::parse_jingle(&elem).unwrap_err();
387 let message = match error {
388 Error::ParseError(string) => string,
389 _ => panic!(),
390 };
391 assert_eq!(message, "Content must have a 'name' attribute.");
392
393 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
394 let error = jingle::parse_jingle(&elem).unwrap_err();
395 let message = match error {
396 Error::ParseError(string) => string,
397 _ => panic!(),
398 };
399 assert_eq!(message, "Unknown creator.");
400
401 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
402 let error = jingle::parse_jingle(&elem).unwrap_err();
403 let message = match error {
404 Error::ParseError(string) => string,
405 _ => panic!(),
406 };
407 assert_eq!(message, "Unknown senders.");
408
409 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
410 let error = jingle::parse_jingle(&elem).unwrap_err();
411 let message = match error {
412 Error::ParseError(string) => string,
413 _ => panic!(),
414 };
415 assert_eq!(message, "Unknown senders.");
416
417 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'/></jingle>".parse().unwrap();
418 let error = jingle::parse_jingle(&elem).unwrap_err();
419 let message = match error {
420 Error::ParseError(string) => string,
421 _ => panic!(),
422 };
423 assert_eq!(message, "Content must have one description.");
424
425 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'><description/></content></jingle>".parse().unwrap();
426 let error = jingle::parse_jingle(&elem).unwrap_err();
427 let message = match error {
428 Error::ParseError(string) => string,
429 _ => panic!(),
430 };
431 assert_eq!(message, "Content must have one transport.");
432 }
433
434 #[test]
435 fn test_reason() {
436 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/></reason></jingle>".parse().unwrap();
437 let jingle = jingle::parse_jingle(&elem).unwrap();
438 let reason = jingle.reason.unwrap();
439 assert_eq!(reason.reason, jingle::Reason::Success);
440 assert_eq!(reason.text, None);
441
442 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/><text>coucou</text></reason></jingle>".parse().unwrap();
443 let jingle = jingle::parse_jingle(&elem).unwrap();
444 let reason = jingle.reason.unwrap();
445 assert_eq!(reason.reason, jingle::Reason::Success);
446 assert_eq!(reason.text, Some(String::from("coucou")));
447 }
448
449 #[test]
450 fn test_invalid_reason() {
451 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
452 let error = jingle::parse_jingle(&elem).unwrap_err();
453 let message = match error {
454 Error::ParseError(string) => string,
455 _ => panic!(),
456 };
457 assert_eq!(message, "Reason doesn’t contain a valid reason.");
458
459 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
460 let error = jingle::parse_jingle(&elem).unwrap_err();
461 let message = match error {
462 Error::ParseError(string) => string,
463 _ => panic!(),
464 };
465 assert_eq!(message, "Unknown reason.");
466
467 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a xmlns='http://www.w3.org/1999/xhtml'/></reason></jingle>".parse().unwrap();
468 let error = jingle::parse_jingle(&elem).unwrap_err();
469 let message = match error {
470 Error::ParseError(string) => string,
471 _ => panic!(),
472 };
473 assert_eq!(message, "Reason contains a foreign element.");
474
475 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
476 let error = jingle::parse_jingle(&elem).unwrap_err();
477 let message = match error {
478 Error::ParseError(string) => string,
479 _ => panic!(),
480 };
481 assert_eq!(message, "Jingle must not have more than one reason.");
482
483 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><text/><text/></reason></jingle>".parse().unwrap();
484 let error = jingle::parse_jingle(&elem).unwrap_err();
485 let message = match error {
486 Error::ParseError(string) => string,
487 _ => panic!(),
488 };
489 assert_eq!(message, "Reason must not have more than one text.");
490 }
491}