aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs/conversations/utils
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/eu/siacs/conversations/utils')
-rw-r--r--src/main/java/eu/siacs/conversations/utils/CryptoHelper.java127
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java323
-rw-r--r--src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java4
-rw-r--r--src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java66
-rw-r--r--src/main/java/eu/siacs/conversations/utils/FileUtils.java149
-rw-r--r--src/main/java/eu/siacs/conversations/utils/MimeUtils.java2
-rw-r--r--src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java4
-rw-r--r--src/main/java/eu/siacs/conversations/utils/PhoneHelper.java95
-rw-r--r--src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java62
-rw-r--r--src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java57
-rw-r--r--src/main/java/eu/siacs/conversations/utils/UIHelper.java8
-rw-r--r--src/main/java/eu/siacs/conversations/utils/Xmlns.java4
12 files changed, 748 insertions, 153 deletions
diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
index 2dec203d..4850f19d 100644
--- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
@@ -1,20 +1,36 @@
package eu.siacs.conversations.utils;
+import android.os.Bundle;
+import android.util.Pair;
+
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x500.style.BCStyle;
+import org.bouncycastle.asn1.x500.style.IETFUtils;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
+
+import java.security.MessageDigest;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
import java.security.SecureRandom;
import java.text.Normalizer;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Locale;
import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
public final class CryptoHelper {
public static final String FILETRANSFER = "?FILETRANSFERv1:";
private final static char[] hexArray = "0123456789abcdef".toCharArray();
- private final static char[] vowels = "aeiou".toCharArray();
- private final static char[] consonants = "bcdfghjklmnpqrstvwxyz".toCharArray();
final public static byte[] ONE = new byte[] { 0, 0, 0, 1 };
public static String bytesToHex(byte[] bytes) {
@@ -48,22 +64,6 @@ public final class CryptoHelper {
return result;
}
- public static String randomMucName(SecureRandom random) {
- return randomWord(3, random) + "." + randomWord(7, random);
- }
-
- private static String randomWord(int lenght, SecureRandom random) {
- StringBuilder builder = new StringBuilder(lenght);
- for (int i = 0; i < lenght; ++i) {
- if (i % 2 == 0) {
- builder.append(consonants[random.nextInt(consonants.length)]);
- } else {
- builder.append(vowels[random.nextInt(vowels.length)]);
- }
- }
- return builder.toString();
- }
-
/**
* Escapes usernames or passwords for SASL.
*/
@@ -96,11 +96,18 @@ public final class CryptoHelper {
} else if (fingerprint.length() < 40) {
return fingerprint;
}
+ StringBuilder builder = new StringBuilder(fingerprint.toLowerCase(Locale.US).replaceAll("\\s", ""));
+ for(int i=8;i<builder.length();i+=9) {
+ builder.insert(i, ' ');
+ }
+ return builder.toString();
+ }
+
+ public static String prettifyFingerprintCert(String fingerprint) {
StringBuilder builder = new StringBuilder(fingerprint);
- builder.insert(8, " ");
- builder.insert(17, " ");
- builder.insert(26, " ");
- builder.insert(35, " ");
+ for(int i=2;i < builder.length(); i+=3) {
+ builder.insert(i,':');
+ }
return builder.toString();
}
@@ -126,4 +133,80 @@ public final class CryptoHelper {
}
}
}
+
+ public static Pair<Jid,String> extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException, CertificateParsingException {
+ Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames();
+ List<String> emails = new ArrayList<>();
+ if (alternativeNames != null) {
+ for(List<?> san : alternativeNames) {
+ Integer type = (Integer) san.get(0);
+ if (type == 1) {
+ emails.add((String) san.get(1));
+ }
+ }
+ }
+ X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
+ if (emails.size() == 0) {
+ emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue()));
+ }
+ String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue());
+ if (emails.size() >= 1) {
+ return new Pair<>(Jid.fromString(emails.get(0)), name);
+ } else {
+ return null;
+ }
+ }
+
+ public static Bundle extractCertificateInformation(X509Certificate certificate) {
+ Bundle information = new Bundle();
+ try {
+ JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate);
+ X500Name subject = holder.getSubject();
+ try {
+ information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString());
+ } catch (Exception e) {
+ //ignored
+ }
+ try {
+ information.putString("subject_o",subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString());
+ } catch (Exception e) {
+ //ignored
+ }
+
+ X500Name issuer = holder.getIssuer();
+ try {
+ information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString());
+ } catch (Exception e) {
+ //ignored
+ }
+ try {
+ information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString());
+ } catch (Exception e) {
+ //ignored
+ }
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-1");
+ byte[] fingerprint = md.digest(certificate.getEncoded());
+ information.putString("sha1", prettifyFingerprintCert(bytesToHex(fingerprint)));
+ } catch (Exception e) {
+
+ }
+ return information;
+ } catch (CertificateEncodingException e) {
+ return information;
+ }
+ }
+
+ public static int encryptionTypeToText(int encryption) {
+ switch (encryption) {
+ case Message.ENCRYPTION_OTR:
+ return R.string.encryption_choice_otr;
+ case Message.ENCRYPTION_AXOLOTL:
+ return R.string.encryption_choice_omemo;
+ case Message.ENCRYPTION_NONE:
+ return R.string.encryption_choice_unencrypted;
+ default:
+ return R.string.encryption_choice_pgp;
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index 79a8c854..58d53216 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -1,12 +1,39 @@
package eu.siacs.conversations.utils;
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.LinkProperties;
+import android.net.Network;
+import android.net.RouteInfo;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.SocketTimeoutException;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.TreeMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+
import de.measite.minidns.Client;
import de.measite.minidns.DNSMessage;
import de.measite.minidns.Record;
+import de.measite.minidns.Record.CLASS;
import de.measite.minidns.Record.TYPE;
import de.measite.minidns.Record.CLASS;
import de.measite.minidns.record.SRV;
+import de.measite.minidns.record.A;
+import de.measite.minidns.record.AAAA;
import de.measite.minidns.record.Data;
+import de.measite.minidns.record.SRV;
import de.measite.minidns.util.NameUtil;
import java.io.IOException;
@@ -21,83 +48,237 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.xmpp.jid.Jid;
public class DNSHelper {
- private static final String CLIENT_SRV_PREFIX = "_xmpp-client._tcp.";
- private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
- private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
+
+ public static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ public static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ public static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ public static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
+ public static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
protected static Client client = new Client();
- /**
- * Queries the SRV record for the server JID.
- * This method uses all available Domain Name Servers.
- * @param jid the server JID
- * @return TreeSet with SrvRecords. If no SRV record is found for JID an empty TreeSet is returned.
- */
- public static final TreeSet<SrvRecord> querySrvRecord(Jid jid) {
- String host = jid.getDomainpart();
- String dns[] = client.findDNS();
- TreeSet<SrvRecord> result = new TreeSet<>();
-
- if (dns != null) {
- for (String dnsserver : dns) {
- result = querySrvRecord(host, dnsserver);
- if (!result.isEmpty()) {
- break;
- }
- }
- }
-
- return result;
- }
-
- /**
- * Queries the SRV record for an host from the given Domain Name Server.
- * @param host the host to query for
- * @param dnsserver the DNS to query on
- * @return TreeSet with SrvRecords.
- */
- private static final TreeSet<SrvRecord> querySrvRecord(String host, String dnsserver) {
- TreeSet<SrvRecord> result = new TreeSet<>();
- try {
- InetAddress dnsServerAddress = InetAddress.getByName(dnsserver);
- String qname = CLIENT_SRV_PREFIX + host;
- DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerAddress.getHostAddress());
- Record[] rrset = message.getAnswers();
- for (Record rr : rrset) {
- Data d = rr.getPayload();
- if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
- SRV srv = (SRV) d;
- SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort());
- result.add(srvRecord);
- }
- }
- } catch (IOException e) {
- Logging.d("dns", "Error while retrieving SRV record for '" + host + "' from DNS '" + dnsserver + "': " + e.getMessage());
- }
- return result;
- }
-
- /**
- * Checks whether the given server is an IP address or not.
- * The following patterns are treated as valid IP addresses:
- * <ul>
- * <li>{@link #PATTERN_IPV4}</li>
- * <li>{@link #PATTERN_IPV6}</li>
- * <li>{@link #PATTERN_IPV6_6HEX4DEC}</li>
- * <li>{@link #PATTERN_IPV6_HEX4DECCOMPRESSED}</li>
- * <li>{@link #PATTERN_IPV6_HEXCOMPRESSED}</li>
- * </ul>
- * @param server the string to check
- * @return <code>true</code> if one of the patterns is matched <code>false</code> otherwise
- */
+ public static Bundle getSRVRecord(final Jid jid, Context context) throws IOException {
+ final String host = jid.getDomainpart();
+ final List<InetAddress> servers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers(context) : getDnsServersPreLollipop();
+ Bundle b = new Bundle();
+ for(InetAddress server : servers) {
+ b = queryDNS(host, server);
+ if (b.containsKey("values")) {
+ return b;
+ }
+ }
+ if (!b.containsKey("values")) {
+ Log.d(Config.LOGTAG,"all dns queries failed. provide fallback A record");
+ ArrayList<Parcelable> values = new ArrayList<>();
+ values.add(createNamePortBundle(host, 5222, false));
+ b.putParcelableArrayList("values",values);
+ }
+ return b;
+ }
+
+ @TargetApi(21)
+ private static List<InetAddress> getDnsServers(Context context) {
+ List<InetAddress> servers = new ArrayList<>();
+ ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks();
+ if (networks == null) {
+ return getDnsServersPreLollipop();
+ }
+ for(int i = 0; i < networks.length; ++i) {
+ LinkProperties linkProperties = connectivityManager.getLinkProperties(networks[i]);
+ if (linkProperties != null) {
+ if (hasDefaultRoute(linkProperties)) {
+ servers.addAll(0, linkProperties.getDnsServers());
+ } else {
+ servers.addAll(linkProperties.getDnsServers());
+ }
+ }
+ }
+ if (servers.size() > 0) {
+ Log.d(Config.LOGTAG, "used lollipop variant to discover dns servers in " + networks.length + " networks");
+ }
+ return servers.size() > 0 ? servers : getDnsServersPreLollipop();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ private static boolean hasDefaultRoute(LinkProperties linkProperties) {
+ for(RouteInfo route: linkProperties.getRoutes()) {
+ if (route.isDefaultRoute()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static List<InetAddress> getDnsServersPreLollipop() {
+ List<InetAddress> servers = new ArrayList<>();
+ String[] dns = client.findDNS();
+ for(int i = 0; i < dns.length; ++i) {
+ try {
+ servers.add(InetAddress.getByName(dns[i]));
+ } catch (UnknownHostException e) {
+ //ignore
+ }
+ }
+ return servers;
+ }
+
+ private static class TlsSrv {
+ private final SRV srv;
+ private final boolean tls;
+
+ public TlsSrv(SRV srv, boolean tls) {
+ this.srv = srv;
+ this.tls = tls;
+ }
+ }
+
+ private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map<Integer, List<TlsSrv>> priorities, final Map<String, List<String>> ips4, final Map<String, List<String>> ips6, final boolean tls) throws IOException {
+ final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
+ for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
+ for (Record rr : rrset) {
+ Data d = rr.getPayload();
+ if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
+ SRV srv = (SRV) d;
+ if (!priorities.containsKey(srv.getPriority())) {
+ priorities.put(srv.getPriority(),new ArrayList<TlsSrv>());
+ }
+ priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls));
+ }
+ if (d instanceof A) {
+ A a = (A) d;
+ if (!ips4.containsKey(rr.getName())) {
+ ips4.put(rr.getName(), new ArrayList<String>());
+ }
+ ips4.get(rr.getName()).add(a.toString());
+ }
+ if (d instanceof AAAA) {
+ AAAA aaaa = (AAAA) d;
+ if (!ips6.containsKey(rr.getName())) {
+ ips6.put(rr.getName(), new ArrayList<String>());
+ }
+ ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
+ }
+ }
+ }
+ }
+
+ public static Bundle queryDNS(String host, InetAddress dnsServer) {
+ Bundle bundle = new Bundle();
+ try {
+ client.setTimeout(Config.PING_TIMEOUT * 1000);
+ final String qname = "_xmpp-client._tcp." + host;
+ final String tlsQname = "_xmpps-client._tcp." + host;
+ Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
+
+ final Map<Integer, List<TlsSrv>> priorities = new TreeMap<>();
+ final Map<String, List<String>> ips4 = new TreeMap<>();
+ final Map<String, List<String>> ips6 = new TreeMap<>();
+
+ fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false);
+ fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true);
+
+ final List<TlsSrv> result = new ArrayList<>();
+ for (final List<TlsSrv> s : priorities.values()) {
+ result.addAll(s);
+ }
+
+ final ArrayList<Bundle> values = new ArrayList<>();
+ if (result.size() == 0) {
+ DNSMessage response;
+ try {
+ response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
+ for (int i = 0; i < response.getAnswers().length; ++i) {
+ values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
+ }
+ } catch (SocketTimeoutException e) {
+ Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress());
+ }
+ try {
+ response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
+ for (int i = 0; i < response.getAnswers().length; ++i) {
+ values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
+ }
+ } catch (SocketTimeoutException e) {
+ Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
+ }
+ values.add(createNamePortBundle(host, 5222, false));
+ bundle.putParcelableArrayList("values", values);
+ return bundle;
+ }
+ for (final TlsSrv tlsSrv : result) {
+ final SRV srv = tlsSrv.srv;
+ if (ips6.containsKey(srv.getName())) {
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6, tlsSrv.tls));
+ } else {
+ try {
+ DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
+ for (int i = 0; i < response.getAnswers().length; ++i) {
+ values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls));
+ }
+ } catch (SocketTimeoutException e) {
+ Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
+ }
+ }
+ if (ips4.containsKey(srv.getName())) {
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4, tlsSrv.tls));
+ } else {
+ DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress());
+ for(int i = 0; i < response.getAnswers().length; ++i) {
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls));
+ }
+ }
+ values.add(createNamePortBundle(srv.getName(), srv.getPort(), tlsSrv.tls));
+ }
+ bundle.putParcelableArrayList("values", values);
+ } catch (SocketTimeoutException e) {
+ bundle.putString("error", "timeout");
+ } catch (Exception e) {
+ bundle.putString("error", "unhandled");
+ }
+ return bundle;
+ }
+
+ private static Bundle createNamePortBundle(String name, int port, final boolean tls) {
+ Bundle namePort = new Bundle();
+ namePort.putString("name", name);
+ namePort.putBoolean("tls", tls);
+ namePort.putInt("port", port);
+ return namePort;
+ }
+
+ private static Bundle createNamePortBundle(String name, int port, Map<String, List<String>> ips, final boolean tls) {
+ Bundle namePort = new Bundle();
+ namePort.putString("name", name);
+ namePort.putBoolean("tls", tls);
+ namePort.putInt("port", port);
+ if (ips!=null) {
+ List<String> ip = ips.get(name);
+ Collections.shuffle(ip, new Random());
+ namePort.putString("ip", ip.get(0));
+ }
+ return namePort;
+ }
+
+ private static Bundle createNamePortBundle(String name, int port, Data data, final boolean tls) {
+ Bundle namePort = new Bundle();
+ namePort.putString("name", name);
+ namePort.putBoolean("tls", tls);
+ namePort.putInt("port", port);
+ if (data instanceof A) {
+ namePort.putString("ip", data.toString());
+ } else if (data instanceof AAAA) {
+ namePort.putString("ip","["+data.toString()+"]");
+ }
+ return namePort;
+ }
+
public static boolean isIp(final String server) {
- return PATTERN_IPV4.matcher(server).matches()
+ return server != null && (
+ PATTERN_IPV4.matcher(server).matches()
|| PATTERN_IPV6.matcher(server).matches()
|| PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
|| PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
- || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches();
+ || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches());
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java
index 0ad57fe2..4e3ec236 100644
--- a/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java
+++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHandler.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.utils;
+import android.content.Context;
+
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
@@ -8,8 +10,6 @@ import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
-import android.content.Context;
-
public class ExceptionHandler implements UncaughtExceptionHandler {
private UncaughtExceptionHandler defaultHandler;
diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
index b0a0455d..892d5a00 100644
--- a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
@@ -1,5 +1,17 @@
package eu.siacs.conversations.utils;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.SharedPreferences;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.preference.PreferenceManager;
+import android.text.format.DateUtils;
+import android.util.Log;
+
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
@@ -15,6 +27,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -37,12 +50,11 @@ public class ExceptionHelper {
}
}
- public static void checkForCrash(final Context context,
- final XmppConnectionService service) {
+ public static boolean checkForCrash(ConversationActivity activity, final XmppConnectionService service) {
try {
boolean neverSend = ConversationsPlusPreferences.neverSend();
if (neverSend) {
- return;
+ return false;
}
List<Account> accounts = service.getAccounts();
Account account = null;
@@ -53,24 +65,25 @@ public class ExceptionHelper {
}
}
if (account == null) {
- return;
+ return false;
}
final Account finalAccount = account;
- FileInputStream file = context.openFileInput("stacktrace.txt");
+ FileInputStream file = activity.openFileInput("stacktrace.txt");
InputStreamReader inputStreamReader = new InputStreamReader(file);
BufferedReader stacktrace = new BufferedReader(inputStreamReader);
final StringBuilder report = new StringBuilder();
- PackageManager pm = context.getPackageManager();
+ PackageManager pm = activity.getPackageManager();
PackageInfo packageInfo = null;
try {
- packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
+ packageInfo = pm.getPackageInfo(activity.getPackageName(), 0);
report.append("Version: " + packageInfo.versionName + '\n');
report.append("Last Update: "
- + DateUtils.formatDateTime(context,
- packageInfo.lastUpdateTime,
- DateUtils.FORMAT_SHOW_TIME
- | DateUtils.FORMAT_SHOW_DATE) + '\n');
+ + DateUtils.formatDateTime(activity,
+ packageInfo.lastUpdateTime,
+ DateUtils.FORMAT_SHOW_TIME
+ | DateUtils.FORMAT_SHOW_DATE) + '\n');
} catch (NameNotFoundException e) {
+ return false;
}
String line;
while ((line = stacktrace.readLine()) != null) {
@@ -78,11 +91,11 @@ public class ExceptionHelper {
report.append('\n');
}
file.close();
- context.deleteFile("stacktrace.txt");
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(context.getString(R.string.crash_report_title));
- builder.setMessage(context.getText(R.string.crash_report_message));
- builder.setPositiveButton(context.getText(R.string.send_now),
+ activity.deleteFile("stacktrace.txt");
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setTitle(activity.getString(R.string.crash_report_title));
+ builder.setMessage(activity.getText(R.string.crash_report_message));
+ builder.setPositiveButton(activity.getText(R.string.send_now),
new OnClickListener() {
@Override
@@ -91,18 +104,18 @@ public class ExceptionHelper {
Logging.d(Config.LOGTAG, "using account="
+ finalAccount.getJid().toBareJid()
+ " to send in stack trace");
- Conversation conversation = null;
- try {
- conversation = service.findOrCreateConversation(finalAccount,
- Jid.fromString(context.getString(R.string.cplus_bugreport_jabberid)), false);
- } catch (final InvalidJidException ignored) {
- }
- Message message = new Message(conversation, report
+ Conversation conversation = null;
+ try {
+ conversation = service.findOrCreateConversation(finalAccount,
+ Jid.fromString("bugs@siacs.eu"), false);
+ } catch (final InvalidJidException ignored) {
+ }
+ Message message = new Message(conversation, report
.toString(), Message.ENCRYPTION_NONE);
service.sendMessage(message);
}
});
- builder.setNegativeButton(context.getText(R.string.send_never),
+ builder.setNegativeButton(activity.getText(R.string.send_never),
new OnClickListener() {
@Override
@@ -111,8 +124,9 @@ public class ExceptionHelper {
}
});
builder.create().show();
+ return true;
} catch (final IOException ignored) {
- }
-
+ return false;
+ }
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/FileUtils.java b/src/main/java/eu/siacs/conversations/utils/FileUtils.java
new file mode 100644
index 00000000..ad8b8640
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/FileUtils.java
@@ -0,0 +1,149 @@
+package eu.siacs.conversations.utils;
+
+import android.annotation.SuppressLint;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+
+public class FileUtils {
+
+ /**
+ * Get a file path from a Uri. This will get the the path for Storage Access
+ * Framework Documents, as well as the _data field for the MediaStore and
+ * other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @param uri The Uri to query.
+ * @author paulburke
+ */
+ @SuppressLint("NewApi")
+ public static String getPath(final Context context, final Uri uri) {
+ if (uri == null) {
+ return null;
+ }
+
+ final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
+
+ // DocumentProvider
+ if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+
+ // TODO handle non-primary volumes
+ }
+ // DownloadsProvider
+ else if (isDownloadsDocument(uri)) {
+
+ final String id = DocumentsContract.getDocumentId(uri);
+ final Uri contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+ return getDataColumn(context, contentUri, null, null);
+ }
+ // MediaProvider
+ else if (isMediaDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[]{
+ split[1]
+ };
+
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ }
+ }
+ // MediaStore (and general)
+ else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ return getDataColumn(context, uri, null, null);
+ }
+ // File
+ else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the value of the data column for this Uri. This is useful for
+ * MediaStore Uris, and other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @param uri The Uri to query.
+ * @param selection (Optional) Filter used in the query.
+ * @param selectionArgs (Optional) Selection arguments used in the query.
+ * @return The value of the _data column, which is typically a file path.
+ */
+ public static String getDataColumn(Context context, Uri uri, String selection,
+ String[] selectionArgs) {
+
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {
+ column
+ };
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int column_index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(column_index);
+ }
+ } catch(Exception e) {
+ return null;
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is ExternalStorageProvider.
+ */
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is DownloadsProvider.
+ */
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is MediaProvider.
+ */
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
index a9e89d1b..d4544424 100644
--- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
+++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
@@ -458,7 +458,7 @@ public final class MimeUtils {
if (extension == null || extension.isEmpty()) {
return null;
}
- return extensionToMimeTypeMap.get(extension);
+ return extensionToMimeTypeMap.get(extension.toLowerCase());
}
/**
* Returns true if the given extension has a registered MIME type.
diff --git a/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java b/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java
index 9a689768..f18a4ed8 100644
--- a/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java
+++ b/src/main/java/eu/siacs/conversations/utils/OnPhoneContactsLoadedListener.java
@@ -1,9 +1,9 @@
package eu.siacs.conversations.utils;
-import java.util.List;
-
import android.os.Bundle;
+import java.util.List;
+
public interface OnPhoneContactsLoadedListener {
public void onPhoneContactsLoaded(List<Bundle> phoneContacts);
}
diff --git a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
index e0556af3..774532d1 100644
--- a/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/PhoneHelper.java
@@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
+import android.Manifest;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
@@ -19,12 +20,17 @@ import de.thedevstack.conversationsplus.ConversationsPlusApplication;
public class PhoneHelper {
- public static void loadPhoneContacts(Context context,final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) {
- final String[] PROJECTION = new String[] { ContactsContract.Data._ID,
+ public static void loadPhoneContacts(Context context, final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+ listener.onPhoneContactsLoaded(phoneContacts);
+ return;
+ }
+ final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.PHOTO_URI,
ContactsContract.Data.LOOKUP_KEY,
- ContactsContract.CommonDataKinds.Im.DATA };
+ ContactsContract.CommonDataKinds.Im.DATA};
final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
@@ -32,39 +38,39 @@ public class PhoneHelper {
+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
+ "\")";
- CursorLoader mCursorLoader = new CursorLoader(context,
+ CursorLoader mCursorLoader = new NotThrowCursorLoader(context,
ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null,
null);
mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() {
@Override
public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) {
- if (cursor == null) {
- return;
- }
- while (cursor.moveToNext()) {
- Bundle contact = new Bundle();
- contact.putInt("phoneid", cursor.getInt(cursor
- .getColumnIndex(ContactsContract.Data._ID)));
- contact.putString(
- "displayname",
- cursor.getString(cursor
- .getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
- contact.putString("photouri", cursor.getString(cursor
- .getColumnIndex(ContactsContract.Data.PHOTO_URI)));
- contact.putString("lookup", cursor.getString(cursor
- .getColumnIndex(ContactsContract.Data.LOOKUP_KEY)));
-
- contact.putString(
- "jid",
- cursor.getString(cursor
- .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
- phoneContacts.add(contact);
+ if (cursor != null) {
+ while (cursor.moveToNext()) {
+ Bundle contact = new Bundle();
+ contact.putInt("phoneid", cursor.getInt(cursor
+ .getColumnIndex(ContactsContract.Data._ID)));
+ contact.putString(
+ "displayname",
+ cursor.getString(cursor
+ .getColumnIndex(ContactsContract.Data.DISPLAY_NAME)));
+ contact.putString("photouri", cursor.getString(cursor
+ .getColumnIndex(ContactsContract.Data.PHOTO_URI)));
+ contact.putString("lookup", cursor.getString(cursor
+ .getColumnIndex(ContactsContract.Data.LOOKUP_KEY)));
+
+ contact.putString(
+ "jid",
+ cursor.getString(cursor
+ .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
+ phoneContacts.add(contact);
+ }
+ cursor.close();
}
+
if (listener != null) {
listener.onPhoneContactsLoaded(phoneContacts);
}
- cursor.close();
}
});
try {
@@ -76,8 +82,30 @@ public class PhoneHelper {
}
}
+ private static class NotThrowCursorLoader extends CursorLoader {
+
+ public NotThrowCursorLoader(Context c, Uri u, String[] p, String s, String[] sa, String so) {
+ super(c, u, p, s, sa, so);
+ }
+
+ @Override
+ public Cursor loadInBackground() {
+
+ try {
+ return (super.loadInBackground());
+ } catch (SecurityException e) {
+ return(null);
+ }
+ }
+
+ }
+
public static Uri getSefliUri(Context context) {
- String[] mProjection = new String[] { Profile._ID, Profile.PHOTO_URI };
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+ return null;
+ }
+ String[] mProjection = new String[]{Profile._ID, Profile.PHOTO_URI};
Cursor mProfileCursor = context.getContentResolver().query(
Profile.CONTENT_URI, mProjection, null, null, null);
@@ -94,4 +122,17 @@ public class PhoneHelper {
}
}
}
+
+ public static String getVersionName(Context context) {
+ final String packageName = context == null ? null : context.getPackageName();
+ if (packageName != null) {
+ try {
+ return context.getPackageManager().getPackageInfo(packageName, 0).versionName;
+ } catch (final PackageManager.NameNotFoundException | RuntimeException e) {
+ return "unknown";
+ }
+ } else {
+ return "unknown";
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java b/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java
new file mode 100644
index 00000000..49e9a81a
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java
@@ -0,0 +1,62 @@
+package eu.siacs.conversations.utils;
+
+import java.lang.reflect.Method;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+public class SSLSocketHelper {
+
+ public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException {
+ final String[] supportProtocols;
+ final Collection<String> supportedProtocols = new LinkedList<>(
+ Arrays.asList(sslSocket.getSupportedProtocols()));
+ supportedProtocols.remove("SSLv3");
+ supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]);
+
+ sslSocket.setEnabledProtocols(supportProtocols);
+
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
+ sslSocket.getSupportedCipherSuites());
+ if (cipherSuites.length > 0) {
+ sslSocket.setEnabledCipherSuites(cipherSuites);
+ }
+ }
+
+ public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) {
+ if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname);
+ } else {
+ try {
+ socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
+ } catch (Throwable e) {
+ // ignore any error, we just can't set the hostname...
+ }
+ }
+ }
+
+ public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) {
+ try {
+ if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
+ // can't call directly because of @hide?
+ //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")});
+ android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}});
+ } else {
+ final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class);
+ // the concatenation of 8-bit, length prefixed protocol names, just one in our case...
+ // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
+ final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8");
+ final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1];
+ lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow
+ System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length);
+ method.invoke(socket, new Object[]{lengthPrefixedProtocols});
+ }
+ } catch (Throwable e) {
+ // ignore any error, we just can't set the alpn protocol...
+ }
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
new file mode 100644
index 00000000..768e9f17
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
@@ -0,0 +1,57 @@
+package eu.siacs.conversations.utils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+import eu.siacs.conversations.Config;
+
+public class SocksSocketFactory {
+
+ public static void createSocksConnection(Socket socket, String destination, int port) throws IOException {
+ InputStream proxyIs = socket.getInputStream();
+ OutputStream proxyOs = socket.getOutputStream();
+ proxyOs.write(new byte[]{0x05, 0x01, 0x00});
+ byte[] response = new byte[2];
+ proxyIs.read(response);
+ byte[] dest = destination.getBytes();
+ ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
+ request.put(new byte[]{0x05, 0x01, 0x00, 0x03});
+ request.put((byte) dest.length);
+ request.put(dest);
+ request.putShort((short) port);
+ proxyOs.write(request.array());
+ response = new byte[7 + dest.length];
+ proxyIs.read(response);
+ if (response[1] != 0x00) {
+ throw new SocksConnectionException();
+ }
+ }
+
+ public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
+ Socket socket = new Socket();
+ try {
+ socket.connect(address, Config.CONNECT_TIMEOUT * 1000);
+ } catch (IOException e) {
+ throw new SocksProxyNotFoundException();
+ }
+ createSocksConnection(socket, destination, port);
+ return socket;
+ }
+
+ public static Socket createSocketOverTor(String destination, int port) throws IOException {
+ return createSocket(new InetSocketAddress(InetAddress.getLocalHost(), 9050), destination, port);
+ }
+
+ static class SocksConnectionException extends IOException {
+
+ }
+
+ public static class SocksProxyNotFoundException extends IOException {
+
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
index 7562c446..e1b1a2a9 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -1,5 +1,10 @@
package eu.siacs.conversations.utils;
+import android.content.Context;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.util.Pair;
+
import java.math.BigDecimal;
import java.text.NumberFormat;
import java.util.ArrayList;
@@ -22,6 +27,7 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.entities.Message;
+import eu.siacs.conversations.entities.Transferable;
import eu.siacs.conversations.xmpp.jid.Jid;
import android.content.Context;
@@ -145,7 +151,7 @@ public class UIHelper {
}
public static int getColorForName(String name) {
- if (name.isEmpty()) {
+ if (name == null || name.isEmpty()) {
return 0xFF202020;
}
int colors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5,
diff --git a/src/main/java/eu/siacs/conversations/utils/Xmlns.java b/src/main/java/eu/siacs/conversations/utils/Xmlns.java
index de0a29ce..a19ec791 100644
--- a/src/main/java/eu/siacs/conversations/utils/Xmlns.java
+++ b/src/main/java/eu/siacs/conversations/utils/Xmlns.java
@@ -1,9 +1,11 @@
package eu.siacs.conversations.utils;
+import eu.siacs.conversations.Config;
+
public final class Xmlns {
public static final String BLOCKING = "urn:xmpp:blocking";
public static final String ROSTER = "jabber:iq:roster";
public static final String REGISTER = "jabber:iq:register";
public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams";
- public static final String HTTP_UPLOAD = "urn:xmpp:http:upload";
+ public static final String HTTP_UPLOAD = Config.LEGACY_NAMESPACE_HTTP_UPLOAD ? "eu:siacs:conversations:http:upload" : "urn:xmpp:http:upload";
}