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