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 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 }
347 }
348 }
349 }
350
351 private void setError(int error) {
352 this.isOnline = false;
353 this.error = error;
354 if (onJoinListener != null) {
355 onJoinListener.onFailure();
356 onJoinListener = null;
357 }
358 }
359
360 private List<String> getStatusCodes(Element x) {
361 List<String> codes = new ArrayList<>();
362 if (x != null) {
363 for (Element child : x.getChildren()) {
364 if (child.getName().equals("status")) {
365 String code = child.getAttribute("code");
366 if (code != null) {
367 codes.add(code);
368 }
369 }
370 }
371 }
372 return codes;
373 }
374
375 public List<User> getUsers() {
376 return this.users;
377 }
378
379 public String getProposedNick() {
380 if (conversation.getBookmark() != null
381 && conversation.getBookmark().getNick() != null
382 && !conversation.getBookmark().getNick().isEmpty()) {
383 return conversation.getBookmark().getNick();
384 } else if (!conversation.getJid().isBareJid()) {
385 return conversation.getJid().getResourcepart();
386 } else {
387 return account.getUsername();
388 }
389 }
390
391 public String getActualNick() {
392 if (this.self.getName() != null) {
393 return this.self.getName();
394 } else {
395 return this.getProposedNick();
396 }
397 }
398
399 public boolean online() {
400 return this.isOnline;
401 }
402
403 public int getError() {
404 return this.error;
405 }
406
407 public void setOnRenameListener(OnRenameListener listener) {
408 this.onRenameListener = listener;
409 }
410
411 public void setOnJoinListener(OnJoinListener listener) {
412 this.onJoinListener = listener;
413 }
414
415 public void setOffline() {
416 this.users.clear();
417 this.error = 0;
418 this.isOnline = false;
419 }
420
421 public User getSelf() {
422 return self;
423 }
424
425 public void setSubject(String content) {
426 this.subject = content;
427 }
428
429 public String getSubject() {
430 return this.subject;
431 }
432
433 public String createNameFromParticipants() {
434 if (users.size() >= 2) {
435 List<String> names = new ArrayList<String>();
436 for (User user : users) {
437 Contact contact = user.getContact();
438 if (contact != null && !contact.getDisplayName().isEmpty()) {
439 names.add(contact.getDisplayName().split("\\s+")[0]);
440 } else {
441 names.add(user.getName());
442 }
443 }
444 StringBuilder builder = new StringBuilder();
445 for (int i = 0; i < names.size(); ++i) {
446 builder.append(names.get(i));
447 if (i != names.size() - 1) {
448 builder.append(", ");
449 }
450 }
451 return builder.toString();
452 } else {
453 return null;
454 }
455 }
456
457 public long[] getPgpKeyIds() {
458 List<Long> ids = new ArrayList<>();
459 for (User user : getUsers()) {
460 if (user.getPgpKeyId() != 0) {
461 ids.add(user.getPgpKeyId());
462 }
463 }
464 long[] primitivLongArray = new long[ids.size()];
465 for (int i = 0; i < ids.size(); ++i) {
466 primitivLongArray[i] = ids.get(i);
467 }
468 return primitivLongArray;
469 }
470
471 public boolean pgpKeysInUse() {
472 for (User user : getUsers()) {
473 if (user.getPgpKeyId() != 0) {
474 return true;
475 }
476 }
477 return false;
478 }
479
480 public boolean everybodyHasKeys() {
481 for (User user : getUsers()) {
482 if (user.getPgpKeyId() == 0) {
483 return false;
484 }
485 }
486 return true;
487 }
488
489 public Jid createJoinJid(String nick) {
490 try {
491 return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
492 } catch (final InvalidJidException e) {
493 return null;
494 }
495 }
496
497 public Jid getTrueCounterpart(String counterpart) {
498 for (User user : this.getUsers()) {
499 if (user.getName().equals(counterpart)) {
500 return user.getJid();
501 }
502 }
503 return null;
504 }
505
506 public String getPassword() {
507 this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
508 if (this.password == null && conversation.getBookmark() != null
509 && conversation.getBookmark().getPassword() != null) {
510 return conversation.getBookmark().getPassword();
511 } else {
512 return this.password;
513 }
514 }
515
516 public void setPassword(String password) {
517 if (conversation.getBookmark() != null) {
518 conversation.getBookmark().setPassword(password);
519 } else {
520 this.password = password;
521 }
522 conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
523 }
524
525 public Conversation getConversation() {
526 return this.conversation;
527 }
528}