MucOptions.java

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