user.rs

  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, IntoAttributeValue};
 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
145/// Optional <actor/> element used in <item/> elements inside presence stanzas of type
146/// "unavailable" that are sent to users who are kick or banned, as well as within IQs for tracking
147/// purposes. -- CHANGELOG  0.17 (2002-10-23)
148/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real
149/// JID or to a roomnick. -- CHANGELOG  1.25 (2012-02-08)
150#[derive(Debug, Clone, PartialEq)]
151pub enum Actor {
152    Jid(Jid),
153    Nick(String),
154}
155
156impl TryFrom<Element> for Actor {
157    type Err = Error;
158
159    fn try_from(elem: Element) -> Result<Actor, Error> {
160        if !elem.is("actor", ns::MUC_USER) {
161            return Err(Error::ParseError("This is not a actor element."));
162        }
163        for _ in elem.children() {
164            return Err(Error::ParseError("Unknown child in actor element."));
165        }
166        for (attr, _) in elem.attrs() {
167            if attr != "jid" && attr != "nick" {
168                return Err(Error::ParseError("Unknown attribute in actor element."));
169            }
170        }
171        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
172        let nick = get_attr!(elem, "nick", optional);
173
174        match (jid, nick) {
175            (Some(_), Some(_))
176          | (None, None) =>
177                return Err(Error::ParseError("Either 'jid' or 'nick' attribute is required.")),
178            (Some(jid), _) => Ok(Actor::Jid(jid)),
179            (_, Some(nick)) => Ok(Actor::Nick(nick)),
180        }
181    }
182}
183
184impl From<Actor> for Element {
185    fn from(actor: Actor) -> Element {
186        let elem = Element::builder("actor").ns(ns::MUC_USER);
187
188        (match actor {
189            Actor::Jid(jid) => elem.attr("jid", jid),
190            Actor::Nick(nick) => elem.attr("nick", nick),
191        }).build()
192    }
193}
194
195#[derive(Debug, Clone, PartialEq)]
196pub struct Continue {
197    thread: Option<String>,
198}
199
200impl TryFrom<Element> for Continue {
201    type Err = Error;
202
203    fn try_from(elem: Element) -> Result<Continue, Error> {
204        if !elem.is("continue", ns::MUC_USER) {
205            return Err(Error::ParseError("This is not a continue element."));
206        }
207        for _ in elem.children() {
208            return Err(Error::ParseError("Unknown child in continue element."));
209        }
210        for (attr, _) in elem.attrs() {
211            if attr != "thread" {
212                return Err(Error::ParseError("Unknown attribute in continue element."));
213            }
214        }
215        Ok(Continue { thread: get_attr!(elem, "thread", optional) })
216    }
217}
218
219impl From<Continue> for Element {
220    fn from(cont: Continue) -> Element {
221        Element::builder("continue")
222                .ns(ns::MUC_USER)
223                .attr("thread", cont.thread)
224                .build()
225    }
226}
227
228#[derive(Debug, Clone, PartialEq)]
229pub struct Reason(String);
230
231impl TryFrom<Element> for Reason {
232    type Err = Error;
233
234    fn try_from(elem: Element) -> Result<Reason, Error> {
235        if !elem.is("reason", ns::MUC_USER) {
236            return Err(Error::ParseError("This is not a reason element."));
237        }
238        for _ in elem.children() {
239            return Err(Error::ParseError("Unknown child in reason element."));
240        }
241        for _ in elem.attrs() {
242            return Err(Error::ParseError("Unknown attribute in reason element."));
243        }
244        Ok(Reason(elem.text()))
245    }
246}
247
248impl From<Reason> for Element {
249    fn from(reason: Reason) -> Element {
250        Element::builder("reason")
251                .ns(ns::MUC_USER)
252                .append(reason.0)
253                .build()
254    }
255}
256
257generate_attribute!(Affiliation, "affiliation", {
258    Owner => "owner",
259    Admin => "admin",
260    Member => "member",
261    Outcast => "outcast",
262    None => "none",
263}, Default = None);
264
265generate_attribute!(Role, "role", {
266    Moderator => "moderator",
267    Participant => "participant",
268    Visitor => "visitor",
269    None => "none",
270}, Default = None);
271
272#[derive(Debug, Clone)]
273pub struct Item {
274    pub affiliation: Affiliation,
275    pub jid: Option<Jid>,
276    pub nick: Option<String>,
277    pub role: Role,
278    pub actor: Option<Actor>,
279    pub continue_: Option<Continue>,
280    pub reason: Option<Reason>,
281}
282
283impl TryFrom<Element> for Item {
284    type Err = Error;
285
286    fn try_from(elem: Element) -> Result<Item, Error> {
287        if !elem.is("item", ns::MUC_USER) {
288            return Err(Error::ParseError("This is not a item element."));
289        }
290        let mut actor: Option<Actor> = None;
291        let mut continue_: Option<Continue> = None;
292        let mut reason: Option<Reason> = None;
293        for child in elem.children() {
294            if child.is("actor", ns::MUC_USER) {
295                actor = Some(child.clone().try_into()?);
296            } else if child.is("continue", ns::MUC_USER) {
297                continue_ = Some(child.clone().try_into()?);
298            } else if child.is("reason", ns::MUC_USER) {
299                reason = Some(child.clone().try_into()?);
300            } else {
301                return Err(Error::ParseError("Unknown child in item element."));
302            }
303        }
304        for (attr, _) in elem.attrs() {
305            if attr != "affiliation" && attr != "jid" &&
306               attr != "nick" && attr != "role" {
307                return Err(Error::ParseError("Unknown attribute in item element."));
308            }
309        }
310
311        let affiliation: Affiliation = get_attr!(elem, "affiliation", required);
312        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
313        let nick: Option<String> = get_attr!(elem, "nick", optional);
314        let role: Role = get_attr!(elem, "role", required);
315
316        Ok(Item{
317            affiliation: affiliation,
318            jid: jid,
319            nick: nick,
320            role: role,
321            actor: actor,
322            continue_: continue_,
323            reason: reason,
324        })
325    }
326}
327
328impl From<Item> for Element {
329    fn from(item: Item) -> Element {
330        Element::builder("item")
331                .ns(ns::MUC_USER)
332                .attr("affiliation", item.affiliation)
333                .attr("jid", item.jid)
334                .attr("nick", item.nick)
335                .attr("role", item.role)
336                .append(item.actor)
337                .append(item.continue_)
338                .append(item.reason)
339                .build()
340    }
341}
342
343#[derive(Debug, Clone)]
344pub struct MucUser {
345    pub status: Vec<Status>,
346    pub items: Vec<Item>,
347}
348
349impl TryFrom<Element> for MucUser {
350    type Err = Error;
351
352    fn try_from(elem: Element) -> Result<MucUser, Error> {
353        if !elem.is("x", ns::MUC_USER) {
354            return Err(Error::ParseError("This is not an x element."));
355        }
356        let mut status = vec!();
357        let mut items = vec!();
358        for child in elem.children() {
359            if child.is("status", ns::MUC_USER) {
360                status.push(Status::try_from(child.clone())?);
361            } else if child.is("item", ns::MUC_USER) {
362                items.push(Item::try_from(child.clone())?);
363            } else {
364                return Err(Error::ParseError("Unknown child in x element."));
365            }
366        }
367        for _ in elem.attrs() {
368            return Err(Error::ParseError("Unknown attribute in x element."));
369        }
370        Ok(MucUser {
371            status,
372            items,
373        })
374    }
375}
376
377impl From<MucUser> for Element {
378    fn from(muc_user: MucUser) -> Element {
379        Element::builder("x")
380                .ns(ns::MUC_USER)
381                .append(muc_user.status)
382                .build()
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389    use std::error::Error as StdError;
390    use compare_elements::NamespaceAwareCompare;
391
392    #[test]
393    fn test_simple() {
394        let elem: Element = "
395            <x xmlns='http://jabber.org/protocol/muc#user'/>
396        ".parse().unwrap();
397        MucUser::try_from(elem).unwrap();
398    }
399
400    #[test]
401    fn test_invalid_child() {
402        let elem: Element = "
403            <x xmlns='http://jabber.org/protocol/muc#user'>
404                <coucou/>
405            </x>
406        ".parse().unwrap();
407        let error = MucUser::try_from(elem).unwrap_err();
408        let message = match error {
409            Error::ParseError(string) => string,
410            _ => panic!(),
411        };
412        assert_eq!(message, "Unknown child in x element.");
413    }
414
415    #[test]
416    fn test_serialise() {
417        let elem: Element = "
418            <x xmlns='http://jabber.org/protocol/muc#user'/>
419        ".parse().unwrap();
420        let muc = MucUser { status: vec!(), items: vec!() };
421        let elem2 = muc.into();
422        assert!(elem.compare_to(&elem2));
423    }
424
425    #[test]
426    fn test_invalid_attribute() {
427        let elem: Element = "
428            <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
429        ".parse().unwrap();
430        let error = MucUser::try_from(elem).unwrap_err();
431        let message = match error {
432            Error::ParseError(string) => string,
433            _ => panic!(),
434        };
435        assert_eq!(message, "Unknown attribute in x element.");
436    }
437
438    #[test]
439    fn test_status_simple() {
440        let elem: Element = "
441            <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
442        ".parse().unwrap();
443        Status::try_from(elem).unwrap();
444    }
445
446    #[test]
447    fn test_status_invalid() {
448        let elem: Element = "
449            <status xmlns='http://jabber.org/protocol/muc#user'/>
450        ".parse().unwrap();
451        let error = Status::try_from(elem).unwrap_err();
452        let message = match error {
453            Error::ParseError(string) => string,
454            _ => panic!(),
455        };
456        assert_eq!(message, "Required attribute 'code' missing.");
457    }
458
459    #[test]
460    fn test_status_invalid_child() {
461        let elem: Element = "
462            <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
463                <foo/>
464            </status>
465        ".parse().unwrap();
466        let error = Status::try_from(elem).unwrap_err();
467        let message = match error {
468            Error::ParseError(string) => string,
469            _ => panic!(),
470        };
471        assert_eq!(message, "Unknown child in status element.");
472    }
473
474    #[test]
475    fn test_status_simple_code() {
476        let elem: Element = "
477            <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
478        ".parse().unwrap();
479        let status = Status::try_from(elem).unwrap();
480        assert_eq!(status, Status::Kicked);
481    }
482
483    #[test]
484    fn test_status_invalid_code() {
485        let elem: Element = "
486            <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
487        ".parse().unwrap();
488        let error = Status::try_from(elem).unwrap_err();
489        let message = match error {
490            Error::ParseError(string) => string,
491            _ => panic!(),
492        };
493        assert_eq!(message, "Invalid status code.");
494    }
495
496    #[test]
497    fn test_status_invalid_code2() {
498        let elem: Element = "
499            <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
500        ".parse().unwrap();
501        let error = Status::try_from(elem).unwrap_err();
502        let error = match error {
503            Error::ParseIntError(error) => error,
504            _ => panic!(),
505        };
506        assert_eq!(error.description(), "invalid digit found in string");
507    }
508
509    #[test]
510    fn test_actor_required_attributes() {
511        let elem: Element = "
512            <actor xmlns='http://jabber.org/protocol/muc#user'/>
513        ".parse().unwrap();
514        let error = Actor::try_from(elem).unwrap_err();
515        let message = match error {
516            Error::ParseError(string) => string,
517            _ => panic!(),
518        };
519        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
520    }
521
522    #[test]
523    fn test_actor_required_attributes2() {
524        let elem: Element = "
525            <actor xmlns='http://jabber.org/protocol/muc#user'
526                   jid='foo@bar/baz'
527                   nick='baz'/>
528        ".parse().unwrap();
529        let error = Actor::try_from(elem).unwrap_err();
530        let message = match error {
531            Error::ParseError(string) => string,
532            _ => panic!(),
533        };
534        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
535    }
536
537    #[test]
538    fn test_actor_jid() {
539        let elem: Element = "
540            <actor xmlns='http://jabber.org/protocol/muc#user'
541                   jid='foo@bar/baz'/>
542        ".parse().unwrap();
543        let actor = Actor::try_from(elem).unwrap();
544        let jid = match actor {
545            Actor::Jid(jid) => jid,
546            _ => panic!(),
547        };
548        assert_eq!(jid, "foo@bar/baz".parse::<Jid>().unwrap());
549    }
550
551    #[test]
552    fn test_actor_nick() {
553        let elem: Element = "
554            <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
555        ".parse().unwrap();
556        let actor = Actor::try_from(elem).unwrap();
557        let nick = match actor {
558            Actor::Nick(nick) => nick,
559            _ => panic!(),
560        };
561        assert_eq!(nick, "baz".to_owned());
562    }
563
564    #[test]
565    fn test_continue_simple() {
566        let elem: Element = "
567            <continue xmlns='http://jabber.org/protocol/muc#user'/>
568        ".parse().unwrap();
569        Continue::try_from(elem).unwrap();
570    }
571
572    #[test]
573    fn test_continue_thread_attribute() {
574        let elem: Element = "
575            <continue xmlns='http://jabber.org/protocol/muc#user'
576                      thread='foo'/>
577        ".parse().unwrap();
578        let continue_ = Continue::try_from(elem).unwrap();
579        assert_eq!(continue_, Continue { thread: Some("foo".to_owned()) });
580    }
581
582    #[test]
583    fn test_continue_invalid() {
584        let elem: Element = "
585            <continue xmlns='http://jabber.org/protocol/muc#user'>
586                <foobar/>
587            </continue>
588        ".parse().unwrap();
589        let continue_ = Continue::try_from(elem).unwrap_err();
590        let message = match continue_ {
591            Error::ParseError(string) => string,
592            _ => panic!(),
593        };
594        assert_eq!(message, "Unknown child in continue element.".to_owned());
595    }
596
597    #[test]
598    fn test_reason_simple() {
599        let elem: Element = "
600            <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
601        .parse().unwrap();
602        let reason = Reason::try_from(elem).unwrap();
603        assert_eq!(reason.0, "Reason".to_owned());
604    }
605
606    #[test]
607    fn test_reason_invalid_attribute() {
608        let elem: Element = "
609            <reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
610        ".parse().unwrap();
611        let error = Reason::try_from(elem).unwrap_err();
612        let message = match error {
613            Error::ParseError(string) => string,
614            _ => panic!(),
615        };
616        assert_eq!(message, "Unknown attribute in reason element.".to_owned());
617    }
618
619    #[test]
620    fn test_reason_invalid() {
621        let elem: Element = "
622            <reason xmlns='http://jabber.org/protocol/muc#user'>
623                <foobar/>
624            </reason>
625        ".parse().unwrap();
626        let error = Reason::try_from(elem).unwrap_err();
627        let message = match error {
628            Error::ParseError(string) => string,
629            _ => panic!(),
630        };
631        assert_eq!(message, "Unknown child in reason element.".to_owned());
632    }
633
634    #[test]
635    fn test_item_invalid_attr(){
636        let elem: Element = "
637            <item xmlns='http://jabber.org/protocol/muc#user'
638                  foo='bar'/>
639        ".parse().unwrap();
640        let error = Item::try_from(elem).unwrap_err();
641        let message = match error {
642            Error::ParseError(string) => string,
643            _ => panic!(),
644        };
645        assert_eq!(message, "Unknown attribute in item element.".to_owned());
646    }
647
648    #[test]
649    fn test_item_affiliation_role_attr(){
650        let elem: Element = "
651            <item xmlns='http://jabber.org/protocol/muc#user'
652                  affiliation='member'
653                  role='moderator'/>
654        ".parse().unwrap();
655        Item::try_from(elem).unwrap();
656    }
657
658    #[test]
659    fn test_item_affiliation_role_invalid_attr(){
660        let elem: Element = "
661            <item xmlns='http://jabber.org/protocol/muc#user'
662                  affiliation='member'/>
663        ".parse().unwrap();
664        let error = Item::try_from(elem).unwrap_err();
665        let message = match error {
666            Error::ParseError(string) => string,
667            _ => panic!(),
668        };
669        assert_eq!(message, "Required attribute 'role' missing.".to_owned());
670    }
671
672    #[test]
673    fn test_item_nick_attr(){
674        let elem: Element = "
675            <item xmlns='http://jabber.org/protocol/muc#user'
676                  affiliation='member'
677                  role='moderator'
678                  nick='foobar'/>
679        ".parse().unwrap();
680        let item = Item::try_from(elem).unwrap();
681        match item {
682            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
683        }
684    }
685
686    #[test]
687    fn test_item_affiliation_role_invalid_attr2(){
688        let elem: Element = "
689            <item xmlns='http://jabber.org/protocol/muc#user'
690                  role='moderator'/>
691        ".parse().unwrap();
692        let error = Item::try_from(elem).unwrap_err();
693        let message = match error {
694            Error::ParseError(string) => string,
695            _ => panic!(),
696        };
697        assert_eq!(message, "Required attribute 'affiliation' missing.".to_owned());
698    }
699
700    #[test]
701    fn test_item_role_actor_child(){
702        let elem: Element = "
703            <item xmlns='http://jabber.org/protocol/muc#user'
704                  affiliation='member'
705                  role='moderator'>
706                <actor nick='foobar'/>
707            </item>
708        ".parse().unwrap();
709        let item = Item::try_from(elem).unwrap();
710        match item {
711            Item { actor, .. } =>
712                assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
713        }
714    }
715
716    #[test]
717    fn test_item_role_continue_child(){
718        let elem: Element = "
719            <item xmlns='http://jabber.org/protocol/muc#user'
720                  affiliation='member'
721                  role='moderator'>
722                <continue thread='foobar'/>
723            </item>
724        ".parse().unwrap();
725        let item = Item::try_from(elem).unwrap();
726        let continue_1 = Continue { thread: Some("foobar".to_owned()) };
727        match item {
728            Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2, continue_1),
729            _ => panic!(),
730        }
731    }
732
733    #[test]
734    fn test_item_role_reason_child(){
735        let elem: Element = "
736            <item xmlns='http://jabber.org/protocol/muc#user'
737                  affiliation='member'
738                  role='moderator'>
739                <reason>foobar</reason>
740            </item>
741        ".parse().unwrap();
742        let item = Item::try_from(elem).unwrap();
743        match item {
744            Item { reason, .. } =>
745                assert_eq!(reason, Some(Reason("foobar".to_owned()))),
746        }
747    }
748}