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