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};
  9
 10use minidom::{Element, IntoAttributeValue};
 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", ns::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/// Possesses a 'jid' and a 'nick' attribute, so that an action can be attributed either to a real
 84/// JID or to a roomnick. -- CHANGELOG  1.25 (2012-02-08)
 85#[derive(Debug, Clone, PartialEq)]
 86pub enum Actor {
 87    Jid(Jid),
 88    Nick(String),
 89}
 90
 91impl TryFrom<Element> for Actor {
 92    type Err = Error;
 93
 94    fn try_from(elem: Element) -> Result<Actor, Error> {
 95        check_self!(elem, "actor", ns::MUC_USER);
 96        check_no_unknown_attributes!(elem, "actor", ["jid", "nick"]);
 97        for _ in elem.children() {
 98            return Err(Error::ParseError("Unknown child in actor element."));
 99        }
100        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
101        let nick = get_attr!(elem, "nick", optional);
102
103        match (jid, nick) {
104            (Some(_), Some(_))
105          | (None, None) =>
106                return Err(Error::ParseError("Either 'jid' or 'nick' attribute is required.")),
107            (Some(jid), _) => Ok(Actor::Jid(jid)),
108            (_, Some(nick)) => Ok(Actor::Nick(nick)),
109        }
110    }
111}
112
113impl From<Actor> for Element {
114    fn from(actor: Actor) -> Element {
115        let elem = Element::builder("actor").ns(ns::MUC_USER);
116
117        (match actor {
118            Actor::Jid(jid) => elem.attr("jid", jid),
119            Actor::Nick(nick) => elem.attr("nick", nick),
120        }).build()
121    }
122}
123
124generate_element_with_only_attributes!(Continue, "continue", ns::MUC_USER, [
125    thread: Option<String> = "thread" => optional,
126]);
127
128generate_elem_id!(Reason, "reason", ns::MUC_USER);
129
130generate_attribute!(Affiliation, "affiliation", {
131    Owner => "owner",
132    Admin => "admin",
133    Member => "member",
134    Outcast => "outcast",
135    None => "none",
136}, Default = None);
137
138generate_attribute!(Role, "role", {
139    Moderator => "moderator",
140    Participant => "participant",
141    Visitor => "visitor",
142    None => "none",
143}, Default = None);
144
145#[derive(Debug, Clone)]
146pub struct Item {
147    pub affiliation: Affiliation,
148    pub jid: Option<Jid>,
149    pub nick: Option<String>,
150    pub role: Role,
151    pub actor: Option<Actor>,
152    pub continue_: Option<Continue>,
153    pub reason: Option<Reason>,
154}
155
156impl TryFrom<Element> for Item {
157    type Err = Error;
158
159    fn try_from(elem: Element) -> Result<Item, Error> {
160        check_self!(elem, "item", ns::MUC_USER);
161        check_no_unknown_attributes!(elem, "item", ["affiliation", "jid", "nick", "role"]);
162        let mut actor: Option<Actor> = None;
163        let mut continue_: Option<Continue> = None;
164        let mut reason: Option<Reason> = None;
165        for child in elem.children() {
166            if child.is("actor", ns::MUC_USER) {
167                actor = Some(child.clone().try_into()?);
168            } else if child.is("continue", ns::MUC_USER) {
169                continue_ = Some(child.clone().try_into()?);
170            } else if child.is("reason", ns::MUC_USER) {
171                reason = Some(child.clone().try_into()?);
172            } else {
173                return Err(Error::ParseError("Unknown child in item element."));
174            }
175        }
176
177        let affiliation: Affiliation = get_attr!(elem, "affiliation", required);
178        let jid: Option<Jid> = get_attr!(elem, "jid", optional);
179        let nick: Option<String> = get_attr!(elem, "nick", optional);
180        let role: Role = get_attr!(elem, "role", required);
181
182        Ok(Item{
183            affiliation: affiliation,
184            jid: jid,
185            nick: nick,
186            role: role,
187            actor: actor,
188            continue_: continue_,
189            reason: reason,
190        })
191    }
192}
193
194impl From<Item> for Element {
195    fn from(item: Item) -> Element {
196        Element::builder("item")
197                .ns(ns::MUC_USER)
198                .attr("affiliation", item.affiliation)
199                .attr("jid", item.jid)
200                .attr("nick", item.nick)
201                .attr("role", item.role)
202                .append(item.actor)
203                .append(item.continue_)
204                .append(item.reason)
205                .build()
206    }
207}
208
209#[derive(Debug, Clone)]
210pub struct MucUser {
211    pub status: Vec<Status>,
212    pub items: Vec<Item>,
213}
214
215impl TryFrom<Element> for MucUser {
216    type Err = Error;
217
218    fn try_from(elem: Element) -> Result<MucUser, Error> {
219        check_self!(elem, "x", ns::MUC_USER);
220        check_no_attributes!(elem, "x");
221        let mut status = vec!();
222        let mut items = vec!();
223        for child in elem.children() {
224            if child.is("status", ns::MUC_USER) {
225                status.push(Status::try_from(child.clone())?);
226            } else if child.is("item", ns::MUC_USER) {
227                items.push(Item::try_from(child.clone())?);
228            } else {
229                return Err(Error::ParseError("Unknown child in x element."));
230            }
231        }
232        Ok(MucUser {
233            status,
234            items,
235        })
236    }
237}
238
239impl From<MucUser> for Element {
240    fn from(muc_user: MucUser) -> Element {
241        Element::builder("x")
242                .ns(ns::MUC_USER)
243                .append(muc_user.status)
244                .build()
245    }
246}
247
248#[cfg(test)]
249mod tests {
250    use super::*;
251    use std::error::Error as StdError;
252    use compare_elements::NamespaceAwareCompare;
253
254    #[test]
255    fn test_simple() {
256        let elem: Element = "
257            <x xmlns='http://jabber.org/protocol/muc#user'/>
258        ".parse().unwrap();
259        MucUser::try_from(elem).unwrap();
260    }
261
262    #[test]
263    fn test_invalid_child() {
264        let elem: Element = "
265            <x xmlns='http://jabber.org/protocol/muc#user'>
266                <coucou/>
267            </x>
268        ".parse().unwrap();
269        let error = MucUser::try_from(elem).unwrap_err();
270        let message = match error {
271            Error::ParseError(string) => string,
272            _ => panic!(),
273        };
274        assert_eq!(message, "Unknown child in x element.");
275    }
276
277    #[test]
278    fn test_serialise() {
279        let elem: Element = "
280            <x xmlns='http://jabber.org/protocol/muc#user'/>
281        ".parse().unwrap();
282        let muc = MucUser { status: vec!(), items: vec!() };
283        let elem2 = muc.into();
284        assert!(elem.compare_to(&elem2));
285    }
286
287    #[test]
288    fn test_invalid_attribute() {
289        let elem: Element = "
290            <x xmlns='http://jabber.org/protocol/muc#user' coucou=''/>
291        ".parse().unwrap();
292        let error = MucUser::try_from(elem).unwrap_err();
293        let message = match error {
294            Error::ParseError(string) => string,
295            _ => panic!(),
296        };
297        assert_eq!(message, "Unknown attribute in x element.");
298    }
299
300    #[test]
301    fn test_status_simple() {
302        let elem: Element = "
303            <status xmlns='http://jabber.org/protocol/muc#user' code='110'/>
304        ".parse().unwrap();
305        Status::try_from(elem).unwrap();
306    }
307
308    #[test]
309    fn test_status_invalid() {
310        let elem: Element = "
311            <status xmlns='http://jabber.org/protocol/muc#user'/>
312        ".parse().unwrap();
313        let error = Status::try_from(elem).unwrap_err();
314        let message = match error {
315            Error::ParseError(string) => string,
316            _ => panic!(),
317        };
318        assert_eq!(message, "Required attribute 'code' missing.");
319    }
320
321    #[test]
322    fn test_status_invalid_child() {
323        let elem: Element = "
324            <status xmlns='http://jabber.org/protocol/muc#user' code='110'>
325                <foo/>
326            </status>
327        ".parse().unwrap();
328        let error = Status::try_from(elem).unwrap_err();
329        let message = match error {
330            Error::ParseError(string) => string,
331            _ => panic!(),
332        };
333        assert_eq!(message, "Unknown child in status element.");
334    }
335
336    #[test]
337    fn test_status_simple_code() {
338        let elem: Element = "
339            <status xmlns='http://jabber.org/protocol/muc#user' code='307'/>
340        ".parse().unwrap();
341        let status = Status::try_from(elem).unwrap();
342        assert_eq!(status, Status::Kicked);
343    }
344
345    #[test]
346    fn test_status_invalid_code() {
347        let elem: Element = "
348            <status xmlns='http://jabber.org/protocol/muc#user' code='666'/>
349        ".parse().unwrap();
350        let error = Status::try_from(elem).unwrap_err();
351        let message = match error {
352            Error::ParseError(string) => string,
353            _ => panic!(),
354        };
355        assert_eq!(message, "Invalid status code value.");
356    }
357
358    #[test]
359    fn test_status_invalid_code2() {
360        let elem: Element = "
361            <status xmlns='http://jabber.org/protocol/muc#user' code='coucou'/>
362        ".parse().unwrap();
363        let error = Status::try_from(elem).unwrap_err();
364        let error = match error {
365            Error::ParseIntError(error) => error,
366            _ => panic!(),
367        };
368        assert_eq!(error.description(), "invalid digit found in string");
369    }
370
371    #[test]
372    fn test_actor_required_attributes() {
373        let elem: Element = "
374            <actor xmlns='http://jabber.org/protocol/muc#user'/>
375        ".parse().unwrap();
376        let error = Actor::try_from(elem).unwrap_err();
377        let message = match error {
378            Error::ParseError(string) => string,
379            _ => panic!(),
380        };
381        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
382    }
383
384    #[test]
385    fn test_actor_required_attributes2() {
386        let elem: Element = "
387            <actor xmlns='http://jabber.org/protocol/muc#user'
388                   jid='foo@bar/baz'
389                   nick='baz'/>
390        ".parse().unwrap();
391        let error = Actor::try_from(elem).unwrap_err();
392        let message = match error {
393            Error::ParseError(string) => string,
394            _ => panic!(),
395        };
396        assert_eq!(message, "Either 'jid' or 'nick' attribute is required.");
397    }
398
399    #[test]
400    fn test_actor_jid() {
401        let elem: Element = "
402            <actor xmlns='http://jabber.org/protocol/muc#user'
403                   jid='foo@bar/baz'/>
404        ".parse().unwrap();
405        let actor = Actor::try_from(elem).unwrap();
406        let jid = match actor {
407            Actor::Jid(jid) => jid,
408            _ => panic!(),
409        };
410        assert_eq!(jid, "foo@bar/baz".parse::<Jid>().unwrap());
411    }
412
413    #[test]
414    fn test_actor_nick() {
415        let elem: Element = "
416            <actor xmlns='http://jabber.org/protocol/muc#user' nick='baz'/>
417        ".parse().unwrap();
418        let actor = Actor::try_from(elem).unwrap();
419        let nick = match actor {
420            Actor::Nick(nick) => nick,
421            _ => panic!(),
422        };
423        assert_eq!(nick, "baz".to_owned());
424    }
425
426    #[test]
427    fn test_continue_simple() {
428        let elem: Element = "
429            <continue xmlns='http://jabber.org/protocol/muc#user'/>
430        ".parse().unwrap();
431        Continue::try_from(elem).unwrap();
432    }
433
434    #[test]
435    fn test_continue_thread_attribute() {
436        let elem: Element = "
437            <continue xmlns='http://jabber.org/protocol/muc#user'
438                      thread='foo'/>
439        ".parse().unwrap();
440        let continue_ = Continue::try_from(elem).unwrap();
441        assert_eq!(continue_.thread, Some("foo".to_owned()));
442    }
443
444    #[test]
445    fn test_continue_invalid() {
446        let elem: Element = "
447            <continue xmlns='http://jabber.org/protocol/muc#user'>
448                <foobar/>
449            </continue>
450        ".parse().unwrap();
451        let continue_ = Continue::try_from(elem).unwrap_err();
452        let message = match continue_ {
453            Error::ParseError(string) => string,
454            _ => panic!(),
455        };
456        assert_eq!(message, "Unknown child in continue element.".to_owned());
457    }
458
459    #[test]
460    fn test_reason_simple() {
461        let elem: Element = "
462            <reason xmlns='http://jabber.org/protocol/muc#user'>Reason</reason>"
463        .parse().unwrap();
464        let reason = Reason::try_from(elem).unwrap();
465        assert_eq!(reason.0, "Reason".to_owned());
466    }
467
468    #[test]
469    fn test_reason_invalid_attribute() {
470        let elem: Element = "
471            <reason xmlns='http://jabber.org/protocol/muc#user' foo='bar'/>
472        ".parse().unwrap();
473        let error = Reason::try_from(elem).unwrap_err();
474        let message = match error {
475            Error::ParseError(string) => string,
476            _ => panic!(),
477        };
478        assert_eq!(message, "Unknown attribute in reason element.".to_owned());
479    }
480
481    #[test]
482    fn test_reason_invalid() {
483        let elem: Element = "
484            <reason xmlns='http://jabber.org/protocol/muc#user'>
485                <foobar/>
486            </reason>
487        ".parse().unwrap();
488        let error = Reason::try_from(elem).unwrap_err();
489        let message = match error {
490            Error::ParseError(string) => string,
491            _ => panic!(),
492        };
493        assert_eq!(message, "Unknown child in reason element.".to_owned());
494    }
495
496    #[test]
497    fn test_item_invalid_attr(){
498        let elem: Element = "
499            <item xmlns='http://jabber.org/protocol/muc#user'
500                  foo='bar'/>
501        ".parse().unwrap();
502        let error = Item::try_from(elem).unwrap_err();
503        let message = match error {
504            Error::ParseError(string) => string,
505            _ => panic!(),
506        };
507        assert_eq!(message, "Unknown attribute in item element.".to_owned());
508    }
509
510    #[test]
511    fn test_item_affiliation_role_attr(){
512        let elem: Element = "
513            <item xmlns='http://jabber.org/protocol/muc#user'
514                  affiliation='member'
515                  role='moderator'/>
516        ".parse().unwrap();
517        Item::try_from(elem).unwrap();
518    }
519
520    #[test]
521    fn test_item_affiliation_role_invalid_attr(){
522        let elem: Element = "
523            <item xmlns='http://jabber.org/protocol/muc#user'
524                  affiliation='member'/>
525        ".parse().unwrap();
526        let error = Item::try_from(elem).unwrap_err();
527        let message = match error {
528            Error::ParseError(string) => string,
529            _ => panic!(),
530        };
531        assert_eq!(message, "Required attribute 'role' missing.".to_owned());
532    }
533
534    #[test]
535    fn test_item_nick_attr(){
536        let elem: Element = "
537            <item xmlns='http://jabber.org/protocol/muc#user'
538                  affiliation='member'
539                  role='moderator'
540                  nick='foobar'/>
541        ".parse().unwrap();
542        let item = Item::try_from(elem).unwrap();
543        match item {
544            Item { nick, .. } => assert_eq!(nick, Some("foobar".to_owned())),
545        }
546    }
547
548    #[test]
549    fn test_item_affiliation_role_invalid_attr2(){
550        let elem: Element = "
551            <item xmlns='http://jabber.org/protocol/muc#user'
552                  role='moderator'/>
553        ".parse().unwrap();
554        let error = Item::try_from(elem).unwrap_err();
555        let message = match error {
556            Error::ParseError(string) => string,
557            _ => panic!(),
558        };
559        assert_eq!(message, "Required attribute 'affiliation' missing.".to_owned());
560    }
561
562    #[test]
563    fn test_item_role_actor_child(){
564        let elem: Element = "
565            <item xmlns='http://jabber.org/protocol/muc#user'
566                  affiliation='member'
567                  role='moderator'>
568                <actor nick='foobar'/>
569            </item>
570        ".parse().unwrap();
571        let item = Item::try_from(elem).unwrap();
572        match item {
573            Item { actor, .. } =>
574                assert_eq!(actor, Some(Actor::Nick("foobar".to_owned()))),
575        }
576    }
577
578    #[test]
579    fn test_item_role_continue_child(){
580        let elem: Element = "
581            <item xmlns='http://jabber.org/protocol/muc#user'
582                  affiliation='member'
583                  role='moderator'>
584                <continue thread='foobar'/>
585            </item>
586        ".parse().unwrap();
587        let item = Item::try_from(elem).unwrap();
588        let continue_1 = Continue { thread: Some("foobar".to_owned()) };
589        match item {
590            Item { continue_: Some(continue_2), .. } => assert_eq!(continue_2.thread, continue_1.thread),
591            _ => panic!(),
592        }
593    }
594
595    #[test]
596    fn test_item_role_reason_child(){
597        let elem: Element = "
598            <item xmlns='http://jabber.org/protocol/muc#user'
599                  affiliation='member'
600                  role='moderator'>
601                <reason>foobar</reason>
602            </item>
603        ".parse().unwrap();
604        let item = Item::try_from(elem).unwrap();
605        match item {
606            Item { reason, .. } =>
607                assert_eq!(reason, Some(Reason("foobar".to_owned()))),
608        }
609    }
610}