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