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 println!("{:#?}", jingle);
379
380 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();
381 let jingle = jingle::parse_jingle(&elem).unwrap();
382 assert_eq!(jingle.contents[0].senders, jingle::Senders::Both);
383
384 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();
385 let jingle = jingle::parse_jingle(&elem).unwrap();
386 assert_eq!(jingle.contents[0].disposition, "early-session");
387 }
388
389 #[test]
390 fn test_invalid_content() {
391 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content/></jingle>".parse().unwrap();
392 let error = jingle::parse_jingle(&elem).unwrap_err();
393 let message = match error {
394 Error::ParseError(string) => string,
395 _ => panic!(),
396 };
397 assert_eq!(message, "Content must have a 'creator' attribute.");
398
399 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator'/></jingle>".parse().unwrap();
400 let error = jingle::parse_jingle(&elem).unwrap_err();
401 let message = match error {
402 Error::ParseError(string) => string,
403 _ => panic!(),
404 };
405 assert_eq!(message, "Content must have a 'name' attribute.");
406
407 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='coucou' name='coucou'/></jingle>".parse().unwrap();
408 let error = jingle::parse_jingle(&elem).unwrap_err();
409 let message = match error {
410 Error::ParseError(string) => string,
411 _ => panic!(),
412 };
413 assert_eq!(message, "Unknown creator.");
414
415 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders='coucou'/></jingle>".parse().unwrap();
416 let error = jingle::parse_jingle(&elem).unwrap_err();
417 let message = match error {
418 Error::ParseError(string) => string,
419 _ => panic!(),
420 };
421 assert_eq!(message, "Unknown senders.");
422
423 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou' senders=''/></jingle>".parse().unwrap();
424 let error = jingle::parse_jingle(&elem).unwrap_err();
425 let message = match error {
426 Error::ParseError(string) => string,
427 _ => panic!(),
428 };
429 assert_eq!(message, "Unknown senders.");
430
431 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'/></jingle>".parse().unwrap();
432 let error = jingle::parse_jingle(&elem).unwrap_err();
433 let message = match error {
434 Error::ParseError(string) => string,
435 _ => panic!(),
436 };
437 assert_eq!(message, "Content must have one description.");
438
439 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><content creator='initiator' name='coucou'><description/></content></jingle>".parse().unwrap();
440 let error = jingle::parse_jingle(&elem).unwrap_err();
441 let message = match error {
442 Error::ParseError(string) => string,
443 _ => panic!(),
444 };
445 assert_eq!(message, "Content must have one transport.");
446 }
447
448 #[test]
449 fn test_reason() {
450 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/></reason></jingle>".parse().unwrap();
451 let jingle = jingle::parse_jingle(&elem).unwrap();
452 let reason = jingle.reason.unwrap();
453 assert_eq!(reason.reason, jingle::Reason::Success);
454 assert_eq!(reason.text, None);
455
456 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><success/><text>coucou</text></reason></jingle>".parse().unwrap();
457 let jingle = jingle::parse_jingle(&elem).unwrap();
458 let reason = jingle.reason.unwrap();
459 assert_eq!(reason.reason, jingle::Reason::Success);
460 assert_eq!(reason.text, Some(String::from("coucou")));
461 }
462
463 #[test]
464 fn test_invalid_reason() {
465 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason/></jingle>".parse().unwrap();
466 let error = jingle::parse_jingle(&elem).unwrap_err();
467 let message = match error {
468 Error::ParseError(string) => string,
469 _ => panic!(),
470 };
471 assert_eq!(message, "Reason doesn’t contain a valid reason.");
472
473 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><a/></reason></jingle>".parse().unwrap();
474 let error = jingle::parse_jingle(&elem).unwrap_err();
475 let message = match error {
476 Error::ParseError(string) => string,
477 _ => panic!(),
478 };
479 assert_eq!(message, "Unknown reason.");
480
481 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();
482 let error = jingle::parse_jingle(&elem).unwrap_err();
483 let message = match error {
484 Error::ParseError(string) => string,
485 _ => panic!(),
486 };
487 assert_eq!(message, "Reason contains a foreign element.");
488
489 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/></reason><reason/></jingle>".parse().unwrap();
490 let error = jingle::parse_jingle(&elem).unwrap_err();
491 let message = match error {
492 Error::ParseError(string) => string,
493 _ => panic!(),
494 };
495 assert_eq!(message, "Jingle must not have more than one reason.");
496
497 let elem: Element = "<jingle xmlns='urn:xmpp:jingle:1' action='session-initiate' sid='coucou'><reason><decline/><text/><text/></reason></jingle>".parse().unwrap();
498 let error = jingle::parse_jingle(&elem).unwrap_err();
499 let message = match error {
500 Error::ParseError(string) => string,
501 _ => panic!(),
502 };
503 assert_eq!(message, "Reason must not have more than one text.");
504 }
505}