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