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