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