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