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 @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 void processPacket(PresencePacket packet, PgpEngine pgp) {
268 final Jid from = packet.getFrom();
269 if (!from.isBareJid()) {
270 final String name = from.getResourcepart();
271 final String type = packet.getAttribute("type");
272 final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
273 final List<String> codes = getStatusCodes(x);
274 if (type == null) {
275 User user = new User();
276 if (x != null) {
277 Element item = x.findChild("item");
278 if (item != null && name != null) {
279 user.setName(name);
280 user.setAffiliation(item.getAttribute("affiliation"));
281 user.setRole(item.getAttribute("role"));
282 user.setJid(item.getAttributeAsJid("jid"));
283 if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) {
284 this.isOnline = true;
285 this.error = ERROR_NO_ERROR;
286 self = user;
287 if (mNickChangingInProgress) {
288 onRenameListener.onSuccess();
289 mNickChangingInProgress = false;
290 } else if (this.onJoinListener != null) {
291 this.onJoinListener.onSuccess();
292 this.onJoinListener = null;
293 }
294 } else {
295 addUser(user);
296 }
297 if (pgp != null) {
298 Element signed = packet.findChild("x", "jabber:x:signed");
299 if (signed != null) {
300 Element status = packet.findChild("status");
301 String msg;
302 if (status != null) {
303 msg = status.getContent();
304 } else {
305 msg = "";
306 }
307 user.setPgpKeyId(pgp.fetchKeyId(account, msg,
308 signed.getContent()));
309 }
310 }
311 }
312 }
313 } else if (type.equals("unavailable")) {
314 if (codes.contains(STATUS_CODE_SELF_PRESENCE) ||
315 packet.getFrom().equals(this.conversation.getJid())) {
316 if (codes.contains(STATUS_CODE_CHANGED_NICK)) {
317 this.mNickChangingInProgress = true;
318 } else if (codes.contains(STATUS_CODE_KICKED)) {
319 setError(KICKED_FROM_ROOM);
320 } else if (codes.contains(STATUS_CODE_BANNED)) {
321 setError(ERROR_BANNED);
322 } else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) {
323 setError(ERROR_MEMBERS_ONLY);
324 } else {
325 setError(ERROR_UNKNOWN);
326 }
327 } else {
328 deleteUser(name);
329 }
330 } else if (type.equals("error")) {
331 Element error = packet.findChild("error");
332 if (error != null && error.hasChild("conflict")) {
333 if (isOnline) {
334 if (onRenameListener != null) {
335 onRenameListener.onFailure();
336 }
337 } else {
338 setError(ERROR_NICK_IN_USE);
339 }
340 } else if (error != null && error.hasChild("not-authorized")) {
341 setError(ERROR_PASSWORD_REQUIRED);
342 } else if (error != null && error.hasChild("forbidden")) {
343 setError(ERROR_BANNED);
344 } else if (error != null && error.hasChild("registration-required")) {
345 setError(ERROR_MEMBERS_ONLY);
346 } else {
347 setError(ERROR_UNKNOWN);
348 }
349 }
350 }
351 }
352
353 private void setError(int error) {
354 this.isOnline = false;
355 this.error = error;
356 if (onJoinListener != null) {
357 onJoinListener.onFailure();
358 onJoinListener = null;
359 }
360 }
361
362 private List<String> getStatusCodes(Element x) {
363 List<String> codes = new ArrayList<>();
364 if (x != null) {
365 for (Element child : x.getChildren()) {
366 if (child.getName().equals("status")) {
367 String code = child.getAttribute("code");
368 if (code != null) {
369 codes.add(code);
370 }
371 }
372 }
373 }
374 return codes;
375 }
376
377 public List<User> getUsers() {
378 return this.users;
379 }
380
381 public String getProposedNick() {
382 if (conversation.getBookmark() != null
383 && conversation.getBookmark().getNick() != null
384 && !conversation.getBookmark().getNick().isEmpty()) {
385 return conversation.getBookmark().getNick();
386 } else if (!conversation.getJid().isBareJid()) {
387 return conversation.getJid().getResourcepart();
388 } else {
389 return account.getUsername();
390 }
391 }
392
393 public String getActualNick() {
394 if (this.self.getName() != null) {
395 return this.self.getName();
396 } else {
397 return this.getProposedNick();
398 }
399 }
400
401 public boolean online() {
402 return this.isOnline;
403 }
404
405 public int getError() {
406 return this.error;
407 }
408
409 public void setOnRenameListener(OnRenameListener listener) {
410 this.onRenameListener = listener;
411 }
412
413 public void setOnJoinListener(OnJoinListener listener) {
414 this.onJoinListener = listener;
415 }
416
417 public void setOffline() {
418 this.users.clear();
419 this.error = 0;
420 this.isOnline = false;
421 }
422
423 public User getSelf() {
424 return self;
425 }
426
427 public void setSubject(String content) {
428 this.subject = content;
429 }
430
431 public String getSubject() {
432 return this.subject;
433 }
434
435 public String createNameFromParticipants() {
436 if (users.size() >= 2) {
437 List<String> names = new ArrayList<String>();
438 for (User user : users) {
439 Contact contact = user.getContact();
440 if (contact != null && !contact.getDisplayName().isEmpty()) {
441 names.add(contact.getDisplayName().split("\\s+")[0]);
442 } else {
443 names.add(user.getName());
444 }
445 }
446 StringBuilder builder = new StringBuilder();
447 for (int i = 0; i < names.size(); ++i) {
448 builder.append(names.get(i));
449 if (i != names.size() - 1) {
450 builder.append(", ");
451 }
452 }
453 return builder.toString();
454 } else {
455 return null;
456 }
457 }
458
459 public long[] getPgpKeyIds() {
460 List<Long> ids = new ArrayList<>();
461 for (User user : getUsers()) {
462 if (user.getPgpKeyId() != 0) {
463 ids.add(user.getPgpKeyId());
464 }
465 }
466 long[] primitivLongArray = new long[ids.size()];
467 for (int i = 0; i < ids.size(); ++i) {
468 primitivLongArray[i] = ids.get(i);
469 }
470 return primitivLongArray;
471 }
472
473 public boolean pgpKeysInUse() {
474 for (User user : getUsers()) {
475 if (user.getPgpKeyId() != 0) {
476 return true;
477 }
478 }
479 return false;
480 }
481
482 public boolean everybodyHasKeys() {
483 for (User user : getUsers()) {
484 if (user.getPgpKeyId() == 0) {
485 return false;
486 }
487 }
488 return true;
489 }
490
491 public Jid createJoinJid(String nick) {
492 try {
493 return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
494 } catch (final InvalidJidException e) {
495 return null;
496 }
497 }
498
499 public Jid getTrueCounterpart(String counterpart) {
500 for (User user : this.getUsers()) {
501 if (user.getName().equals(counterpart)) {
502 return user.getJid();
503 }
504 }
505 return null;
506 }
507
508 public String getPassword() {
509 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
510 if (this.password == null && conversation.getBookmark() != null
511 && conversation.getBookmark().getPassword() != null) {
512 return conversation.getBookmark().getPassword();
513 } else {
514 return this.password;
515 }
516 }
517
518 public void setPassword(String password) {
519 if (conversation.getBookmark() != null) {
520 conversation.getBookmark().setPassword(password);
521 } else {
522 this.password = password;
523 }
524 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
525 }
526
527 public Conversation getConversation() {
528 return this.conversation;
529 }
530}