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