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