1package eu.siacs.conversations.entities;
2
3import android.annotation.SuppressLint;
4
5import java.util.ArrayList;
6import java.util.List;
7import java.util.concurrent.CopyOnWriteArrayList;
8
9import eu.siacs.conversations.R;
10import eu.siacs.conversations.crypto.PgpEngine;
11import eu.siacs.conversations.xml.Element;
12import eu.siacs.conversations.xmpp.jid.InvalidJidException;
13import eu.siacs.conversations.xmpp.jid.Jid;
14import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
15
16@SuppressLint("DefaultLocale")
17public class MucOptions {
18
19 public enum Affiliation {
20 OWNER("owner", 4, R.string.owner),
21 ADMIN("admin", 3, R.string.admin),
22 MEMBER("member", 2, R.string.member),
23 OUTCAST("outcast", 0, R.string.outcast),
24 NONE("none", 1, R.string.no_affiliation);
25
26 Affiliation(String string, int rank, int resId) {
27 this.string = string;
28 this.resId = resId;
29 this.rank = rank;
30 }
31
32 private String string;
33 private int resId;
34 private int rank;
35
36 public int getResId() {
37 return resId;
38 }
39
40 @Override
41 public String toString() {
42 return this.string;
43 }
44
45 public boolean outranks(Affiliation affiliation) {
46 return rank > affiliation.rank;
47 }
48
49 public boolean ranks(Affiliation affiliation) {
50 return rank >= affiliation.rank;
51 }
52 }
53
54 public enum Role {
55 MODERATOR("moderator", R.string.moderator,3),
56 VISITOR("visitor", R.string.visitor,1),
57 PARTICIPANT("participant", R.string.participant,2),
58 NONE("none", R.string.no_role,0);
59
60 private Role(String string, int resId, int rank) {
61 this.string = string;
62 this.resId = resId;
63 this.rank = rank;
64 }
65
66 private String string;
67 private int resId;
68 private int rank;
69
70 public int getResId() {
71 return resId;
72 }
73
74 @Override
75 public String toString() {
76 return this.string;
77 }
78
79 public boolean ranks(Role role) {
80 return rank >= role.rank;
81 }
82 }
83
84 public static final int ERROR_NO_ERROR = 0;
85 public static final int ERROR_NICK_IN_USE = 1;
86 public static final int ERROR_UNKNOWN = 2;
87 public static final int ERROR_PASSWORD_REQUIRED = 3;
88 public static final int ERROR_BANNED = 4;
89 public static final int ERROR_MEMBERS_ONLY = 5;
90
91 public static final int KICKED_FROM_ROOM = 9;
92
93 public static final String STATUS_CODE_ROOM_CONFIG_CHANGED = "104";
94 public static final String STATUS_CODE_SELF_PRESENCE = "110";
95 public static final String STATUS_CODE_BANNED = "301";
96 public static final String STATUS_CODE_CHANGED_NICK = "303";
97 public static final String STATUS_CODE_KICKED = "307";
98 public static final String STATUS_CODE_LOST_MEMBERSHIP = "321";
99
100 private interface OnEventListener {
101 public void onSuccess();
102
103 public void onFailure();
104 }
105
106 public interface OnRenameListener extends OnEventListener {
107
108 }
109
110 public interface OnJoinListener extends OnEventListener {
111
112 }
113
114 public class User {
115 private Role role = Role.NONE;
116 private Affiliation affiliation = Affiliation.NONE;
117 private String name;
118 private Jid jid;
119 private long pgpKeyId = 0;
120
121 public String getName() {
122 return name;
123 }
124
125 public void setName(String user) {
126 this.name = user;
127 }
128
129 public void setJid(Jid jid) {
130 this.jid = jid;
131 }
132
133 public Jid getJid() {
134 return this.jid;
135 }
136
137 public Role getRole() {
138 return this.role;
139 }
140
141 public void setRole(String role) {
142 role = role.toLowerCase();
143 switch (role) {
144 case "moderator":
145 this.role = Role.MODERATOR;
146 break;
147 case "participant":
148 this.role = Role.PARTICIPANT;
149 break;
150 case "visitor":
151 this.role = Role.VISITOR;
152 break;
153 default:
154 this.role = Role.NONE;
155 break;
156 }
157 }
158
159 @Override
160 public boolean equals(Object other) {
161 if (this == other) {
162 return true;
163 } else if (!(other instanceof User)) {
164 return false;
165 } else {
166 User o = (User) other;
167 return name != null && name.equals(o.name)
168 && jid != null && jid.equals(o.jid)
169 && affiliation == o.affiliation
170 && role == o.role;
171 }
172 }
173
174 public Affiliation getAffiliation() {
175 return this.affiliation;
176 }
177
178 public void setAffiliation(String affiliation) {
179 affiliation = affiliation.toLowerCase();
180 switch (affiliation) {
181 case "admin":
182 this.affiliation = Affiliation.ADMIN;
183 break;
184 case "owner":
185 this.affiliation = Affiliation.OWNER;
186 break;
187 case "member":
188 this.affiliation = Affiliation.MEMBER;
189 break;
190 case "outcast":
191 this.affiliation = Affiliation.OUTCAST;
192 break;
193 default:
194 this.affiliation = Affiliation.NONE;
195 }
196 }
197
198 public void setPgpKeyId(long id) {
199 this.pgpKeyId = id;
200 }
201
202 public long getPgpKeyId() {
203 return this.pgpKeyId;
204 }
205
206 public Contact getContact() {
207 return account.getRoster().getContactFromRoster(getJid());
208 }
209 }
210
211 private Account account;
212 private List<User> users = new CopyOnWriteArrayList<>();
213 private List<String> features = new ArrayList<>();
214 private Conversation conversation;
215 private boolean isOnline = false;
216 private int error = ERROR_UNKNOWN;
217 private OnRenameListener onRenameListener = null;
218 private OnJoinListener onJoinListener = null;
219 private User self = new User();
220 private String subject = null;
221 private String password = null;
222 private boolean mNickChangingInProgress = false;
223
224 public MucOptions(Conversation conversation) {
225 this.account = conversation.getAccount();
226 this.conversation = conversation;
227 }
228
229 public void updateFeatures(ArrayList<String> features) {
230 this.features.clear();
231 this.features.addAll(features);
232 }
233
234 public boolean hasFeature(String feature) {
235 return this.features.contains(feature);
236 }
237
238 public boolean canInvite() {
239 return !membersOnly() || self.getAffiliation().ranks(Affiliation.ADMIN);
240 }
241
242 public boolean participating() {
243 return !online() || self.getRole().ranks(Role.PARTICIPANT);
244 }
245
246 public boolean membersOnly() {
247 return hasFeature("muc_membersonly");
248 }
249
250 public boolean nonanonymous() {
251 return hasFeature("muc_nonanonymous");
252 }
253
254 public boolean persistent() {
255 return hasFeature("muc_persistent");
256 }
257
258 public boolean moderated() {
259 return hasFeature("muc_moderated");
260 }
261
262 public void deleteUser(String name) {
263 for (int i = 0; i < users.size(); ++i) {
264 if (users.get(i).getName().equals(name)) {
265 users.remove(i);
266 return;
267 }
268 }
269 }
270
271 public void addUser(User user) {
272 for (int i = 0; i < users.size(); ++i) {
273 if (users.get(i).getName().equals(user.getName())) {
274 users.set(i, user);
275 return;
276 }
277 }
278 users.add(user);
279 }
280
281 public boolean isUserInRoom(String name) {
282 for (int i = 0; i < users.size(); ++i) {
283 if (users.get(i).getName().equals(name)) {
284 return true;
285 }
286 }
287 return false;
288 }
289
290 public void processPacket(PresencePacket packet, PgpEngine pgp) {
291 final Jid from = packet.getFrom();
292 if (!from.isBareJid()) {
293 final String name = from.getResourcepart();
294 final String type = packet.getAttribute("type");
295 final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
296 final List<String> codes = getStatusCodes(x);
297 if (type == null) {
298 User user = new User();
299 if (x != null) {
300 Element item = x.findChild("item");
301 if (item != null && name != null) {
302 user.setName(name);
303 user.setAffiliation(item.getAttribute("affiliation"));
304 user.setRole(item.getAttribute("role"));
305 user.setJid(item.getAttributeAsJid("jid"));
306 if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) {
307 this.isOnline = true;
308 this.error = ERROR_NO_ERROR;
309 self = user;
310 if (mNickChangingInProgress) {
311 if (onRenameListener != null) {
312 onRenameListener.onSuccess();
313 }
314 mNickChangingInProgress = false;
315 } else if (this.onJoinListener != null) {
316 this.onJoinListener.onSuccess();
317 this.onJoinListener = null;
318 }
319 } else {
320 addUser(user);
321 }
322 if (pgp != null) {
323 Element signed = packet.findChild("x", "jabber:x:signed");
324 if (signed != null) {
325 Element status = packet.findChild("status");
326 String msg;
327 if (status != null) {
328 msg = status.getContent();
329 } else {
330 msg = "";
331 }
332 user.setPgpKeyId(pgp.fetchKeyId(account, msg,
333 signed.getContent()));
334 }
335 }
336 }
337 }
338 } else if (type.equals("unavailable")) {
339 if (codes.contains(STATUS_CODE_SELF_PRESENCE) ||
340 packet.getFrom().equals(this.conversation.getJid())) {
341 if (codes.contains(STATUS_CODE_CHANGED_NICK)) {
342 this.mNickChangingInProgress = true;
343 } else if (codes.contains(STATUS_CODE_KICKED)) {
344 setError(KICKED_FROM_ROOM);
345 } else if (codes.contains(STATUS_CODE_BANNED)) {
346 setError(ERROR_BANNED);
347 } else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) {
348 setError(ERROR_MEMBERS_ONLY);
349 } else {
350 setError(ERROR_UNKNOWN);
351 }
352 } else {
353 deleteUser(name);
354 }
355 } else if (type.equals("error")) {
356 Element error = packet.findChild("error");
357 if (error != null && error.hasChild("conflict")) {
358 if (isOnline) {
359 if (onRenameListener != null) {
360 onRenameListener.onFailure();
361 }
362 } else {
363 setError(ERROR_NICK_IN_USE);
364 }
365 } else if (error != null && error.hasChild("not-authorized")) {
366 setError(ERROR_PASSWORD_REQUIRED);
367 } else if (error != null && error.hasChild("forbidden")) {
368 setError(ERROR_BANNED);
369 } else if (error != null && error.hasChild("registration-required")) {
370 setError(ERROR_MEMBERS_ONLY);
371 }
372 }
373 }
374 }
375
376 private void setError(int error) {
377 this.isOnline = false;
378 this.error = error;
379 if (onJoinListener != null) {
380 onJoinListener.onFailure();
381 onJoinListener = null;
382 }
383 }
384
385 private List<String> getStatusCodes(Element x) {
386 List<String> codes = new ArrayList<>();
387 if (x != null) {
388 for (Element child : x.getChildren()) {
389 if (child.getName().equals("status")) {
390 String code = child.getAttribute("code");
391 if (code != null) {
392 codes.add(code);
393 }
394 }
395 }
396 }
397 return codes;
398 }
399
400 public List<User> getUsers() {
401 return this.users;
402 }
403
404 public String getProposedNick() {
405 if (conversation.getBookmark() != null
406 && conversation.getBookmark().getNick() != null
407 && !conversation.getBookmark().getNick().isEmpty()) {
408 return conversation.getBookmark().getNick();
409 } else if (!conversation.getJid().isBareJid()) {
410 return conversation.getJid().getResourcepart();
411 } else {
412 return account.getUsername();
413 }
414 }
415
416 public String getActualNick() {
417 if (this.self.getName() != null) {
418 return this.self.getName();
419 } else {
420 return this.getProposedNick();
421 }
422 }
423
424 public boolean online() {
425 return this.isOnline;
426 }
427
428 public int getError() {
429 return this.error;
430 }
431
432 public void setOnRenameListener(OnRenameListener listener) {
433 this.onRenameListener = listener;
434 }
435
436 public void setOnJoinListener(OnJoinListener listener) {
437 this.onJoinListener = listener;
438 }
439
440 public void setOffline() {
441 this.users.clear();
442 this.error = 0;
443 this.isOnline = false;
444 }
445
446 public User getSelf() {
447 return self;
448 }
449
450 public void setSubject(String content) {
451 this.subject = content;
452 }
453
454 public String getSubject() {
455 return this.subject;
456 }
457
458 public String createNameFromParticipants() {
459 if (users.size() >= 2) {
460 List<String> names = new ArrayList<String>();
461 for (User user : users) {
462 Contact contact = user.getContact();
463 if (contact != null && !contact.getDisplayName().isEmpty()) {
464 names.add(contact.getDisplayName().split("\\s+")[0]);
465 } else {
466 names.add(user.getName());
467 }
468 }
469 StringBuilder builder = new StringBuilder();
470 for (int i = 0; i < names.size(); ++i) {
471 builder.append(names.get(i));
472 if (i != names.size() - 1) {
473 builder.append(", ");
474 }
475 }
476 return builder.toString();
477 } else {
478 return null;
479 }
480 }
481
482 public long[] getPgpKeyIds() {
483 List<Long> ids = new ArrayList<>();
484 for (User user : getUsers()) {
485 if (user.getPgpKeyId() != 0) {
486 ids.add(user.getPgpKeyId());
487 }
488 }
489 long[] primitivLongArray = new long[ids.size()];
490 for (int i = 0; i < ids.size(); ++i) {
491 primitivLongArray[i] = ids.get(i);
492 }
493 return primitivLongArray;
494 }
495
496 public boolean pgpKeysInUse() {
497 for (User user : getUsers()) {
498 if (user.getPgpKeyId() != 0) {
499 return true;
500 }
501 }
502 return false;
503 }
504
505 public boolean everybodyHasKeys() {
506 for (User user : getUsers()) {
507 if (user.getPgpKeyId() == 0) {
508 return false;
509 }
510 }
511 return true;
512 }
513
514 public Jid createJoinJid(String nick) {
515 try {
516 return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
517 } catch (final InvalidJidException e) {
518 return null;
519 }
520 }
521
522 public Jid getTrueCounterpart(String counterpart) {
523 for (User user : this.getUsers()) {
524 if (user.getName().equals(counterpart)) {
525 return user.getJid();
526 }
527 }
528 return null;
529 }
530
531 public String getPassword() {
532 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
533 if (this.password == null && conversation.getBookmark() != null
534 && conversation.getBookmark().getPassword() != null) {
535 return conversation.getBookmark().getPassword();
536 } else {
537 return this.password;
538 }
539 }
540
541 public void setPassword(String password) {
542 if (conversation.getBookmark() != null) {
543 conversation.getBookmark().setPassword(password);
544 } else {
545 this.password = password;
546 }
547 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
548 }
549
550 public Conversation getConversation() {
551 return this.conversation;
552 }
553}