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