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", String::from(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", match item.jid {
334                    Some(jid) => Some(String::from(jid)),
335                    None => None,
336                })
337                .attr("nick", item.nick)
338                .attr("role", item.role)
339                .append(item.actor)
340                .append(item.continue_)
341                .append(item.reason)
342                .build()
343    }
344}
345
346#[derive(Debug, Clone)]
347pub struct MucUser {
348    pub status: Vec<Status>,
349    pub items: Vec<Item>,
350}
351
352impl TryFrom<Element> for MucUser {
353    type Err = Error;
354
355    fn try_from(elem: Element) -> Result<MucUser, Error> {
356        if !elem.is("x", ns::MUC_USER) {
357            return Err(Error::ParseError("This is not an x element."));
358        }
359        let mut status = vec!();
360        let mut items = vec!();
361        for child in elem.children() {
362            if child.is("status", ns::MUC_USER) {
363                status.push(Status::try_from(child.clone())?);
364            } else if child.is("item", ns::MUC_USER) {
365                items.push(Item::try_from(child.clone())?);
366            } else {
367                return Err(Error::ParseError("Unknown child in x element."));
368            }
369        }
370        for _ in elem.attrs() {
371            return Err(Error::ParseError("Unknown attribute in x element."));
372        }
373        Ok(MucUser {
374            status,
375            items,
376        })
377    }
378}
379
380impl From<MucUser> for Element {
381    fn from(muc_user: MucUser) -> Element {
382        Element::builder("x")
383                .ns(ns::MUC_USER)
384                .append(muc_user.status)
385                .build()
386    }
387}
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392    use std::error::Error as StdError;
393
394    #[test]
395    fn test_simple() {
396        let elem: Element = "
397            <x xmlns='http://jabber.org/protocol/muc#user'/>
398        ".parse().unwrap();
399        MucUser::try_from(elem).unwrap();
400    }
401
402    #[test]
403    fn test_invalid_child() {
404        let elem: Element = "
405            <x xmlns='http://jabber.org/protocol/muc#user'>
406                <coucou/>
407            </x>
408        ".parse().unwrap();
409        let error = MucUser::try_from(elem).unwrap_err();
410        let message = match error {
411            Error::ParseError(string) => string,
412            _ => panic!(),
413        };
414        assert_eq!(message, "Unknown child in x element.");
415    }
416
417    #[test]
418    fn test_serialise() {
419        let elem: Element = "
420            <x xmlns='http://jabber.org/protocol/muc#user'/>
421        ".parse().unwrap();
422        let muc = MucUser { status: vec!(), items: vec!() };
423        let elem2 = muc.into();
424        assert_eq!(elem, elem2);
425    }
426
427    #[test]
428    fn test_invalid_attribute() {
429        let elem: Element = "
430            <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
431        ".parse().unwrap();
432        let error = MucUser::try_from(elem).unwrap_err();
433        let message = match error {
434            Error::ParseError(string) => string,
435            _ => panic!(),
436        };
437        assert_eq!(message, "Unknown attribute in x element.");
438    }
439
440    #[test]
441    fn test_status_simple() {
442        let elem: Element = "
443            <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
444        ".parse().unwrap();
445        Status::try_from(elem).unwrap();
446    }
447
448    #[test]
449    fn test_status_invalid() {
450        let elem: Element = "
451            <status xmlns='http://jabber.org/protocol/muc#user'/>
452        ".parse().unwrap();
453        let error = Status::try_from(elem).unwrap_err();
454        let message = match error {
455            Error::ParseError(string) => string,
456            _ => panic!(),
457        };
458        assert_eq!(message, "Required attribute 'code' missing.");
459    }
460
461    #[test]
462    fn test_status_invalid_child() {
463        let elem: Element = "
464            <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
465                <foo/>
466            </status>
467        ".parse().unwrap();
468        let error = Status::try_from(elem).unwrap_err();
469        let message = match error {
470            Error::ParseError(string) => string,
471            _ => panic!(),
472        };
473        assert_eq!(message, "Unknown child in status element.");
474    }
475
476    #[test]
477    fn test_status_simple_code() {
478        let elem: Element = "
479            <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
480        ".parse().unwrap();
481        let status = Status::try_from(elem).unwrap();
482        assert_eq!(status, Status::Kicked);
483    }
484
485    #[test]
486    fn test_status_invalid_code() {
487        let elem: Element = "
488            <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
489        ".parse().unwrap();
490        let error = Status::try_from(elem).unwrap_err();
491        let message = match error {
492            Error::ParseError(string) => string,
493            _ => panic!(),
494        };
495        assert_eq!(message, "Invalid status code.");
496    }
497
498    #[test]
499    fn test_status_invalid_code2() {
500        let elem: Element = "
501            <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
502        ".parse().unwrap();
503        let error = Status::try_from(elem).unwrap_err();
504        let error = match error {
505            Error::ParseIntError(error) => error,
506            _ => panic!(),
507        };
508        assert_eq!(error.description(), "invalid digit found in string");
509    }
510
511    #[test]
512    fn test_actor_required_attributes() {
513        let elem: Element = "
514            <actor xmlns='http://jabber.org/protocol/muc#user'/>
515        ".parse().unwrap();
516        let error = Actor::try_from(elem).unwrap_err();
517        let message = match error {
518            Error::ParseError(string) => string,
519            _ => panic!(),
520        };
521        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
522    }
523
524    #[test]
525    fn test_actor_required_attributes2() {
526        let elem: Element = "
527            <actor xmlns='http://jabber.org/protocol/muc#user'
528                   jid='foo@bar/baz'
529                   nick='baz'/>
530        ".parse().unwrap();
531        let error = Actor::try_from(elem).unwrap_err();
532        let message = match error {
533            Error::ParseError(string) => string,
534            _ => panic!(),
535        };
536        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
537    }
538
539    #[test]
540    fn test_actor_jid() {
541        let elem: Element = "
542            <actor xmlns='http://jabber.org/protocol/muc#user'
543                   jid='foo@bar/baz'/>
544        ".parse().unwrap();
545        let actor = Actor::try_from(elem).unwrap();
546        let jid = match actor {
547            Actor::Jid(jid) => jid,
548            _ => panic!(),
549        };
550        assert_eq!(jid, "foo@bar/baz".parse::<Jid>().unwrap());
551    }
552
553    #[test]
554    fn test_actor_nick() {
555        let elem: Element = "
556            <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
557        ".parse().unwrap();
558        let actor = Actor::try_from(elem).unwrap();
559        let nick = match actor {
560            Actor::Nick(nick) => nick,
561            _ => panic!(),
562        };
563        assert_eq!(nick, "baz".to_owned());
564    }
565
566    #[test]
567    fn test_continue_simple() {
568        let elem: Element = "
569            <continue xmlns='http://jabber.org/protocol/muc#user'/>
570        ".parse().unwrap();
571        Continue::try_from(elem).unwrap();
572    }
573
574    #[test]
575    fn test_continue_thread_attribute() {
576        let elem: Element = "
577            <continue xmlns='http://jabber.org/protocol/muc#user'
578                      thread='foo'/>
579        ".parse().unwrap();
580        let continue_ = Continue::try_from(elem).unwrap();
581        assert_eq!(continue_, Continue { thread: Some("foo".to_owned()) });
582    }
583
584    #[test]
585    fn test_continue_invalid() {
586        let elem: Element = "
587            <continue xmlns='http://jabber.org/protocol/muc#user'>
588                <foobar/>
589            </continue>
590        ".parse().unwrap();
591        let continue_ = Continue::try_from(elem).unwrap_err();
592        let message = match continue_ {
593            Error::ParseError(string) => string,
594            _ => panic!(),
595        };
596        assert_eq!(message, "Unknown child in continue element.".to_owned());
597    }
598
599    #[test]
600    fn test_reason_simple() {
601        let elem: Element = "
602            <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
603        .parse().unwrap();
604        let reason = Reason::try_from(elem).unwrap();
605        assert_eq!(reason.0, "Reason".to_owned());
606    }
607
608    #[test]
609    fn test_reason_invalid_attribute() {
610        let elem: Element = "
611            <reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
612        ".parse().unwrap();
613        let error = Reason::try_from(elem).unwrap_err();
614        let message = match error {
615            Error::ParseError(string) => string,
616            _ => panic!(),
617        };
618        assert_eq!(message, "Unknown attribute in reason element.".to_owned());
619    }
620
621    #[test]
622    fn test_reason_invalid() {
623        let elem: Element = "
624            <reason xmlns='http://jabber.org/protocol/muc#user'>
625                <foobar/>
626            </reason>
627        ".parse().unwrap();
628        let error = Reason::try_from(elem).unwrap_err();
629        let message = match error {
630            Error::ParseError(string) => string,
631            _ => panic!(),
632        };
633        assert_eq!(message, "Unknown child in reason element.".to_owned());
634    }
635
636    #[test]
637    fn test_item_invalid_attr(){
638        let elem: Element = "
639            <item xmlns='http://jabber.org/protocol/muc#user'
640                  foo='bar'/>
641        ".parse().unwrap();
642        let error = Item::try_from(elem).unwrap_err();
643        let message = match error {
644            Error::ParseError(string) => string,
645            _ => panic!(),
646        };
647        assert_eq!(message, "Unknown attribute in item element.".to_owned());
648    }
649
650    #[test]
651    fn test_item_affiliation_role_attr(){
652        let elem: Element = "
653            <item xmlns='http://jabber.org/protocol/muc#user'
654                  affiliation='member'
655                  role='moderator'/>
656        ".parse().unwrap();
657        Item::try_from(elem).unwrap();
658    }
659
660    #[test]
661    fn test_item_affiliation_role_invalid_attr(){
662        let elem: Element = "
663            <item xmlns='http://jabber.org/protocol/muc#user'
664                  affiliation='member'/>
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, "Required attribute 'role' missing.".to_owned());
672    }
673
674    #[test]
675    fn test_item_nick_attr(){
676        let elem: Element = "
677            <item xmlns='http://jabber.org/protocol/muc#user'
678                  affiliation='member'
679                  role='moderator'
680                  nick='foobar'/>
681        ".parse().unwrap();
682        let item = Item::try_from(elem).unwrap();
683        match item {
684            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
685        }
686    }
687
688    #[test]
689    fn test_item_affiliation_role_invalid_attr2(){
690        let elem: Element = "
691            <item xmlns='http://jabber.org/protocol/muc#user'
692                  role='moderator'/>
693        ".parse().unwrap();
694        let error = Item::try_from(elem).unwrap_err();
695        let message = match error {
696            Error::ParseError(string) => string,
697            _ => panic!(),
698        };
699        assert_eq!(message, "Required attribute 'affiliation' missing.".to_owned());
700    }
701
702    #[test]
703    fn test_item_role_actor_child(){
704        let elem: Element = "
705            <item xmlns='http://jabber.org/protocol/muc#user'
706                  affiliation='member'
707                  role='moderator'>
708                <actor nick='foobar'/>
709            </item>
710        ".parse().unwrap();
711        let item = Item::try_from(elem).unwrap();
712        match item {
713            Item { actor, .. } =>
714                assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
715        }
716    }
717
718    #[test]
719    fn test_item_role_continue_child(){
720        let elem: Element = "
721            <item xmlns='http://jabber.org/protocol/muc#user'
722                  affiliation='member'
723                  role='moderator'>
724                <continue thread='foobar'/>
725            </item>
726        ".parse().unwrap();
727        let item = Item::try_from(elem).unwrap();
728        let continue_1 = Continue { thread: Some("foobar".to_owned()) };
729        match item {
730            Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2, continue_1),
731            _ => panic!(),
732        }
733    }
734
735    #[test]
736    fn test_item_role_reason_child(){
737        let elem: Element = "
738            <item xmlns='http://jabber.org/protocol/muc#user'
739                  affiliation='member'
740                  role='moderator'>
741                <reason>foobar</reason>
742            </item>
743        ".parse().unwrap();
744        let item = Item::try_from(elem).unwrap();
745        match item {
746            Item { reason, .. } =>
747                assert_eq!(reason, Some(Reason("foobar".to_owned()))),
748        }
749    }
750}