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