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