1 /*
2 * muc.c
3 * vim: expandtab:ts=4:sts=4:sw=4
4 *
5 * Copyright (C) 2012 - 2019 James Booth <boothj5@gmail.com>
6 *
7 * This file is part of Profanity.
8 *
9 * Profanity is free software: you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation, either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * Profanity is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with Profanity. If not, see <https://www.gnu.org/licenses/>.
21 *
22 * In addition, as a special exception, the copyright holders give permission to
23 * link the code of portions of this program with the OpenSSL library under
24 * certain conditions as described in each individual source file, and
25 * distribute linked combinations including the two.
26 *
27 * You must obey the GNU General Public License in all respects for all of the
28 * code used other than OpenSSL. If you modify file(s) with this exception, you
29 * may extend this exception to your version of the file(s), but you are not
30 * obligated to do so. If you do not wish to do so, delete this exception
31 * statement from your version. If you delete this exception statement from all
32 * source files in the program, then also delete it here.
33 *
34 */
35
36 #include "config.h"
37
38 #include <stdlib.h>
39 #include <string.h>
40 #include <assert.h>
41
42 #include <glib.h>
43
44 #include "common.h"
45 #include "tools/autocomplete.h"
46 #include "ui/ui.h"
47 #include "ui/window_list.h"
48 #include "xmpp/jid.h"
49 #include "xmpp/muc.h"
50 #include "xmpp/contact.h"
51
52 #ifdef HAVE_OMEMO
53 #include "omemo/omemo.h"
54 #endif
55
56 typedef struct _muc_room_t
57 {
58 char* room; // e.g. test@conference.server
59 char* nick; // e.g. Some User
60 muc_role_t role;
61 muc_affiliation_t affiliation;
62 char* password;
63 char* subject;
64 char* autocomplete_prefix;
65 gboolean pending_config;
66 GList* pending_broadcasts;
67 gboolean autojoin;
68 gboolean pending_nick_change;
69 GHashTable* roster;
70 GHashTable* members;
71 Autocomplete nick_ac;
72 Autocomplete jid_ac;
73 GHashTable* nick_changes;
74 gboolean roster_received;
75 muc_member_type_t member_type;
76 muc_anonymity_type_t anonymity_type;
77 } ChatRoom;
78
79 GHashTable* rooms = NULL;
80 GHashTable* invite_passwords = NULL;
81 Autocomplete invite_ac = NULL;
82 Autocomplete confservers_ac = NULL;
83
84 static void _free_room(ChatRoom* room);
85 static gint _compare_occupants(Occupant* a, Occupant* b);
86 static muc_role_t _role_from_string(const char* const role);
87 static muc_affiliation_t _affiliation_from_string(const char* const affiliation);
88 static char* _role_to_string(muc_role_t role);
89 static char* _affiliation_to_string(muc_affiliation_t affiliation);
90 static Occupant* _muc_occupant_new(const char* const nick, const char* const jid, muc_role_t role,
91 muc_affiliation_t affiliation, resource_presence_t presence, const char* const status);
92 static void _occupant_free(Occupant* occupant);
93
94 void
muc_init(void)95 muc_init(void)
96 {
97 invite_ac = autocomplete_new();
98 confservers_ac = autocomplete_new();
99 rooms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)_free_room);
100 invite_passwords = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
101 }
102
103 void
muc_close(void)104 muc_close(void)
105 {
106 autocomplete_free(invite_ac);
107 autocomplete_free(confservers_ac);
108 g_hash_table_destroy(rooms);
109 g_hash_table_destroy(invite_passwords);
110 rooms = NULL;
111 invite_passwords = NULL;
112 invite_ac = NULL;
113 confservers_ac = NULL;
114 }
115
116 void
muc_confserver_add(const char * const server)117 muc_confserver_add(const char* const server)
118 {
119 autocomplete_add(confservers_ac, server);
120 }
121
122 void
muc_invites_add(const char * const room,const char * const password)123 muc_invites_add(const char* const room, const char* const password)
124 {
125 autocomplete_add(invite_ac, room);
126 if (password) {
127 g_hash_table_replace(invite_passwords, strdup(room), strdup(password));
128 }
129 }
130
131 void
muc_invites_remove(const char * const room)132 muc_invites_remove(const char* const room)
133 {
134 autocomplete_remove(invite_ac, room);
135 g_hash_table_remove(invite_passwords, room);
136 }
137
138 gint
muc_invites_count(void)139 muc_invites_count(void)
140 {
141 return autocomplete_length(invite_ac);
142 }
143
144 GList*
muc_invites(void)145 muc_invites(void)
146 {
147 return autocomplete_create_list(invite_ac);
148 }
149
150 char*
muc_invite_password(const char * const room)151 muc_invite_password(const char* const room)
152 {
153 return g_hash_table_lookup(invite_passwords, room);
154 }
155
156 gboolean
muc_invites_contain(const char * const room)157 muc_invites_contain(const char* const room)
158 {
159 GList* invites = autocomplete_create_list(invite_ac);
160 GList* curr = invites;
161 while (curr) {
162 if (strcmp(curr->data, room) == 0) {
163 g_list_free_full(invites, g_free);
164 return TRUE;
165 } else {
166 curr = g_list_next(curr);
167 }
168 }
169 g_list_free_full(invites, g_free);
170
171 return FALSE;
172 }
173
174 void
muc_invites_reset_ac(void)175 muc_invites_reset_ac(void)
176 {
177 autocomplete_reset(invite_ac);
178 }
179
180 void
muc_confserver_reset_ac(void)181 muc_confserver_reset_ac(void)
182 {
183 autocomplete_reset(confservers_ac);
184 }
185
186 char*
muc_invites_find(const char * const search_str,gboolean previous,void * context)187 muc_invites_find(const char* const search_str, gboolean previous, void* context)
188 {
189 return autocomplete_complete(invite_ac, search_str, TRUE, previous);
190 }
191
192 char*
muc_confserver_find(const char * const search_str,gboolean previous,void * context)193 muc_confserver_find(const char* const search_str, gboolean previous, void* context)
194 {
195 return autocomplete_complete(confservers_ac, search_str, TRUE, previous);
196 }
197
198 void
muc_invites_clear(void)199 muc_invites_clear(void)
200 {
201 autocomplete_clear(invite_ac);
202 if (invite_passwords) {
203 g_hash_table_remove_all(invite_passwords);
204 }
205 }
206
207 void
muc_confserver_clear(void)208 muc_confserver_clear(void)
209 {
210 autocomplete_clear(confservers_ac);
211 }
212
213 void
muc_join(const char * const room,const char * const nick,const char * const password,gboolean autojoin)214 muc_join(const char* const room, const char* const nick, const char* const password, gboolean autojoin)
215 {
216 ChatRoom* new_room = malloc(sizeof(ChatRoom));
217 new_room->room = strdup(room);
218 new_room->nick = strdup(nick);
219 new_room->role = MUC_ROLE_NONE;
220 new_room->affiliation = MUC_AFFILIATION_NONE;
221 new_room->autocomplete_prefix = NULL;
222 if (password) {
223 new_room->password = strdup(password);
224 } else {
225 new_room->password = NULL;
226 }
227 new_room->subject = NULL;
228 new_room->pending_broadcasts = NULL;
229 new_room->pending_config = FALSE;
230 new_room->roster = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)_occupant_free);
231 new_room->members = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
232 new_room->nick_ac = autocomplete_new();
233 new_room->jid_ac = autocomplete_new();
234 new_room->nick_changes = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
235 new_room->roster_received = FALSE;
236 new_room->pending_nick_change = FALSE;
237 new_room->autojoin = autojoin;
238 new_room->member_type = MUC_MEMBER_TYPE_UNKNOWN;
239 new_room->anonymity_type = MUC_ANONYMITY_TYPE_UNKNOWN;
240
241 g_hash_table_insert(rooms, strdup(room), new_room);
242 }
243
244 void
muc_leave(const char * const room)245 muc_leave(const char* const room)
246 {
247 g_hash_table_remove(rooms, room);
248 }
249
250 gboolean
muc_requires_config(const char * const room)251 muc_requires_config(const char* const room)
252 {
253 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
254 if (chat_room) {
255 return chat_room->pending_config;
256 } else {
257 return FALSE;
258 }
259 }
260
261 void
muc_set_requires_config(const char * const room,gboolean val)262 muc_set_requires_config(const char* const room, gboolean val)
263 {
264 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
265 if (chat_room) {
266 chat_room->pending_config = val;
267 }
268 }
269
270 void
muc_set_features(const char * const room,GSList * features)271 muc_set_features(const char* const room, GSList* features)
272 {
273 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
274 if (chat_room && features) {
275 if (g_slist_find_custom(features, "muc_membersonly", (GCompareFunc)g_strcmp0)) {
276 chat_room->member_type = MUC_MEMBER_TYPE_MEMBERS_ONLY;
277 } else {
278 chat_room->member_type = MUC_MEMBER_TYPE_PUBLIC;
279 }
280 if (g_slist_find_custom(features, "muc_nonanonymous", (GCompareFunc)g_strcmp0)) {
281 chat_room->anonymity_type = MUC_ANONYMITY_TYPE_NONANONYMOUS;
282 } else if (g_slist_find_custom(features, "muc_semianonymous", (GCompareFunc)g_strcmp0)) {
283 chat_room->anonymity_type = MUC_ANONYMITY_TYPE_SEMIANONYMOUS;
284 } else {
285 chat_room->anonymity_type = MUC_ANONYMITY_TYPE_UNKNOWN;
286 }
287 }
288 }
289
290 /*
291 * Returns TRUE if the user is currently in the room
292 */
293 gboolean
muc_active(const char * const room)294 muc_active(const char* const room)
295 {
296 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
297 return (chat_room != NULL);
298 }
299
300 gboolean
muc_autojoin(const char * const room)301 muc_autojoin(const char* const room)
302 {
303 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
304 if (chat_room) {
305 return chat_room->autojoin;
306 } else {
307 return FALSE;
308 }
309 }
310
311 void
muc_set_subject(const char * const room,const char * const subject)312 muc_set_subject(const char* const room, const char* const subject)
313 {
314 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
315 if (chat_room) {
316 free(chat_room->subject);
317 if (subject) {
318 chat_room->subject = strdup(subject);
319 } else {
320 chat_room->subject = NULL;
321 }
322 }
323 }
324
325 char*
muc_subject(const char * const room)326 muc_subject(const char* const room)
327 {
328 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
329 if (chat_room) {
330 return chat_room->subject;
331 } else {
332 return NULL;
333 }
334 }
335
336 void
muc_pending_broadcasts_add(const char * const room,const char * const message)337 muc_pending_broadcasts_add(const char* const room, const char* const message)
338 {
339 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
340 if (chat_room) {
341 chat_room->pending_broadcasts = g_list_append(chat_room->pending_broadcasts, strdup(message));
342 }
343 }
344
345 GList*
muc_pending_broadcasts(const char * const room)346 muc_pending_broadcasts(const char* const room)
347 {
348 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
349 if (chat_room) {
350 return chat_room->pending_broadcasts;
351 } else {
352 return NULL;
353 }
354 }
355
356 char*
muc_old_nick(const char * const room,const char * const new_nick)357 muc_old_nick(const char* const room, const char* const new_nick)
358 {
359 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
360 if (chat_room && chat_room->pending_nick_change) {
361 return g_hash_table_lookup(chat_room->nick_changes, new_nick);
362 } else {
363 return NULL;
364 }
365 }
366
367 /*
368 * Flag that the user has sent a nick change to the service
369 * and is awaiting the response
370 */
371 void
muc_nick_change_start(const char * const room,const char * const new_nick)372 muc_nick_change_start(const char* const room, const char* const new_nick)
373 {
374 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
375 if (chat_room) {
376 chat_room->pending_nick_change = TRUE;
377 g_hash_table_insert(chat_room->nick_changes, strdup(new_nick), strdup(chat_room->nick));
378 }
379 }
380
381 /*
382 * Returns TRUE if the room is awaiting the result of a
383 * nick change
384 */
385 gboolean
muc_nick_change_pending(const char * const room)386 muc_nick_change_pending(const char* const room)
387 {
388 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
389 if (chat_room) {
390 return chat_room->pending_nick_change;
391 } else {
392 return FALSE;
393 }
394 }
395
396 /*
397 * Change the current nick name for the room, call once
398 * the service has responded
399 */
400 void
muc_nick_change_complete(const char * const room,const char * const nick)401 muc_nick_change_complete(const char* const room, const char* const nick)
402 {
403 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
404 if (chat_room) {
405 g_hash_table_remove(chat_room->roster, chat_room->nick);
406 autocomplete_remove(chat_room->nick_ac, chat_room->nick);
407 free(chat_room->nick);
408 chat_room->nick = strdup(nick);
409 chat_room->pending_nick_change = FALSE;
410 g_hash_table_remove(chat_room->nick_changes, nick);
411 }
412 }
413
414 /*
415 * Return a list of room names
416 * The contents of the list are owned by the chat room and should not be
417 * modified or freed.
418 */
419 GList*
muc_rooms(void)420 muc_rooms(void)
421 {
422 return g_hash_table_get_keys(rooms);
423 }
424
425 /*
426 * Return current users nickname for the specified room
427 * The nickname is owned by the chat room and should not be modified or freed
428 */
429 char*
muc_nick(const char * const room)430 muc_nick(const char* const room)
431 {
432 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
433 if (chat_room) {
434 return chat_room->nick;
435 } else {
436 return NULL;
437 }
438 }
439
440 /*
441 * Return password for the specified room
442 * The password is owned by the chat room and should not be modified or freed
443 */
444 char*
muc_password(const char * const room)445 muc_password(const char* const room)
446 {
447 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
448 if (chat_room) {
449 return chat_room->password;
450 } else {
451 return NULL;
452 }
453 }
454
455 /*
456 * Returns TRUE if the specified nick exists in the room's roster
457 */
458 gboolean
muc_roster_contains_nick(const char * const room,const char * const nick)459 muc_roster_contains_nick(const char* const room, const char* const nick)
460 {
461 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
462 if (chat_room) {
463 Occupant* occupant = g_hash_table_lookup(chat_room->roster, nick);
464 return (occupant != NULL);
465 } else {
466 return FALSE;
467 }
468 }
469
470 /*
471 * Add a new chat room member to the room's roster
472 */
473 gboolean
muc_roster_add(const char * const room,const char * const nick,const char * const jid,const char * const role,const char * const affiliation,const char * const show,const char * const status)474 muc_roster_add(const char* const room, const char* const nick, const char* const jid, const char* const role,
475 const char* const affiliation, const char* const show, const char* const status)
476 {
477 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
478 gboolean updated = FALSE;
479 resource_presence_t new_presence = resource_presence_from_string(show);
480
481 if (chat_room) {
482 Occupant* old = g_hash_table_lookup(chat_room->roster, nick);
483
484 if (!old) {
485 updated = TRUE;
486 autocomplete_add(chat_room->nick_ac, nick);
487 } else if (old->presence != new_presence || (g_strcmp0(old->status, status) != 0)) {
488 updated = TRUE;
489 }
490
491 resource_presence_t presence = resource_presence_from_string(show);
492 muc_role_t role_t = _role_from_string(role);
493 muc_affiliation_t affiliation_t = _affiliation_from_string(affiliation);
494 Occupant* occupant = _muc_occupant_new(nick, jid, role_t, affiliation_t, presence, status);
495 g_hash_table_replace(chat_room->roster, strdup(nick), occupant);
496
497 if (jid) {
498 Jid* jidp = jid_create(jid);
499 if (jidp->barejid) {
500 autocomplete_add(chat_room->jid_ac, jidp->barejid);
501 }
502 jid_destroy(jidp);
503 }
504 }
505
506 return updated;
507 }
508
509 /*
510 * Remove a room member from the room's roster
511 */
512 void
muc_roster_remove(const char * const room,const char * const nick)513 muc_roster_remove(const char* const room, const char* const nick)
514 {
515 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
516 if (chat_room) {
517 g_hash_table_remove(chat_room->roster, nick);
518 autocomplete_remove(chat_room->nick_ac, nick);
519 }
520 }
521
522 Occupant*
muc_roster_item(const char * const room,const char * const nick)523 muc_roster_item(const char* const room, const char* const nick)
524 {
525 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
526 if (chat_room) {
527 Occupant* occupant = g_hash_table_lookup(chat_room->roster, nick);
528 return occupant;
529 } else {
530 return NULL;
531 }
532 }
533
534 /*
535 * Return a list of PContacts representing the room members in the room's roster
536 */
537 GList*
muc_roster(const char * const room)538 muc_roster(const char* const room)
539 {
540 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
541 if (chat_room) {
542 GList* result = NULL;
543 GList* occupants = g_hash_table_get_values(chat_room->roster);
544
545 GList* curr = occupants;
546 while (curr) {
547 result = g_list_insert_sorted(result, curr->data, (GCompareFunc)_compare_occupants);
548 curr = g_list_next(curr);
549 }
550
551 g_list_free(occupants);
552
553 return result;
554 } else {
555 return NULL;
556 }
557 }
558
559 /*
560 * Return a Autocomplete representing the room member's in the roster
561 */
562 Autocomplete
muc_roster_ac(const char * const room)563 muc_roster_ac(const char* const room)
564 {
565 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
566 if (chat_room) {
567 return chat_room->nick_ac;
568 } else {
569 return NULL;
570 }
571 }
572
573 Autocomplete
muc_roster_jid_ac(const char * const room)574 muc_roster_jid_ac(const char* const room)
575 {
576 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
577 if (chat_room) {
578 return chat_room->jid_ac;
579 } else {
580 return NULL;
581 }
582 }
583
584 /*
585 * Set to TRUE when the rooms roster has been fully received
586 */
587 void
muc_roster_set_complete(const char * const room)588 muc_roster_set_complete(const char* const room)
589 {
590 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
591 if (chat_room) {
592 chat_room->roster_received = TRUE;
593 }
594 }
595
596 /*
597 * Returns TRUE id the rooms roster has been fully received
598 */
599 gboolean
muc_roster_complete(const char * const room)600 muc_roster_complete(const char* const room)
601 {
602 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
603 if (chat_room) {
604 return chat_room->roster_received;
605 } else {
606 return FALSE;
607 }
608 }
609
610 gboolean
muc_occupant_available(Occupant * occupant)611 muc_occupant_available(Occupant* occupant)
612 {
613 return (occupant->presence == RESOURCE_ONLINE || occupant->presence == RESOURCE_CHAT);
614 }
615
616 const char*
muc_occupant_affiliation_str(Occupant * occupant)617 muc_occupant_affiliation_str(Occupant* occupant)
618 {
619 return _affiliation_to_string(occupant->affiliation);
620 }
621
622 const char*
muc_occupant_role_str(Occupant * occupant)623 muc_occupant_role_str(Occupant* occupant)
624 {
625 return _role_to_string(occupant->role);
626 }
627
628 GSList*
muc_occupants_by_role(const char * const room,muc_role_t role)629 muc_occupants_by_role(const char* const room, muc_role_t role)
630 {
631 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
632 if (chat_room) {
633 GSList* result = NULL;
634 GHashTableIter iter;
635 gpointer key;
636 gpointer value;
637
638 g_hash_table_iter_init(&iter, chat_room->roster);
639 while (g_hash_table_iter_next(&iter, &key, &value)) {
640 Occupant* occupant = (Occupant*)value;
641 if (occupant->role == role) {
642 result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_occupants);
643 }
644 }
645 return result;
646 } else {
647 return NULL;
648 }
649 }
650
651 GSList*
muc_occupants_by_affiliation(const char * const room,muc_affiliation_t affiliation)652 muc_occupants_by_affiliation(const char* const room, muc_affiliation_t affiliation)
653 {
654 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
655 if (chat_room) {
656 GSList* result = NULL;
657 GHashTableIter iter;
658 gpointer key;
659 gpointer value;
660
661 g_hash_table_iter_init(&iter, chat_room->roster);
662 while (g_hash_table_iter_next(&iter, &key, &value)) {
663 Occupant* occupant = (Occupant*)value;
664 if (occupant->affiliation == affiliation) {
665 result = g_slist_insert_sorted(result, value, (GCompareFunc)_compare_occupants);
666 }
667 }
668 return result;
669 } else {
670 return NULL;
671 }
672 }
673
674 /*
675 * Remove the old_nick from the roster, and flag that a pending nickname change
676 * is in progress
677 */
678 void
muc_occupant_nick_change_start(const char * const room,const char * const new_nick,const char * const old_nick)679 muc_occupant_nick_change_start(const char* const room, const char* const new_nick, const char* const old_nick)
680 {
681 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
682 if (chat_room) {
683 g_hash_table_insert(chat_room->nick_changes, strdup(new_nick), strdup(old_nick));
684 muc_roster_remove(room, old_nick);
685 }
686 }
687
688 /*
689 * Complete the pending nick name change for a contact in the room's roster
690 * The new nick name will be added to the roster
691 * The old nick name will be returned in a new string which must be freed by
692 * the caller
693 */
694 char*
muc_roster_nick_change_complete(const char * const room,const char * const nick)695 muc_roster_nick_change_complete(const char* const room, const char* const nick)
696 {
697 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
698 if (chat_room) {
699 char* old_nick = g_hash_table_lookup(chat_room->nick_changes, nick);
700 if (old_nick) {
701 char* old_nick_cpy = strdup(old_nick);
702 g_hash_table_remove(chat_room->nick_changes, nick);
703
704 return old_nick_cpy;
705 }
706 }
707
708 return NULL;
709 }
710
711 char*
muc_autocomplete(ProfWin * window,const char * const input,gboolean previous)712 muc_autocomplete(ProfWin* window, const char* const input, gboolean previous)
713 {
714 if (window->type != WIN_MUC) {
715 return NULL;
716 }
717
718 ProfMucWin* mucwin = (ProfMucWin*)window;
719 assert(mucwin->memcheck == PROFMUCWIN_MEMCHECK);
720 ChatRoom* chat_room = g_hash_table_lookup(rooms, mucwin->roomjid);
721 if (chat_room == NULL || chat_room->nick_ac == NULL) {
722 return NULL;
723 }
724
725 const char* search_str = NULL;
726
727 gchar* last_space = g_strrstr(input, " ");
728 if (!last_space) {
729 search_str = input;
730 if (!chat_room->autocomplete_prefix) {
731 chat_room->autocomplete_prefix = strdup("");
732 }
733 } else {
734 search_str = last_space + 1;
735 if (!chat_room->autocomplete_prefix) {
736 chat_room->autocomplete_prefix = g_strndup(input, search_str - input);
737 }
738 }
739
740 char* result = autocomplete_complete(chat_room->nick_ac, search_str, FALSE, previous);
741 if (result == NULL) {
742 return NULL;
743 }
744
745 GString* replace_with = g_string_new(chat_room->autocomplete_prefix);
746 g_string_append(replace_with, result);
747
748 if (strlen(chat_room->autocomplete_prefix) == 0) {
749 g_string_append(replace_with, ": ");
750 }
751 g_free(result);
752 result = replace_with->str;
753 g_string_free(replace_with, FALSE);
754 return result;
755 }
756
757 void
muc_jid_autocomplete_reset(const char * const room)758 muc_jid_autocomplete_reset(const char* const room)
759 {
760 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
761 if (chat_room) {
762 if (chat_room->jid_ac) {
763 autocomplete_reset(chat_room->jid_ac);
764 }
765 }
766 }
767
768 void
muc_jid_autocomplete_add_all(const char * const room,GSList * jids)769 muc_jid_autocomplete_add_all(const char* const room, GSList* jids)
770 {
771 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
772 if (chat_room) {
773 if (chat_room->jid_ac) {
774 GSList* curr_jid = jids;
775 while (curr_jid) {
776 const char* jid = curr_jid->data;
777 Jid* jidp = jid_create(jid);
778 if (jidp) {
779 if (jidp->barejid) {
780 autocomplete_add(chat_room->jid_ac, jidp->barejid);
781 }
782 }
783 jid_destroy(jidp);
784 curr_jid = g_slist_next(curr_jid);
785 }
786 }
787 }
788 }
789
790 void
muc_autocomplete_reset(const char * const room)791 muc_autocomplete_reset(const char* const room)
792 {
793 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
794 if (chat_room) {
795 if (chat_room->nick_ac) {
796 autocomplete_reset(chat_room->nick_ac);
797 }
798
799 if (chat_room->autocomplete_prefix) {
800 free(chat_room->autocomplete_prefix);
801 chat_room->autocomplete_prefix = NULL;
802 }
803 }
804 }
805
806 char*
muc_role_str(const char * const room)807 muc_role_str(const char* const room)
808 {
809 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
810 if (chat_room) {
811 return _role_to_string(chat_room->role);
812 } else {
813 return "none";
814 }
815 }
816
817 void
muc_set_role(const char * const room,const char * const role)818 muc_set_role(const char* const room, const char* const role)
819 {
820 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
821 if (chat_room) {
822 chat_room->role = _role_from_string(role);
823 }
824 }
825
826 char*
muc_affiliation_str(const char * const room)827 muc_affiliation_str(const char* const room)
828 {
829 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
830 if (chat_room) {
831 return _affiliation_to_string(chat_room->affiliation);
832 } else {
833 return "none";
834 }
835 }
836
837 void
muc_set_affiliation(const char * const room,const char * const affiliation)838 muc_set_affiliation(const char* const room, const char* const affiliation)
839 {
840 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
841 if (chat_room) {
842 chat_room->affiliation = _affiliation_from_string(affiliation);
843 }
844 }
845
846 muc_member_type_t
muc_member_type(const char * const room)847 muc_member_type(const char* const room)
848 {
849 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
850 if (chat_room) {
851 return chat_room->member_type;
852 } else {
853 return MUC_MEMBER_TYPE_UNKNOWN;
854 }
855 }
856
857 muc_anonymity_type_t
muc_anonymity_type(const char * const room)858 muc_anonymity_type(const char* const room)
859 {
860 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
861 if (chat_room) {
862 return chat_room->anonymity_type;
863 } else {
864 return MUC_ANONYMITY_TYPE_UNKNOWN;
865 }
866 }
867
868 /*
869 * Return the list of jid affiliated as member in the room
870 */
871 GList*
muc_members(const char * const room)872 muc_members(const char* const room)
873 {
874 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
875 if (chat_room) {
876 return g_hash_table_get_keys(chat_room->members);
877 } else {
878 return NULL;
879 }
880 }
881
882 void
muc_members_add(const char * const room,const char * const jid)883 muc_members_add(const char* const room, const char* const jid)
884 {
885 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
886 if (chat_room) {
887 if (g_hash_table_insert(chat_room->members, strdup(jid), NULL)) {
888 #ifdef HAVE_OMEMO
889 if (chat_room->anonymity_type == MUC_ANONYMITY_TYPE_NONANONYMOUS) {
890 char* our_barejid = connection_get_barejid();
891 if (strcmp(jid, our_barejid) != 0) {
892 omemo_start_session(jid);
893 }
894 free(our_barejid);
895 }
896 #endif
897 }
898 }
899 }
900
901 void
muc_members_remove(const char * const room,const char * const jid)902 muc_members_remove(const char* const room, const char* const jid)
903 {
904 ChatRoom* chat_room = g_hash_table_lookup(rooms, room);
905 if (chat_room) {
906 g_hash_table_remove(chat_room->members, jid);
907 }
908 }
909
910 void
muc_members_update(const char * const room,const char * const jid,const char * const affiliation)911 muc_members_update(const char* const room, const char* const jid, const char* const affiliation)
912 {
913 if (strcmp(affiliation, "outcast") == 0 || strcmp(affiliation, "none") == 0) {
914 muc_members_remove(room, jid);
915 } else if (strcmp(affiliation, "member") == 0 || strcmp(affiliation, "admin") == 0 || strcmp(affiliation, "owner") == 0) {
916 muc_members_add(room, jid);
917 }
918 }
919
920 static void
_free_room(ChatRoom * room)921 _free_room(ChatRoom* room)
922 {
923 if (room) {
924 free(room->room);
925 free(room->nick);
926 free(room->subject);
927 free(room->password);
928 free(room->autocomplete_prefix);
929 if (room->roster) {
930 g_hash_table_destroy(room->roster);
931 }
932 if (room->members) {
933 g_hash_table_destroy(room->members);
934 }
935 autocomplete_free(room->nick_ac);
936 autocomplete_free(room->jid_ac);
937 if (room->nick_changes) {
938 g_hash_table_destroy(room->nick_changes);
939 }
940 if (room->pending_broadcasts) {
941 g_list_free_full(room->pending_broadcasts, free);
942 }
943 free(room);
944 }
945 }
946
947 static gint
_compare_occupants(Occupant * a,Occupant * b)948 _compare_occupants(Occupant* a, Occupant* b)
949 {
950 const char* utf8_str_a = a->nick_collate_key;
951 const char* utf8_str_b = b->nick_collate_key;
952
953 gint result = g_strcmp0(utf8_str_a, utf8_str_b);
954
955 return result;
956 }
957
958 static muc_role_t
_role_from_string(const char * const role)959 _role_from_string(const char* const role)
960 {
961 if (role) {
962 if (g_strcmp0(role, "visitor") == 0) {
963 return MUC_ROLE_VISITOR;
964 } else if (g_strcmp0(role, "participant") == 0) {
965 return MUC_ROLE_PARTICIPANT;
966 } else if (g_strcmp0(role, "moderator") == 0) {
967 return MUC_ROLE_MODERATOR;
968 } else {
969 return MUC_ROLE_NONE;
970 }
971 } else {
972 return MUC_ROLE_NONE;
973 }
974 }
975
976 static char*
_role_to_string(muc_role_t role)977 _role_to_string(muc_role_t role)
978 {
979 char* result = NULL;
980
981 switch (role) {
982 case MUC_ROLE_NONE:
983 result = "none";
984 break;
985 case MUC_ROLE_VISITOR:
986 result = "visitor";
987 break;
988 case MUC_ROLE_PARTICIPANT:
989 result = "participant";
990 break;
991 case MUC_ROLE_MODERATOR:
992 result = "moderator";
993 break;
994 default:
995 result = "none";
996 break;
997 }
998
999 return result;
1000 }
1001
1002 static muc_affiliation_t
_affiliation_from_string(const char * const affiliation)1003 _affiliation_from_string(const char* const affiliation)
1004 {
1005 if (affiliation) {
1006 if (g_strcmp0(affiliation, "outcast") == 0) {
1007 return MUC_AFFILIATION_OUTCAST;
1008 } else if (g_strcmp0(affiliation, "member") == 0) {
1009 return MUC_AFFILIATION_MEMBER;
1010 } else if (g_strcmp0(affiliation, "admin") == 0) {
1011 return MUC_AFFILIATION_ADMIN;
1012 } else if (g_strcmp0(affiliation, "owner") == 0) {
1013 return MUC_AFFILIATION_OWNER;
1014 } else {
1015 return MUC_AFFILIATION_NONE;
1016 }
1017 } else {
1018 return MUC_AFFILIATION_NONE;
1019 }
1020 }
1021
1022 static char*
_affiliation_to_string(muc_affiliation_t affiliation)1023 _affiliation_to_string(muc_affiliation_t affiliation)
1024 {
1025 char* result = NULL;
1026
1027 switch (affiliation) {
1028 case MUC_AFFILIATION_NONE:
1029 result = "none";
1030 break;
1031 case MUC_AFFILIATION_OUTCAST:
1032 result = "outcast";
1033 break;
1034 case MUC_AFFILIATION_MEMBER:
1035 result = "member";
1036 break;
1037 case MUC_AFFILIATION_ADMIN:
1038 result = "admin";
1039 break;
1040 case MUC_AFFILIATION_OWNER:
1041 result = "owner";
1042 break;
1043 default:
1044 result = "none";
1045 break;
1046 }
1047
1048 return result;
1049 }
1050
1051 static Occupant*
_muc_occupant_new(const char * const nick,const char * const jid,muc_role_t role,muc_affiliation_t affiliation,resource_presence_t presence,const char * const status)1052 _muc_occupant_new(const char* const nick, const char* const jid, muc_role_t role, muc_affiliation_t affiliation,
1053 resource_presence_t presence, const char* const status)
1054 {
1055 Occupant* occupant = malloc(sizeof(Occupant));
1056
1057 if (nick) {
1058 occupant->nick = strdup(nick);
1059 occupant->nick_collate_key = g_utf8_collate_key(occupant->nick, -1);
1060 } else {
1061 occupant->nick = NULL;
1062 occupant->nick_collate_key = NULL;
1063 }
1064
1065 if (jid) {
1066 occupant->jid = strdup(jid);
1067 } else {
1068 occupant->jid = NULL;
1069 }
1070
1071 occupant->presence = presence;
1072
1073 if (status) {
1074 occupant->status = strdup(status);
1075 } else {
1076 occupant->status = NULL;
1077 }
1078
1079 occupant->role = role;
1080 occupant->affiliation = affiliation;
1081
1082 return occupant;
1083 }
1084
1085 static void
_occupant_free(Occupant * occupant)1086 _occupant_free(Occupant* occupant)
1087 {
1088 if (occupant) {
1089 free(occupant->nick);
1090 free(occupant->nick_collate_key);
1091 free(occupant->jid);
1092 free(occupant->status);
1093 free(occupant);
1094 }
1095 }
1096