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;
  9
 10use minidom::Element;
 11
 12use jid::Jid;
 13
 14use error::Error;
 15
 16use ns;
 17
 18generate_attribute_enum!(
 19/// Lists all of the possible status codes used in MUC presences.
 20Status, "status", MUC_USER, "code", {
 21    /// Inform user that any occupant is allowed to see the user's full JID
 22    NonAnonymousRoom => 100,
 23
 24    /// Inform user that his or her affiliation changed while not in the room
 25    AffiliationChange => 101,
 26
 27    /// Inform occupants that room now shows unavailable members
 28    ConfigShowsUnavailableMembers => 102,
 29
 30    /// Inform occupants that room now does not show unavailable members
 31    ConfigHidesUnavailableMembers => 103,
 32
 33    /// Inform occupants that a non-privacy-related room configuration change has occurred
 34    ConfigNonPrivacyRelated => 104,
 35
 36    /// Inform user that presence refers to itself
 37    SelfPresence => 110,
 38
 39    /// Inform occupants that room logging is now enabled
 40    ConfigRoomLoggingEnabled => 170,
 41
 42    /// Inform occupants that room logging is now disabled
 43    ConfigRoomLoggingDisabled => 171,
 44
 45    /// Inform occupants that the room is now non-anonymous
 46    ConfigRoomNonAnonymous => 172,
 47
 48    /// Inform occupants that the room is now semi-anonymous
 49    ConfigRoomSemiAnonymous => 173,
 50
 51    /// Inform user that a new room has been created
 52    RoomHasBeenCreated => 201,
 53
 54    /// Inform user that service has assigned or modified occupant's roomnick
 55    AssignedNick => 210,
 56
 57    /// Inform user that he or she has been banned from the room
 58    Banned => 301,
 59
 60    /// Inform all occupants of new room nickname
 61    NewNick => 303,
 62
 63    /// Inform user that he or she has been kicked from the room
 64    Kicked => 307,
 65
 66    /// Inform user that he or she is being removed from the room
 67    /// because of an affiliation change
 68    RemovalFromRoom => 321,
 69
 70    /// Inform user that he or she is being removed from the room
 71    /// because the room has been changed to members-only and the
 72    /// user is not a member
 73    ConfigMembersOnly => 322,
 74
 75    /// Inform user that he or she is being removed from the room
 76    /// because the MUC service is being shut down
 77    ServiceShutdown => 332,
 78});
 79
 80/// Optional <actor/> element used in <item/> elements inside presence stanzas of type
 81/// "unavailable" that are sent to users who are kick or banned, as well as within IQs for tracking
 82/// purposes. -- CHANGELOG  0.17 (2002-10-23)
 83///
 84/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real
 85/// JID or to a roomnick. -- CHANGELOG  1.25 (2012-02-08)
 86#[derive(Debug, Clone, PartialEq)]
 87pub enum Actor {
 88    /// The full JID associated with this user.
 89    Jid(Jid),
 90
 91    /// The nickname of this user.
 92    Nick(String),
 93}
 94
 95impl TryFrom<Element> for Actor {
 96    type Err = Error;
 97
 98    fn try_from(elem: Element) -> Result<Actor, Error> {
 99        check_self!(elem, "actor", MUC_USER);
100        check_no_unknown_attributes!(elem, "actor", ["jid", "nick"]);
101        check_no_children!(elem, "actor");
102        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
103        let nick = get_attr!(elem, "nick", optional);
104
105        match (jid, nick) {
106            (Some(_), Some(_))
107          | (None, None) =>
108                return Err(Error::ParseError("Either 'jid' or 'nick' attribute is required.")),
109            (Some(jid), _) => Ok(Actor::Jid(jid)),
110            (_, Some(nick)) => Ok(Actor::Nick(nick)),
111        }
112    }
113}
114
115impl From<Actor> for Element {
116    fn from(actor: Actor) -> Element {
117        let elem = Element::builder("actor").ns(ns::MUC_USER);
118
119        (match actor {
120            Actor::Jid(jid) => elem.attr("jid", jid),
121            Actor::Nick(nick) => elem.attr("nick", nick),
122        }).build()
123    }
124}
125
126generate_element!(
127    /// Used to continue a one-to-one discussion in a room, with more than one
128    /// participant.
129    Continue, "continue", MUC_USER,
130    attributes: [
131        /// The thread to continue in this room.
132        thread: Option<String> = "thread" => optional,
133    ]
134);
135
136generate_elem_id!(
137    /// A reason for inviting, declining, etc. a request.
138    Reason, "reason", MUC_USER
139);
140
141generate_attribute!(
142    /// The affiliation of an entity with a room, which isn’t tied to its
143    /// presence in it.
144    Affiliation, "affiliation", {
145        /// The user who created the room, or who got appointed by its creator
146        /// to be their equal.
147        Owner => "owner",
148
149        /// A user who has been empowered by an owner to do administrative
150        /// operations.
151        Admin => "admin",
152
153        /// A user who is whitelisted to speak in moderated rooms, or to join a
154        /// member-only room.
155        Member => "member",
156
157        /// A user who has been banned from this room.
158        Outcast => "outcast",
159
160        /// A normal participant.
161        None => "none",
162    }, Default = None
163);
164
165generate_attribute!(
166    /// The current role of an entity in a room, it can be changed by an owner
167    /// or an administrator but will be lost once they leave the room.
168    Role, "role", {
169        /// This user can kick other participants, as well as grant and revoke
170        /// them voice.
171        Moderator => "moderator",
172
173        /// A user who can speak in this room.
174        Participant => "participant",
175
176        /// A user who cannot speak in this room, and must request voice before
177        /// doing so.
178        Visitor => "visitor",
179
180        /// A user who is absent from the room.
181        None => "none",
182    }, Default = None
183);
184
185generate_element!(
186    /// An item representing a user in a room.
187    Item, "item", MUC_USER, attributes: [
188        /// The affiliation of this user with the room.
189        affiliation: Affiliation = "affiliation" => required,
190
191        /// The real JID of this user, if you are allowed to see it.
192        jid: Option<Jid> = "jid" => optional,
193
194        /// The current nickname of this user.
195        nick: Option<String> = "nick" => optional,
196
197        /// The current role of this user.
198        role: Role = "role" => required
199    ], children: [
200        /// The actor affected by this item.
201        actor: Option<Actor> = ("actor", MUC_USER) => Actor,
202
203        /// Whether this continues a one-to-one discussion.
204        continue_: Option<Continue> = ("continue", MUC_USER) => Continue,
205
206        /// A reason for this item.
207        reason: Option<Reason> = ("reason", MUC_USER) => Reason
208    ]
209);
210
211impl Item {
212    /// Creates a new item with the given affiliation and role.
213    pub fn new(affiliation: Affiliation, role: Role) -> Item {
214        Item {
215            affiliation,
216            role,
217            jid: None,
218            nick: None,
219            actor: None,
220            continue_: None,
221            reason: None,
222        }
223    }
224}
225
226generate_element!(
227    /// The main muc#user element.
228    MucUser, "x", MUC_USER, children: [
229        /// List of statuses applying to this item.
230        status: Vec<Status> = ("status", MUC_USER) => Status,
231
232        /// List of items.
233        items: Vec<Item> = ("item", MUC_USER) => Item
234    ]
235);
236
237#[cfg(test)]
238mod tests {
239    use super::*;
240    use std::error::Error as StdError;
241    use compare_elements::NamespaceAwareCompare;
242
243    #[test]
244    fn test_simple() {
245        let elem: Element = "
246            <x xmlns='http://jabber.org/protocol/muc#user'/>
247        ".parse().unwrap();
248        MucUser::try_from(elem).unwrap();
249    }
250
251    #[test]
252    fn statuses_and_items() {
253        let elem: Element = "
254            <x xmlns='http://jabber.org/protocol/muc#user'>
255                <status code='101'/>
256                <status code='102'/>
257                <item affiliation='member' role='moderator'/>
258            </x>
259        ".parse().unwrap();
260        let muc_user = MucUser::try_from(elem).unwrap();
261        assert_eq!(muc_user.status.len(), 2);
262        assert_eq!(muc_user.status[0], Status::AffiliationChange);
263        assert_eq!(muc_user.status[1], Status::ConfigShowsUnavailableMembers);
264        assert_eq!(muc_user.items.len(), 1);
265        assert_eq!(muc_user.items[0].affiliation, Affiliation::Member);
266        assert_eq!(muc_user.items[0].role, Role::Moderator);
267    }
268
269    #[test]
270    fn test_invalid_child() {
271        let elem: Element = "
272            <x xmlns='http://jabber.org/protocol/muc#user'>
273                <coucou/>
274            </x>
275        ".parse().unwrap();
276        let error = MucUser::try_from(elem).unwrap_err();
277        let message = match error {
278            Error::ParseError(string) => string,
279            _ => panic!(),
280        };
281        assert_eq!(message, "Unknown child in x element.");
282    }
283
284    #[test]
285    fn test_serialise() {
286        let elem: Element = "
287            <x xmlns='http://jabber.org/protocol/muc#user'/>
288        ".parse().unwrap();
289        let muc = MucUser { status: vec!(), items: vec!() };
290        let elem2 = muc.into();
291        assert!(elem.compare_to(&elem2));
292    }
293
294    #[test]
295    fn test_invalid_attribute() {
296        let elem: Element = "
297            <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
298        ".parse().unwrap();
299        let error = MucUser::try_from(elem).unwrap_err();
300        let message = match error {
301            Error::ParseError(string) => string,
302            _ => panic!(),
303        };
304        assert_eq!(message, "Unknown attribute in x element.");
305    }
306
307    #[test]
308    fn test_status_simple() {
309        let elem: Element = "
310            <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
311        ".parse().unwrap();
312        Status::try_from(elem).unwrap();
313    }
314
315    #[test]
316    fn test_status_invalid() {
317        let elem: Element = "
318            <status xmlns='http://jabber.org/protocol/muc#user'/>
319        ".parse().unwrap();
320        let error = Status::try_from(elem).unwrap_err();
321        let message = match error {
322            Error::ParseError(string) => string,
323            _ => panic!(),
324        };
325        assert_eq!(message, "Required attribute 'code' missing.");
326    }
327
328    #[test]
329    fn test_status_invalid_child() {
330        let elem: Element = "
331            <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
332                <foo/>
333            </status>
334        ".parse().unwrap();
335        let error = Status::try_from(elem).unwrap_err();
336        let message = match error {
337            Error::ParseError(string) => string,
338            _ => panic!(),
339        };
340        assert_eq!(message, "Unknown child in status element.");
341    }
342
343    #[test]
344    fn test_status_simple_code() {
345        let elem: Element = "
346            <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
347        ".parse().unwrap();
348        let status = Status::try_from(elem).unwrap();
349        assert_eq!(status, Status::Kicked);
350    }
351
352    #[test]
353    fn test_status_invalid_code() {
354        let elem: Element = "
355            <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
356        ".parse().unwrap();
357        let error = Status::try_from(elem).unwrap_err();
358        let message = match error {
359            Error::ParseError(string) => string,
360            _ => panic!(),
361        };
362        assert_eq!(message, "Invalid status code value.");
363    }
364
365    #[test]
366    fn test_status_invalid_code2() {
367        let elem: Element = "
368            <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
369        ".parse().unwrap();
370        let error = Status::try_from(elem).unwrap_err();
371        let error = match error {
372            Error::ParseIntError(error) => error,
373            _ => panic!(),
374        };
375        assert_eq!(error.description(), "invalid digit found in string");
376    }
377
378    #[test]
379    fn test_actor_required_attributes() {
380        let elem: Element = "
381            <actor xmlns='http://jabber.org/protocol/muc#user'/>
382        ".parse().unwrap();
383        let error = Actor::try_from(elem).unwrap_err();
384        let message = match error {
385            Error::ParseError(string) => string,
386            _ => panic!(),
387        };
388        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
389    }
390
391    #[test]
392    fn test_actor_required_attributes2() {
393        let elem: Element = "
394            <actor xmlns='http://jabber.org/protocol/muc#user'
395                   jid='foo@bar/baz'
396                   nick='baz'/>
397        ".parse().unwrap();
398        let error = Actor::try_from(elem).unwrap_err();
399        let message = match error {
400            Error::ParseError(string) => string,
401            _ => panic!(),
402        };
403        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
404    }
405
406    #[test]
407    fn test_actor_jid() {
408        let elem: Element = "
409            <actor xmlns='http://jabber.org/protocol/muc#user'
410                   jid='foo@bar/baz'/>
411        ".parse().unwrap();
412        let actor = Actor::try_from(elem).unwrap();
413        let jid = match actor {
414            Actor::Jid(jid) => jid,
415            _ => panic!(),
416        };
417        assert_eq!(jid, "foo@bar/baz".parse::<Jid>().unwrap());
418    }
419
420    #[test]
421    fn test_actor_nick() {
422        let elem: Element = "
423            <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
424        ".parse().unwrap();
425        let actor = Actor::try_from(elem).unwrap();
426        let nick = match actor {
427            Actor::Nick(nick) => nick,
428            _ => panic!(),
429        };
430        assert_eq!(nick, "baz".to_owned());
431    }
432
433    #[test]
434    fn test_continue_simple() {
435        let elem: Element = "
436            <continue xmlns='http://jabber.org/protocol/muc#user'/>
437        ".parse().unwrap();
438        Continue::try_from(elem).unwrap();
439    }
440
441    #[test]
442    fn test_continue_thread_attribute() {
443        let elem: Element = "
444            <continue xmlns='http://jabber.org/protocol/muc#user'
445                      thread='foo'/>
446        ".parse().unwrap();
447        let continue_ = Continue::try_from(elem).unwrap();
448        assert_eq!(continue_.thread, Some("foo".to_owned()));
449    }
450
451    #[test]
452    fn test_continue_invalid() {
453        let elem: Element = "
454            <continue xmlns='http://jabber.org/protocol/muc#user'>
455                <foobar/>
456            </continue>
457        ".parse().unwrap();
458        let continue_ = Continue::try_from(elem).unwrap_err();
459        let message = match continue_ {
460            Error::ParseError(string) => string,
461            _ => panic!(),
462        };
463        assert_eq!(message, "Unknown child in continue element.".to_owned());
464    }
465
466    #[test]
467    fn test_reason_simple() {
468        let elem: Element = "
469            <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
470        .parse().unwrap();
471        let reason = Reason::try_from(elem).unwrap();
472        assert_eq!(reason.0, "Reason".to_owned());
473    }
474
475    #[test]
476    fn test_reason_invalid_attribute() {
477        let elem: Element = "
478            <reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
479        ".parse().unwrap();
480        let error = Reason::try_from(elem).unwrap_err();
481        let message = match error {
482            Error::ParseError(string) => string,
483            _ => panic!(),
484        };
485        assert_eq!(message, "Unknown attribute in reason element.".to_owned());
486    }
487
488    #[test]
489    fn test_reason_invalid() {
490        let elem: Element = "
491            <reason xmlns='http://jabber.org/protocol/muc#user'>
492                <foobar/>
493            </reason>
494        ".parse().unwrap();
495        let error = Reason::try_from(elem).unwrap_err();
496        let message = match error {
497            Error::ParseError(string) => string,
498            _ => panic!(),
499        };
500        assert_eq!(message, "Unknown child in reason element.".to_owned());
501    }
502
503    #[test]
504    fn test_item_invalid_attr(){
505        let elem: Element = "
506            <item xmlns='http://jabber.org/protocol/muc#user'
507                  foo='bar'/>
508        ".parse().unwrap();
509        let error = Item::try_from(elem).unwrap_err();
510        let message = match error {
511            Error::ParseError(string) => string,
512            _ => panic!(),
513        };
514        assert_eq!(message, "Unknown attribute in item element.".to_owned());
515    }
516
517    #[test]
518    fn test_item_affiliation_role_attr(){
519        let elem: Element = "
520            <item xmlns='http://jabber.org/protocol/muc#user'
521                  affiliation='member'
522                  role='moderator'/>
523        ".parse().unwrap();
524        Item::try_from(elem).unwrap();
525    }
526
527    #[test]
528    fn test_item_affiliation_role_invalid_attr(){
529        let elem: Element = "
530            <item xmlns='http://jabber.org/protocol/muc#user'
531                  affiliation='member'/>
532        ".parse().unwrap();
533        let error = Item::try_from(elem).unwrap_err();
534        let message = match error {
535            Error::ParseError(string) => string,
536            _ => panic!(),
537        };
538        assert_eq!(message, "Required attribute 'role' missing.".to_owned());
539    }
540
541    #[test]
542    fn test_item_nick_attr(){
543        let elem: Element = "
544            <item xmlns='http://jabber.org/protocol/muc#user'
545                  affiliation='member'
546                  role='moderator'
547                  nick='foobar'/>
548        ".parse().unwrap();
549        let item = Item::try_from(elem).unwrap();
550        match item {
551            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
552        }
553    }
554
555    #[test]
556    fn test_item_affiliation_role_invalid_attr2(){
557        let elem: Element = "
558            <item xmlns='http://jabber.org/protocol/muc#user'
559                  role='moderator'/>
560        ".parse().unwrap();
561        let error = Item::try_from(elem).unwrap_err();
562        let message = match error {
563            Error::ParseError(string) => string,
564            _ => panic!(),
565        };
566        assert_eq!(message, "Required attribute 'affiliation' missing.".to_owned());
567    }
568
569    #[test]
570    fn test_item_role_actor_child(){
571        let elem: Element = "
572            <item xmlns='http://jabber.org/protocol/muc#user'
573                  affiliation='member'
574                  role='moderator'>
575                <actor nick='foobar'/>
576            </item>
577        ".parse().unwrap();
578        let item = Item::try_from(elem).unwrap();
579        match item {
580            Item { actor, .. } =>
581                assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
582        }
583    }
584
585    #[test]
586    fn test_item_role_continue_child(){
587        let elem: Element = "
588            <item xmlns='http://jabber.org/protocol/muc#user'
589                  affiliation='member'
590                  role='moderator'>
591                <continue thread='foobar'/>
592            </item>
593        ".parse().unwrap();
594        let item = Item::try_from(elem).unwrap();
595        let continue_1 = Continue { thread: Some("foobar".to_owned()) };
596        match item {
597            Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2.thread, continue_1.thread),
598            _ => panic!(),
599        }
600    }
601
602    #[test]
603    fn test_item_role_reason_child(){
604        let elem: Element = "
605            <item xmlns='http://jabber.org/protocol/muc#user'
606                  affiliation='member'
607                  role='moderator'>
608                <reason>foobar</reason>
609            </item>
610        ".parse().unwrap();
611        let item = Item::try_from(elem).unwrap();
612        match item {
613            Item { reason, .. } =>
614                assert_eq!(reason, Some(Reason("foobar".to_owned()))),
615        }
616    }
617}