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