1// Copyright (c) 2017 Maxime “pep” Buquet <pep+code@bouah.net>
2// Copyright (c) 2017 Emmanuel Gil Peyrot <linkmauve@linkmauve.fr>
3//
4// This Source Code Form is subject to the terms of the Mozilla Public
5// License, v. 2.0. If a copy of the MPL was not distributed with this
6// file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
8use try_from::{TryFrom, TryInto};
9use std::str::FromStr;
10
11use minidom::{Element, IntoElements, IntoAttributeValue, ElementEmitter};
12
13use jid::Jid;
14
15use error::Error;
16
17use ns;
18
19#[derive(Debug, Clone, PartialEq)]
20pub enum Status {
21 /// Status: 100
22 NonAnonymousRoom,
23
24 /// Status: 101
25 AffiliationChange,
26
27 /// Status: 102
28 ConfigShowsUnavailableMembers,
29
30 /// Status: 103
31 ConfigHidesUnavailableMembers,
32
33 /// Status: 104
34 ConfigNonPrivacyRelated,
35
36 /// Status: 110
37 SelfPresence,
38
39 /// Status: 170
40 ConfigRoomLoggingEnabled,
41
42 /// Status: 171
43 ConfigRoomLoggingDisabled,
44
45 /// Status: 172
46 ConfigRoomNonAnonymous,
47
48 /// Status: 173
49 ConfigRoomSemiAnonymous,
50
51 /// Status: 201
52 RoomHasBeenCreated,
53
54 /// Status: 210
55 AssignedNick,
56
57 /// Status: 301
58 Banned,
59
60 /// Status: 303
61 NewNick,
62
63 /// Status: 307
64 Kicked,
65
66 /// Status: 321
67 RemovalFromRoom,
68
69 /// Status: 322
70 ConfigMembersOnly,
71
72 /// Status: 332
73 ServiceShutdown,
74}
75
76impl TryFrom<Element> for Status {
77 type Err = Error;
78
79 fn try_from(elem: Element) -> Result<Status, Error> {
80 if !elem.is("status", ns::MUC_USER) {
81 return Err(Error::ParseError("This is not a status element."));
82 }
83 for _ in elem.children() {
84 return Err(Error::ParseError("Unknown child in status element."));
85 }
86 for (attr, _) in elem.attrs() {
87 if attr != "code" {
88 return Err(Error::ParseError("Unknown attribute in status element."));
89 }
90 }
91 let code = get_attr!(elem, "code", required);
92
93 Ok(match code {
94 100 => Status::NonAnonymousRoom,
95 101 => Status::AffiliationChange,
96 102 => Status::ConfigShowsUnavailableMembers,
97 103 => Status::ConfigHidesUnavailableMembers,
98 104 => Status::ConfigNonPrivacyRelated,
99 110 => Status::SelfPresence,
100 170 => Status::ConfigRoomLoggingEnabled,
101 171 => Status::ConfigRoomLoggingDisabled,
102 172 => Status::ConfigRoomNonAnonymous,
103 173 => Status::ConfigRoomSemiAnonymous,
104 201 => Status::RoomHasBeenCreated,
105 210 => Status::AssignedNick,
106 301 => Status::Banned,
107 303 => Status::NewNick,
108 307 => Status::Kicked,
109 321 => Status::RemovalFromRoom,
110 322 => Status::ConfigMembersOnly,
111 332 => Status::ServiceShutdown,
112 _ => return Err(Error::ParseError("Invalid status code.")),
113 })
114 }
115}
116
117impl From<Status> for Element {
118 fn from(status: Status) -> Element {
119 Element::builder("status")
120 .ns(ns::MUC_USER)
121 .attr("code", match status {
122 Status::NonAnonymousRoom => 100,
123 Status::AffiliationChange => 101,
124 Status::ConfigShowsUnavailableMembers => 102,
125 Status::ConfigHidesUnavailableMembers => 103,
126 Status::ConfigNonPrivacyRelated => 104,
127 Status::SelfPresence => 110,
128 Status::ConfigRoomLoggingEnabled => 170,
129 Status::ConfigRoomLoggingDisabled => 171,
130 Status::ConfigRoomNonAnonymous => 172,
131 Status::ConfigRoomSemiAnonymous => 173,
132 Status::RoomHasBeenCreated => 201,
133 Status::AssignedNick => 210,
134 Status::Banned => 301,
135 Status::NewNick => 303,
136 Status::Kicked => 307,
137 Status::RemovalFromRoom => 321,
138 Status::ConfigMembersOnly => 322,
139 Status::ServiceShutdown => 332,
140 })
141 .build()
142 }
143}
144
145impl IntoElements for Status {
146 fn into_elements(self, emitter: &mut ElementEmitter) {
147 emitter.append_child(self.into());
148 }
149}
150
151/// Optional <actor/> element used in <item/> elements inside presence stanzas of type
152/// "unavailable" that are sent to users who are kick or banned, as well as within IQs for tracking
153/// purposes. -- CHANGELOG 0.17 (2002-10-23)
154/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real
155/// JID or to a roomnick. -- CHANGELOG 1.25 (2012-02-08)
156#[derive(Debug, Clone, PartialEq)]
157pub enum Actor {
158 Jid(Jid),
159 Nick(String),
160}
161
162impl TryFrom<Element> for Actor {
163 type Err = Error;
164
165 fn try_from(elem: Element) -> Result<Actor, Error> {
166 if !elem.is("actor", ns::MUC_USER) {
167 return Err(Error::ParseError("This is not a actor element."));
168 }
169 for _ in elem.children() {
170 return Err(Error::ParseError("Unknown child in actor element."));
171 }
172 for (attr, _) in elem.attrs() {
173 if attr != "jid" && attr != "nick" {
174 return Err(Error::ParseError("Unknown attribute in actor element."));
175 }
176 }
177 let jid: Option<Jid> = get_attr!(elem, "jid", optional);
178 let nick = get_attr!(elem, "nick", optional);
179
180 match (jid, nick) {
181 (Some(_), Some(_))
182 | (None, None) =>
183 return Err(Error::ParseError("Either 'jid' or 'nick' attribute is required.")),
184 (Some(jid), _) => Ok(Actor::Jid(jid)),
185 (_, Some(nick)) => Ok(Actor::Nick(nick)),
186 }
187 }
188}
189
190impl From<Actor> for Element {
191 fn from(actor: Actor) -> Element {
192 let elem = Element::builder("actor").ns(ns::MUC_USER);
193
194 (match actor {
195 Actor::Jid(jid) => elem.attr("jid", String::from(jid)),
196 Actor::Nick(nick) => elem.attr("nick", nick),
197 }).build()
198 }
199}
200
201impl IntoElements for Actor {
202 fn into_elements(self, emitter: &mut ElementEmitter) {
203 emitter.append_child(self.into());
204 }
205}
206
207#[derive(Debug, Clone, PartialEq)]
208pub struct Continue {
209 thread: Option<String>,
210}
211
212impl TryFrom<Element> for Continue {
213 type Err = Error;
214
215 fn try_from(elem: Element) -> Result<Continue, Error> {
216 if !elem.is("continue", ns::MUC_USER) {
217 return Err(Error::ParseError("This is not a continue element."));
218 }
219 for _ in elem.children() {
220 return Err(Error::ParseError("Unknown child in continue element."));
221 }
222 for (attr, _) in elem.attrs() {
223 if attr != "thread" {
224 return Err(Error::ParseError("Unknown attribute in continue element."));
225 }
226 }
227 Ok(Continue { thread: get_attr!(elem, "thread", optional) })
228 }
229}
230
231impl From<Continue> for Element {
232 fn from(cont: Continue) -> Element {
233 Element::builder("continue")
234 .ns(ns::MUC_USER)
235 .attr("thread", cont.thread)
236 .build()
237 }
238}
239
240impl IntoElements for Continue {
241 fn into_elements(self, emitter: &mut ElementEmitter) {
242 emitter.append_child(self.into());
243 }
244}
245
246#[derive(Debug, Clone, PartialEq)]
247pub struct Reason(String);
248
249impl TryFrom<Element> for Reason {
250 type Err = Error;
251
252 fn try_from(elem: Element) -> Result<Reason, Error> {
253 if !elem.is("reason", ns::MUC_USER) {
254 return Err(Error::ParseError("This is not a reason element."));
255 }
256 for _ in elem.children() {
257 return Err(Error::ParseError("Unknown child in reason element."));
258 }
259 for _ in elem.attrs() {
260 return Err(Error::ParseError("Unknown attribute in reason element."));
261 }
262 Ok(Reason(elem.text()))
263 }
264}
265
266impl From<Reason> for Element {
267 fn from(reason: Reason) -> Element {
268 Element::builder("reason")
269 .ns(ns::MUC_USER)
270 .append(reason.0)
271 .build()
272 }
273}
274
275impl IntoElements for Reason {
276 fn into_elements(self, emitter: &mut ElementEmitter) {
277 emitter.append_child(self.into());
278 }
279}
280
281generate_attribute!(Affiliation, "affiliation", {
282 Owner => "owner",
283 Admin => "admin",
284 Member => "member",
285 Outcast => "outcast",
286 None => "none",
287}, Default = None);
288
289generate_attribute!(Role, "role", {
290 Moderator => "moderator",
291 Participant => "participant",
292 Visitor => "visitor",
293 None => "none",
294}, Default = None);
295
296#[derive(Debug, Clone)]
297pub struct Item {
298 pub affiliation: Affiliation,
299 pub jid: Option<Jid>,
300 pub nick: Option<String>,
301 pub role: Role,
302 pub actor: Option<Actor>,
303 pub continue_: Option<Continue>,
304 pub reason: Option<Reason>,
305}
306
307impl TryFrom<Element> for Item {
308 type Err = Error;
309
310 fn try_from(elem: Element) -> Result<Item, Error> {
311 if !elem.is("item", ns::MUC_USER) {
312 return Err(Error::ParseError("This is not a item element."));
313 }
314 let mut actor: Option<Actor> = None;
315 let mut continue_: Option<Continue> = None;
316 let mut reason: Option<Reason> = None;
317 for child in elem.children() {
318 if child.is("actor", ns::MUC_USER) {
319 actor = Some(child.clone().try_into()?);
320 } else if child.is("continue", ns::MUC_USER) {
321 continue_ = Some(child.clone().try_into()?);
322 } else if child.is("reason", ns::MUC_USER) {
323 reason = Some(child.clone().try_into()?);
324 } else {
325 return Err(Error::ParseError("Unknown child in item element."));
326 }
327 }
328 for (attr, _) in elem.attrs() {
329 if attr != "affiliation" && attr != "jid" &&
330 attr != "nick" && attr != "role" {
331 return Err(Error::ParseError("Unknown attribute in item element."));
332 }
333 }
334
335 let affiliation: Affiliation = get_attr!(elem, "affiliation", required);
336 let jid: Option<Jid> = get_attr!(elem, "jid", optional);
337 let nick: Option<String> = get_attr!(elem, "nick", optional);
338 let role: Role = get_attr!(elem, "role", required);
339
340 Ok(Item{
341 affiliation: affiliation,
342 jid: jid,
343 nick: nick,
344 role: role,
345 actor: actor,
346 continue_: continue_,
347 reason: reason,
348 })
349 }
350}
351
352impl From<Item> for Element {
353 fn from(item: Item) -> Element {
354 Element::builder("item")
355 .ns(ns::MUC_USER)
356 .attr("affiliation", item.affiliation)
357 .attr("jid", match item.jid {
358 Some(jid) => Some(String::from(jid)),
359 None => None,
360 })
361 .attr("nick", item.nick)
362 .attr("role", item.role)
363 .append(item.actor)
364 .append(item.continue_)
365 .append(item.reason)
366 .build()
367 }
368}
369
370#[derive(Debug, Clone)]
371pub struct MucUser {
372 pub status: Vec<Status>,
373 pub items: Vec<Item>,
374}
375
376impl TryFrom<Element> for MucUser {
377 type Err = Error;
378
379 fn try_from(elem: Element) -> Result<MucUser, Error> {
380 if !elem.is("x", ns::MUC_USER) {
381 return Err(Error::ParseError("This is not an x element."));
382 }
383 let mut status = vec!();
384 let mut items = vec!();
385 for child in elem.children() {
386 if child.is("status", ns::MUC_USER) {
387 status.push(Status::try_from(child.clone())?);
388 } else if child.is("item", ns::MUC_USER) {
389 items.push(Item::try_from(child.clone())?);
390 } else {
391 return Err(Error::ParseError("Unknown child in x element."));
392 }
393 }
394 for _ in elem.attrs() {
395 return Err(Error::ParseError("Unknown attribute in x element."));
396 }
397 Ok(MucUser {
398 status,
399 items,
400 })
401 }
402}
403
404impl From<MucUser> for Element {
405 fn from(muc_user: MucUser) -> Element {
406 Element::builder("x")
407 .ns(ns::MUC_USER)
408 .append(muc_user.status)
409 .build()
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use std::error::Error as StdError;
417
418 #[test]
419 fn test_simple() {
420 let elem: Element = "
421 <x xmlns='http://jabber.org/protocol/muc#user'/>
422 ".parse().unwrap();
423 MucUser::try_from(elem).unwrap();
424 }
425
426 #[test]
427 fn test_invalid_child() {
428 let elem: Element = "
429 <x xmlns='http://jabber.org/protocol/muc#user'>
430 <coucou/>
431 </x>
432 ".parse().unwrap();
433 let error = MucUser::try_from(elem).unwrap_err();
434 let message = match error {
435 Error::ParseError(string) => string,
436 _ => panic!(),
437 };
438 assert_eq!(message, "Unknown child in x element.");
439 }
440
441 #[test]
442 fn test_serialise() {
443 let elem: Element = "
444 <x xmlns='http://jabber.org/protocol/muc#user'/>
445 ".parse().unwrap();
446 let muc = MucUser { status: vec!(), items: vec!() };
447 let elem2 = muc.into();
448 assert_eq!(elem, elem2);
449 }
450
451 #[test]
452 fn test_invalid_attribute() {
453 let elem: Element = "
454 <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
455 ".parse().unwrap();
456 let error = MucUser::try_from(elem).unwrap_err();
457 let message = match error {
458 Error::ParseError(string) => string,
459 _ => panic!(),
460 };
461 assert_eq!(message, "Unknown attribute in x element.");
462 }
463
464 #[test]
465 fn test_status_simple() {
466 let elem: Element = "
467 <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
468 ".parse().unwrap();
469 Status::try_from(elem).unwrap();
470 }
471
472 #[test]
473 fn test_status_invalid() {
474 let elem: Element = "
475 <status xmlns='http://jabber.org/protocol/muc#user'/>
476 ".parse().unwrap();
477 let error = Status::try_from(elem).unwrap_err();
478 let message = match error {
479 Error::ParseError(string) => string,
480 _ => panic!(),
481 };
482 assert_eq!(message, "Required attribute 'code' missing.");
483 }
484
485 #[test]
486 fn test_status_invalid_child() {
487 let elem: Element = "
488 <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
489 <foo/>
490 </status>
491 ".parse().unwrap();
492 let error = Status::try_from(elem).unwrap_err();
493 let message = match error {
494 Error::ParseError(string) => string,
495 _ => panic!(),
496 };
497 assert_eq!(message, "Unknown child in status element.");
498 }
499
500 #[test]
501 fn test_status_simple_code() {
502 let elem: Element = "
503 <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
504 ".parse().unwrap();
505 let status = Status::try_from(elem).unwrap();
506 assert_eq!(status, Status::Kicked);
507 }
508
509 #[test]
510 fn test_status_invalid_code() {
511 let elem: Element = "
512 <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
513 ".parse().unwrap();
514 let error = Status::try_from(elem).unwrap_err();
515 let message = match error {
516 Error::ParseError(string) => string,
517 _ => panic!(),
518 };
519 assert_eq!(message, "Invalid status code.");
520 }
521
522 #[test]
523 fn test_status_invalid_code2() {
524 let elem: Element = "
525 <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
526 ".parse().unwrap();
527 let error = Status::try_from(elem).unwrap_err();
528 let error = match error {
529 Error::ParseIntError(error) => error,
530 _ => panic!(),
531 };
532 assert_eq!(error.description(), "invalid digit found in string");
533 }
534
535 #[test]
536 fn test_actor_required_attributes() {
537 let elem: Element = "
538 <actor xmlns='http://jabber.org/protocol/muc#user'/>
539 ".parse().unwrap();
540 let error = Actor::try_from(elem).unwrap_err();
541 let message = match error {
542 Error::ParseError(string) => string,
543 _ => panic!(),
544 };
545 assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
546 }
547
548 #[test]
549 fn test_actor_required_attributes2() {
550 let elem: Element = "
551 <actor xmlns='http://jabber.org/protocol/muc#user'
552 jid='foo@bar/baz'
553 nick='baz'/>
554 ".parse().unwrap();
555 let error = Actor::try_from(elem).unwrap_err();
556 let message = match error {
557 Error::ParseError(string) => string,
558 _ => panic!(),
559 };
560 assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
561 }
562
563 #[test]
564 fn test_actor_jid() {
565 let elem: Element = "
566 <actor xmlns='http://jabber.org/protocol/muc#user'
567 jid='foo@bar/baz'/>
568 ".parse().unwrap();
569 let actor = Actor::try_from(elem).unwrap();
570 let jid = match actor {
571 Actor::Jid(jid) => jid,
572 _ => panic!(),
573 };
574 assert_eq!(jid, "foo@bar/baz".parse::<Jid>().unwrap());
575 }
576
577 #[test]
578 fn test_actor_nick() {
579 let elem: Element = "
580 <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
581 ".parse().unwrap();
582 let actor = Actor::try_from(elem).unwrap();
583 let nick = match actor {
584 Actor::Nick(nick) => nick,
585 _ => panic!(),
586 };
587 assert_eq!(nick, "baz".to_owned());
588 }
589
590 #[test]
591 fn test_continue_simple() {
592 let elem: Element = "
593 <continue xmlns='http://jabber.org/protocol/muc#user'/>
594 ".parse().unwrap();
595 Continue::try_from(elem).unwrap();
596 }
597
598 #[test]
599 fn test_continue_thread_attribute() {
600 let elem: Element = "
601 <continue xmlns='http://jabber.org/protocol/muc#user'
602 thread='foo'/>
603 ".parse().unwrap();
604 let continue_ = Continue::try_from(elem).unwrap();
605 assert_eq!(continue_, Continue { thread: Some("foo".to_owned()) });
606 }
607
608 #[test]
609 fn test_continue_invalid() {
610 let elem: Element = "
611 <continue xmlns='http://jabber.org/protocol/muc#user'>
612 <foobar/>
613 </continue>
614 ".parse().unwrap();
615 let continue_ = Continue::try_from(elem).unwrap_err();
616 let message = match continue_ {
617 Error::ParseError(string) => string,
618 _ => panic!(),
619 };
620 assert_eq!(message, "Unknown child in continue element.".to_owned());
621 }
622
623 #[test]
624 fn test_reason_simple() {
625 let elem: Element = "
626 <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
627 .parse().unwrap();
628 let reason = Reason::try_from(elem).unwrap();
629 assert_eq!(reason.0, "Reason".to_owned());
630 }
631
632 #[test]
633 fn test_reason_invalid_attribute() {
634 let elem: Element = "
635 <reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
636 ".parse().unwrap();
637 let error = Reason::try_from(elem).unwrap_err();
638 let message = match error {
639 Error::ParseError(string) => string,
640 _ => panic!(),
641 };
642 assert_eq!(message, "Unknown attribute in reason element.".to_owned());
643 }
644
645 #[test]
646 fn test_reason_invalid() {
647 let elem: Element = "
648 <reason xmlns='http://jabber.org/protocol/muc#user'>
649 <foobar/>
650 </reason>
651 ".parse().unwrap();
652 let error = Reason::try_from(elem).unwrap_err();
653 let message = match error {
654 Error::ParseError(string) => string,
655 _ => panic!(),
656 };
657 assert_eq!(message, "Unknown child in reason element.".to_owned());
658 }
659
660 #[test]
661 fn test_item_invalid_attr(){
662 let elem: Element = "
663 <item xmlns='http://jabber.org/protocol/muc#user'
664 foo='bar'/>
665 ".parse().unwrap();
666 let error = Item::try_from(elem).unwrap_err();
667 let message = match error {
668 Error::ParseError(string) => string,
669 _ => panic!(),
670 };
671 assert_eq!(message, "Unknown attribute in item element.".to_owned());
672 }
673
674 #[test]
675 fn test_item_affiliation_role_attr(){
676 let elem: Element = "
677 <item xmlns='http://jabber.org/protocol/muc#user'
678 affiliation='member'
679 role='moderator'/>
680 ".parse().unwrap();
681 Item::try_from(elem).unwrap();
682 }
683
684 #[test]
685 fn test_item_affiliation_role_invalid_attr(){
686 let elem: Element = "
687 <item xmlns='http://jabber.org/protocol/muc#user'
688 affiliation='member'/>
689 ".parse().unwrap();
690 let error = Item::try_from(elem).unwrap_err();
691 let message = match error {
692 Error::ParseError(string) => string,
693 _ => panic!(),
694 };
695 assert_eq!(message, "Required attribute 'role' missing.".to_owned());
696 }
697
698 #[test]
699 fn test_item_nick_attr(){
700 let elem: Element = "
701 <item xmlns='http://jabber.org/protocol/muc#user'
702 affiliation='member'
703 role='moderator'
704 nick='foobar'/>
705 ".parse().unwrap();
706 let item = Item::try_from(elem).unwrap();
707 match item {
708 Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
709 }
710 }
711
712 #[test]
713 fn test_item_affiliation_role_invalid_attr2(){
714 let elem: Element = "
715 <item xmlns='http://jabber.org/protocol/muc#user'
716 role='moderator'/>
717 ".parse().unwrap();
718 let error = Item::try_from(elem).unwrap_err();
719 let message = match error {
720 Error::ParseError(string) => string,
721 _ => panic!(),
722 };
723 assert_eq!(message, "Required attribute 'affiliation' missing.".to_owned());
724 }
725
726 #[test]
727 fn test_item_role_actor_child(){
728 let elem: Element = "
729 <item xmlns='http://jabber.org/protocol/muc#user'
730 affiliation='member'
731 role='moderator'>
732 <actor nick='foobar'/>
733 </item>
734 ".parse().unwrap();
735 let item = Item::try_from(elem).unwrap();
736 match item {
737 Item { actor, .. } =>
738 assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
739 }
740 }
741
742 #[test]
743 fn test_item_role_continue_child(){
744 let elem: Element = "
745 <item xmlns='http://jabber.org/protocol/muc#user'
746 affiliation='member'
747 role='moderator'>
748 <continue thread='foobar'/>
749 </item>
750 ".parse().unwrap();
751 let item = Item::try_from(elem).unwrap();
752 let continue_1 = Continue { thread: Some("foobar".to_owned()) };
753 match item {
754 Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2, continue_1),
755 _ => panic!(),
756 }
757 }
758
759 #[test]
760 fn test_item_role_reason_child(){
761 let elem: Element = "
762 <item xmlns='http://jabber.org/protocol/muc#user'
763 affiliation='member'
764 role='moderator'>
765 <reason>foobar</reason>
766 </item>
767 ".parse().unwrap();
768 let item = Item::try_from(elem).unwrap();
769 match item {
770 Item { reason, .. } =>
771 assert_eq!(reason, Some(Reason("foobar".to_owned()))),
772 }
773 }
774}