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