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