1package eu.siacs.conversations.entities;
2
3import java.util.ArrayList;
4import java.util.List;
5import java.util.concurrent.CopyOnWriteArrayList;
6
7import eu.siacs.conversations.R;
8import eu.siacs.conversations.crypto.PgpEngine;
9import eu.siacs.conversations.xml.Element;
10import eu.siacs.conversations.xmpp.jid.InvalidJidException;
11import eu.siacs.conversations.xmpp.jid.Jid;
12import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
13
14import android.annotation.SuppressLint;
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 public Affiliation getAffiliation() {
154 return this.affiliation;
155 }
156
157 public void setAffiliation(String affiliation) {
158 affiliation = affiliation.toLowerCase();
159 switch (affiliation) {
160 case "admin":
161 this.affiliation = Affiliation.ADMIN;
162 break;
163 case "owner":
164 this.affiliation = Affiliation.OWNER;
165 break;
166 case "member":
167 this.affiliation = Affiliation.MEMBER;
168 break;
169 case "outcast":
170 this.affiliation = Affiliation.OUTCAST;
171 break;
172 default:
173 this.affiliation = Affiliation.NONE;
174 }
175 }
176
177 public void setPgpKeyId(long id) {
178 this.pgpKeyId = id;
179 }
180
181 public long getPgpKeyId() {
182 return this.pgpKeyId;
183 }
184
185 public Contact getContact() {
186 return account.getRoster().getContactFromRoster(getJid());
187 }
188 }
189
190 private Account account;
191 private List<User> users = new CopyOnWriteArrayList<>();
192 private List<String> features = new ArrayList<>();
193 private Conversation conversation;
194 private boolean isOnline = false;
195 private int error = ERROR_UNKNOWN;
196 private OnRenameListener onRenameListener = null;
197 private OnJoinListener onJoinListener = null;
198 private User self = new User();
199 private String subject = null;
200 private String password = null;
201 private boolean mNickChangingInProgress = false;
202
203 public MucOptions(Conversation conversation) {
204 this.account = conversation.getAccount();
205 this.conversation = conversation;
206 }
207
208 public void updateFeatures(ArrayList<String> features) {
209 this.features.clear();
210 this.features.addAll(features);
211 }
212
213 public boolean hasFeature(String feature) {
214 return this.features.contains(feature);
215 }
216
217 public boolean canInvite() {
218 return !membersOnly() || self.getAffiliation().ranks(Affiliation.ADMIN);
219 }
220
221 public boolean membersOnly() {
222 return hasFeature("muc_membersonly");
223 }
224
225 public boolean nonanonymous() {
226 return hasFeature("muc_nonanonymous");
227 }
228
229 public boolean persistent() {
230 return hasFeature("muc_persistent");
231 }
232
233 public void deleteUser(String name) {
234 for (int i = 0; i < users.size(); ++i) {
235 if (users.get(i).getName().equals(name)) {
236 users.remove(i);
237 return;
238 }
239 }
240 }
241
242 public void addUser(User user) {
243 for (int i = 0; i < users.size(); ++i) {
244 if (users.get(i).getName().equals(user.getName())) {
245 users.set(i, user);
246 return;
247 }
248 }
249 users.add(user);
250 }
251
252 public void processPacket(PresencePacket packet, PgpEngine pgp) {
253 final Jid from = packet.getFrom();
254 if (!from.isBareJid()) {
255 final String name = from.getResourcepart();
256 final String type = packet.getAttribute("type");
257 final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
258 final List<String> codes = getStatusCodes(x);
259 if (type == null) {
260 User user = new User();
261 if (x != null) {
262 Element item = x.findChild("item");
263 if (item != null) {
264 user.setName(name);
265 user.setAffiliation(item.getAttribute("affiliation"));
266 user.setRole(item.getAttribute("role"));
267 user.setJid(item.getAttributeAsJid("jid"));
268 if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) {
269 this.isOnline = true;
270 this.error = ERROR_NO_ERROR;
271 self = user;
272 if (mNickChangingInProgress) {
273 onRenameListener.onSuccess();
274 mNickChangingInProgress = false;
275 } else if (this.onJoinListener != null) {
276 this.onJoinListener.onSuccess();
277 this.onJoinListener = null;
278 }
279 } else {
280 addUser(user);
281 }
282 if (pgp != null) {
283 Element signed = packet.findChild("x", "jabber:x:signed");
284 if (signed != null) {
285 Element status = packet.findChild("status");
286 String msg;
287 if (status != null) {
288 msg = status.getContent();
289 } else {
290 msg = "";
291 }
292 user.setPgpKeyId(pgp.fetchKeyId(account, msg,
293 signed.getContent()));
294 }
295 }
296 }
297 }
298 } else if (type.equals("unavailable")) {
299 if (codes.contains(STATUS_CODE_SELF_PRESENCE) ||
300 packet.getFrom().equals(this.conversation.getJid())) {
301 if (codes.contains(STATUS_CODE_CHANGED_NICK)) {
302 this.mNickChangingInProgress = true;
303 } else if (codes.contains(STATUS_CODE_KICKED)) {
304 setError(KICKED_FROM_ROOM);
305 } else if (codes.contains(STATUS_CODE_BANNED)) {
306 setError(ERROR_BANNED);
307 } else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) {
308 setError(ERROR_MEMBERS_ONLY);
309 } else {
310 setError(ERROR_UNKNOWN);
311 }
312 } else {
313 deleteUser(name);
314 }
315 } else if (type.equals("error")) {
316 Element error = packet.findChild("error");
317 if (error != null && error.hasChild("conflict")) {
318 if (isOnline) {
319 if (onRenameListener != null) {
320 onRenameListener.onFailure();
321 }
322 } else {
323 setError(ERROR_NICK_IN_USE);
324 }
325 } else if (error != null && error.hasChild("not-authorized")) {
326 setError(ERROR_PASSWORD_REQUIRED);
327 } else if (error != null && error.hasChild("forbidden")) {
328 setError(ERROR_BANNED);
329 } else if (error != null && error.hasChild("registration-required")) {
330 setError(ERROR_MEMBERS_ONLY);
331 } else {
332 setError(ERROR_UNKNOWN);
333 }
334 }
335 }
336 }
337
338 private void setError(int error) {
339 this.isOnline = false;
340 this.error = error;
341 if (onJoinListener != null) {
342 onJoinListener.onFailure();
343 onJoinListener = null;
344 }
345 }
346
347 private List<String> getStatusCodes(Element x) {
348 List<String> codes = new ArrayList<>();
349 if (x != null) {
350 for (Element child : x.getChildren()) {
351 if (child.getName().equals("status")) {
352 String code = child.getAttribute("code");
353 if (code != null) {
354 codes.add(code);
355 }
356 }
357 }
358 }
359 return codes;
360 }
361
362 public List<User> getUsers() {
363 return this.users;
364 }
365
366 public String getProposedNick() {
367 if (conversation.getBookmark() != null
368 && conversation.getBookmark().getNick() != null
369 && !conversation.getBookmark().getNick().isEmpty()) {
370 return conversation.getBookmark().getNick();
371 } else if (!conversation.getJid().isBareJid()) {
372 return conversation.getJid().getResourcepart();
373 } else {
374 return account.getUsername();
375 }
376 }
377
378 public String getActualNick() {
379 if (this.self.getName() != null) {
380 return this.self.getName();
381 } else {
382 return this.getProposedNick();
383 }
384 }
385
386 public boolean online() {
387 return this.isOnline;
388 }
389
390 public int getError() {
391 return this.error;
392 }
393
394 public void setOnRenameListener(OnRenameListener listener) {
395 this.onRenameListener = listener;
396 }
397
398 public void setOnJoinListener(OnJoinListener listener) {
399 this.onJoinListener = listener;
400 }
401
402 public void setOffline() {
403 this.users.clear();
404 this.error = 0;
405 this.isOnline = false;
406 }
407
408 public User getSelf() {
409 return self;
410 }
411
412 public void setSubject(String content) {
413 this.subject = content;
414 }
415
416 public String getSubject() {
417 return this.subject;
418 }
419
420 public String createNameFromParticipants() {
421 if (users.size() >= 2) {
422 List<String> names = new ArrayList<String>();
423 for (User user : users) {
424 Contact contact = user.getContact();
425 if (contact != null && !contact.getDisplayName().isEmpty()) {
426 names.add(contact.getDisplayName().split("\\s+")[0]);
427 } else {
428 names.add(user.getName());
429 }
430 }
431 StringBuilder builder = new StringBuilder();
432 for (int i = 0; i < names.size(); ++i) {
433 builder.append(names.get(i));
434 if (i != names.size() - 1) {
435 builder.append(", ");
436 }
437 }
438 return builder.toString();
439 } else {
440 return null;
441 }
442 }
443
444 public long[] getPgpKeyIds() {
445 List<Long> ids = new ArrayList<>();
446 for (User user : getUsers()) {
447 if (user.getPgpKeyId() != 0) {
448 ids.add(user.getPgpKeyId());
449 }
450 }
451 long[] primitivLongArray = new long[ids.size()];
452 for (int i = 0; i < ids.size(); ++i) {
453 primitivLongArray[i] = ids.get(i);
454 }
455 return primitivLongArray;
456 }
457
458 public boolean pgpKeysInUse() {
459 for (User user : getUsers()) {
460 if (user.getPgpKeyId() != 0) {
461 return true;
462 }
463 }
464 return false;
465 }
466
467 public boolean everybodyHasKeys() {
468 for (User user : getUsers()) {
469 if (user.getPgpKeyId() == 0) {
470 return false;
471 }
472 }
473 return true;
474 }
475
476 public Jid createJoinJid(String nick) {
477 try {
478 return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
479 } catch (final InvalidJidException e) {
480 return null;
481 }
482 }
483
484 public Jid getTrueCounterpart(String counterpart) {
485 for (User user : this.getUsers()) {
486 if (user.getName().equals(counterpart)) {
487 return user.getJid();
488 }
489 }
490 return null;
491 }
492
493 public String getPassword() {
494 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
495 if (this.password == null && conversation.getBookmark() != null
496 && conversation.getBookmark().getPassword() != null) {
497 return conversation.getBookmark().getPassword();
498 } else {
499 return this.password;
500 }
501 }
502
503 public void setPassword(String password) {
504 if (conversation.getBookmark() != null) {
505 conversation.getBookmark().setPassword(password);
506 } else {
507 this.password = password;
508 }
509 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
510 }
511
512 public Conversation getConversation() {
513 return this.conversation;
514 }
515}