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