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