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