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}