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 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		@Override
156		public boolean equals(Object other) {
157			if (this == other) {
158				return true;
159			} else if (!(other instanceof User)) {
160				return false;
161			} else {
162				User o = (User) other;
163				return name != null && name.equals(o.name)
164						&& jid != null && jid.equals(o.jid)
165						&& affiliation == o.affiliation
166						&& role == o.role;
167			}
168		}
169
170		public Affiliation getAffiliation() {
171			return this.affiliation;
172		}
173
174		public void setAffiliation(String affiliation) {
175			affiliation = affiliation.toLowerCase();
176			switch (affiliation) {
177				case "admin":
178					this.affiliation = Affiliation.ADMIN;
179					break;
180				case "owner":
181					this.affiliation = Affiliation.OWNER;
182					break;
183				case "member":
184					this.affiliation = Affiliation.MEMBER;
185					break;
186				case "outcast":
187					this.affiliation = Affiliation.OUTCAST;
188					break;
189				default:
190					this.affiliation = Affiliation.NONE;
191			}
192		}
193
194		public void setPgpKeyId(long id) {
195			this.pgpKeyId = id;
196		}
197
198		public long getPgpKeyId() {
199			return this.pgpKeyId;
200		}
201
202		public Contact getContact() {
203			return account.getRoster().getContactFromRoster(getJid());
204		}
205	}
206
207	private Account account;
208	private List<User> users = new CopyOnWriteArrayList<>();
209	private List<String> features = new ArrayList<>();
210	private Conversation conversation;
211	private boolean isOnline = false;
212	private int error = ERROR_UNKNOWN;
213	private OnRenameListener onRenameListener = null;
214	private User self = new User();
215	private String subject = null;
216	private String password = null;
217	private boolean mNickChangingInProgress = false;
218
219	public MucOptions(Conversation conversation) {
220		this.account = conversation.getAccount();
221		this.conversation = conversation;
222	}
223
224	public void updateFeatures(ArrayList<String> features) {
225		this.features.clear();
226		this.features.addAll(features);
227	}
228
229	public boolean hasFeature(String feature) {
230		return this.features.contains(feature);
231	}
232
233	public boolean canInvite() {
234		 return !membersOnly() || self.getAffiliation().ranks(Affiliation.ADMIN);
235	}
236
237	public boolean participating() {
238		return !online() || self.getRole().ranks(Role.PARTICIPANT);
239	}
240
241	public boolean membersOnly() {
242		return hasFeature("muc_membersonly");
243	}
244
245	public boolean mamSupport() {
246		// Update with "urn:xmpp:mam:1" once we support it
247		return hasFeature("urn:xmpp:mam:0");
248	}
249
250	public boolean nonanonymous() {
251		return hasFeature("muc_nonanonymous");
252	}
253
254	public boolean persistent() {
255		return hasFeature("muc_persistent");
256	}
257
258	public boolean moderated() {
259		return hasFeature("muc_moderated");
260	}
261
262	public void deleteUser(String name) {
263		for (int i = 0; i < users.size(); ++i) {
264			if (users.get(i).getName().equals(name)) {
265				users.remove(i);
266				return;
267			}
268		}
269	}
270
271	public void addUser(User user) {
272		for (int i = 0; i < users.size(); ++i) {
273			if (users.get(i).getName().equals(user.getName())) {
274				users.set(i, user);
275				return;
276			}
277		}
278		users.add(user);
279	}
280
281	public boolean isUserInRoom(String name) {
282		for (int i = 0; i < users.size(); ++i) {
283			if (users.get(i).getName().equals(name)) {
284				return true;
285			}
286		}
287		return false;
288	}
289
290	public void processPacket(PresencePacket packet, PgpEngine pgp) {
291		final Jid from = packet.getFrom();
292		if (!from.isBareJid()) {
293			final String name = from.getResourcepart();
294			final String type = packet.getAttribute("type");
295			final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
296			final List<String> codes = getStatusCodes(x);
297			if (type == null) {
298				User user = new User();
299				if (x != null) {
300					Element item = x.findChild("item");
301					if (item != null && name != null) {
302						user.setName(name);
303						user.setAffiliation(item.getAttribute("affiliation"));
304						user.setRole(item.getAttribute("role"));
305						user.setJid(item.getAttributeAsJid("jid"));
306						if (codes.contains(STATUS_CODE_SELF_PRESENCE) || packet.getFrom().equals(this.conversation.getJid())) {
307							this.isOnline = true;
308							this.error = ERROR_NO_ERROR;
309							self = user;
310							if (mNickChangingInProgress) {
311								if (onRenameListener != null) {
312									onRenameListener.onSuccess();
313								}
314								mNickChangingInProgress = false;
315							}
316						} else {
317							addUser(user);
318						}
319						if (pgp != null) {
320							Element signed = packet.findChild("x", "jabber:x:signed");
321							if (signed != null) {
322								Element status = packet.findChild("status");
323								String msg = status == null ? "" : status.getContent();
324								long keyId = pgp.fetchKeyId(account, msg, signed.getContent());
325								if (keyId != 0) {
326									user.setPgpKeyId(keyId);
327								}
328							}
329						}
330					}
331				}
332			} else if (type.equals("unavailable")) {
333				if (codes.contains(STATUS_CODE_SELF_PRESENCE) ||
334						packet.getFrom().equals(this.conversation.getJid())) {
335					if (codes.contains(STATUS_CODE_CHANGED_NICK)) {
336						this.mNickChangingInProgress = true;
337					} else if (codes.contains(STATUS_CODE_KICKED)) {
338						setError(KICKED_FROM_ROOM);
339					} else if (codes.contains(STATUS_CODE_BANNED)) {
340						setError(ERROR_BANNED);
341					} else if (codes.contains(STATUS_CODE_LOST_MEMBERSHIP)) {
342						setError(ERROR_MEMBERS_ONLY);
343					} else {
344						setError(ERROR_UNKNOWN);
345					}
346				} else {
347					deleteUser(name);
348				}
349			} else if (type.equals("error")) {
350				Element error = packet.findChild("error");
351				if (error != null && error.hasChild("conflict")) {
352					if (isOnline) {
353						if (onRenameListener != null) {
354							onRenameListener.onFailure();
355						}
356					} else {
357						setError(ERROR_NICK_IN_USE);
358					}
359				} else if (error != null && error.hasChild("not-authorized")) {
360					setError(ERROR_PASSWORD_REQUIRED);
361				} else if (error != null && error.hasChild("forbidden")) {
362					setError(ERROR_BANNED);
363				} else if (error != null && error.hasChild("registration-required")) {
364					setError(ERROR_MEMBERS_ONLY);
365				}
366			}
367		}
368	}
369
370	private void setError(int error) {
371		this.isOnline = false;
372		this.error = error;
373	}
374
375	private List<String> getStatusCodes(Element x) {
376		List<String> codes = new ArrayList<>();
377		if (x != null) {
378			for (Element child : x.getChildren()) {
379				if (child.getName().equals("status")) {
380					String code = child.getAttribute("code");
381					if (code != null) {
382						codes.add(code);
383					}
384				}
385			}
386		}
387		return codes;
388	}
389
390	public List<User> getUsers() {
391		return this.users;
392	}
393
394	public String getProposedNick() {
395		if (conversation.getBookmark() != null
396				&& conversation.getBookmark().getNick() != null
397				&& !conversation.getBookmark().getNick().isEmpty()) {
398			return conversation.getBookmark().getNick();
399		} else if (!conversation.getJid().isBareJid()) {
400			return conversation.getJid().getResourcepart();
401		} else {
402			return account.getUsername();
403		}
404	}
405
406	public String getActualNick() {
407		if (this.self.getName() != null) {
408			return this.self.getName();
409		} else {
410			return this.getProposedNick();
411		}
412	}
413
414	public boolean online() {
415		return this.isOnline;
416	}
417
418	public int getError() {
419		return this.error;
420	}
421
422	public void setOnRenameListener(OnRenameListener listener) {
423		this.onRenameListener = listener;
424	}
425
426	public void setOffline() {
427		this.users.clear();
428		this.error = 0;
429		this.isOnline = false;
430	}
431
432	public User getSelf() {
433		return self;
434	}
435
436	public void setSubject(String content) {
437		this.subject = content;
438	}
439
440	public String getSubject() {
441		return this.subject;
442	}
443
444	public String createNameFromParticipants() {
445		if (users.size() >= 2) {
446			List<String> names = new ArrayList<String>();
447			for (User user : users) {
448				Contact contact = user.getContact();
449				if (contact != null && !contact.getDisplayName().isEmpty()) {
450					names.add(contact.getDisplayName().split("\\s+")[0]);
451				} else {
452					names.add(user.getName());
453				}
454			}
455			StringBuilder builder = new StringBuilder();
456			for (int i = 0; i < names.size(); ++i) {
457				builder.append(names.get(i));
458				if (i != names.size() - 1) {
459					builder.append(", ");
460				}
461			}
462			return builder.toString();
463		} else {
464			return null;
465		}
466	}
467
468	public long[] getPgpKeyIds() {
469		List<Long> ids = new ArrayList<>();
470		for (User user : getUsers()) {
471			if (user.getPgpKeyId() != 0) {
472				ids.add(user.getPgpKeyId());
473			}
474		}
475		long[] primitivLongArray = new long[ids.size()];
476		for (int i = 0; i < ids.size(); ++i) {
477			primitivLongArray[i] = ids.get(i);
478		}
479		return primitivLongArray;
480	}
481
482	public boolean pgpKeysInUse() {
483		for (User user : getUsers()) {
484			if (user.getPgpKeyId() != 0) {
485				return true;
486			}
487		}
488		return false;
489	}
490
491	public boolean everybodyHasKeys() {
492		for (User user : getUsers()) {
493			if (user.getPgpKeyId() == 0) {
494				return false;
495			}
496		}
497		return true;
498	}
499
500	public Jid createJoinJid(String nick) {
501		try {
502			return Jid.fromString(this.conversation.getJid().toBareJid().toString() + "/" + nick);
503		} catch (final InvalidJidException e) {
504			return null;
505		}
506	}
507
508	public Jid getTrueCounterpart(String counterpart) {
509		for (User user : this.getUsers()) {
510			if (user.getName().equals(counterpart)) {
511				return user.getJid();
512			}
513		}
514		return null;
515	}
516
517	public String getPassword() {
518		this.password = conversation.getAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD);
519		if (this.password == null && conversation.getBookmark() != null
520				&& conversation.getBookmark().getPassword() != null) {
521			return conversation.getBookmark().getPassword();
522		} else {
523			return this.password;
524		}
525	}
526
527	public void setPassword(String password) {
528		if (conversation.getBookmark() != null) {
529			conversation.getBookmark().setPassword(password);
530		} else {
531			this.password = password;
532		}
533		conversation.setAttribute(Conversation.ATTRIBUTE_MUC_PASSWORD, password);
534	}
535
536	public Conversation getConversation() {
537		return this.conversation;
538	}
539}