From 7a1204e044c69aa9d5e1de6511f2bb8c9371aefe Mon Sep 17 00:00:00 2001 From: Janne Koschinski <janne@kuschku.de> Date: Sun, 31 Jan 2016 00:32:02 +0100 Subject: [PATCH] Added SSL Handling --- .../java/de/kuschku/libquassel/Client.java | 17 +- .../de/kuschku/libquassel/CoreConnection.java | 46 +++-- .../kuschku/libquassel/ProtocolHandler.java | 6 +- .../events/CertificateAcceptedEvent.java | 11 ++ .../events/UnknownCertificateEvent.java | 20 ++ .../serializers/HeartbeatReplySerializer.java | 2 - .../libquassel/localtypes/QueryBuffer.java | 6 +- .../backlogmanagers/BacklogFilter.java | 16 +- .../backlogmanagers/BacklogManager.java | 3 - .../backlogmanagers/SimpleBacklogManager.java | 4 +- .../serializers/MessageSerializer.java | 2 +- .../libquassel/protocols/DatastreamPeer.java | 14 +- .../libquassel/protocols/LegacyPeer.java | 5 +- .../libquassel/ssl/CertificateManager.java | 24 +++ .../libquassel/ssl/QuasselTrustManager.java | 70 +++++++ .../ssl/UnknownCertificateException.java | 16 ++ .../serializers/AliasManagerSerializer.java | 8 +- .../IgnoreListManagerSerializer.java | 7 +- .../serializers/NetworkConfigSerializer.java | 8 +- .../syncables/types/BufferSyncer.java | 1 - .../syncables/types/BufferViewConfig.java | 3 - .../syncables/types/BufferViewManager.java | 1 - .../libquassel/syncables/types/Identity.java | 2 - .../syncables/types/IgnoreListManager.java | 45 +++-- .../syncables/types/IrcChannel.java | 29 ++- .../libquassel/syncables/types/IrcUser.java | 123 ++++++------ .../libquassel/syncables/types/Network.java | 61 +++--- .../syncables/types/NetworkConfig.java | 1 - .../syncables/types/SyncableObject.java | 5 +- .../service/ClientBackgroundThread.java | 18 +- .../service/QuasselService.java | 2 +- .../quasseldroid_ng/ui/chat/ChatActivity.java | 179 +++++++++--------- .../ui/chat/chatview/ChatMessageRenderer.java | 8 - .../ui/chat/chatview/MessageAdapter.java | 10 +- .../ui/chat/drawer/BufferItem.java | 12 +- .../chat/drawer/BufferViewConfigWrapper.java | 9 - .../ui/chat/drawer/NetworkItem.java | 3 +- .../ui/editor/AdvancedEditor.java | 5 + .../ui/editor/FormattingHelper.java | 4 +- .../quasseldroid_ng/ui/theme/AppTheme.java | 12 +- .../quasseldroid_ng/ui/theme/ThemeUtil.java | 1 - .../java/de/kuschku/util/ServerAddress.java | 4 + .../CertificateDatabaseHandler.java | 152 +++++++++++++++ .../util/certificates/CertificateUtils.java | 56 ++++++ .../SQLiteCertificateManager.java | 67 +++++++ .../de/kuschku/util/irc/IrcFormatHelper.java | 1 - .../util/niohelpers/WrappedChannel.java | 33 +++- .../util/observables/ContentComparable.java | 1 + .../ChildParentObservableSortedList.java | 1 - .../lists/ObservableSortedList.java | 10 +- .../kuschku/util/ui/DateTimeFormatHelper.java | 32 ++-- .../java/de/kuschku/util/ui/MessageUtil.java | 12 +- 52 files changed, 836 insertions(+), 352 deletions(-) create mode 100644 app/src/main/java/de/kuschku/libquassel/events/CertificateAcceptedEvent.java create mode 100644 app/src/main/java/de/kuschku/libquassel/events/UnknownCertificateEvent.java create mode 100644 app/src/main/java/de/kuschku/libquassel/ssl/CertificateManager.java create mode 100644 app/src/main/java/de/kuschku/libquassel/ssl/QuasselTrustManager.java create mode 100644 app/src/main/java/de/kuschku/libquassel/ssl/UnknownCertificateException.java create mode 100644 app/src/main/java/de/kuschku/util/certificates/CertificateDatabaseHandler.java create mode 100644 app/src/main/java/de/kuschku/util/certificates/CertificateUtils.java create mode 100644 app/src/main/java/de/kuschku/util/certificates/SQLiteCertificateManager.java diff --git a/app/src/main/java/de/kuschku/libquassel/Client.java b/app/src/main/java/de/kuschku/libquassel/Client.java index 62fe02b78..d942bf33a 100644 --- a/app/src/main/java/de/kuschku/libquassel/Client.java +++ b/app/src/main/java/de/kuschku/libquassel/Client.java @@ -10,17 +10,17 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import de.kuschku.libquassel.events.LagChangedEvent; -import de.kuschku.libquassel.localtypes.NotificationManager; -import de.kuschku.libquassel.localtypes.backlogmanagers.BacklogManager; -import de.kuschku.libquassel.localtypes.backlogmanagers.SimpleBacklogManager; import de.kuschku.libquassel.events.ConnectionChangeEvent; +import de.kuschku.libquassel.events.LagChangedEvent; import de.kuschku.libquassel.events.StatusMessageEvent; import de.kuschku.libquassel.functions.types.HandshakeFunction; import de.kuschku.libquassel.functions.types.InitRequestFunction; import de.kuschku.libquassel.functions.types.RpcCallFunction; import de.kuschku.libquassel.localtypes.Buffer; import de.kuschku.libquassel.localtypes.Buffers; +import de.kuschku.libquassel.localtypes.NotificationManager; +import de.kuschku.libquassel.localtypes.backlogmanagers.BacklogManager; +import de.kuschku.libquassel.localtypes.backlogmanagers.SimpleBacklogManager; import de.kuschku.libquassel.message.Message; import de.kuschku.libquassel.objects.types.ClientInitAck; import de.kuschku.libquassel.objects.types.ClientLogin; @@ -28,7 +28,6 @@ import de.kuschku.libquassel.objects.types.SessionState; import de.kuschku.libquassel.primitives.types.BufferInfo; import de.kuschku.libquassel.primitives.types.QVariant; import de.kuschku.libquassel.syncables.types.BufferSyncer; -import de.kuschku.libquassel.syncables.types.BufferViewConfig; import de.kuschku.libquassel.syncables.types.BufferViewManager; import de.kuschku.libquassel.syncables.types.Identity; import de.kuschku.libquassel.syncables.types.IgnoreListManager; @@ -273,15 +272,15 @@ public class Client { return notificationManager; } + public long getLag() { + return lag; + } + public void setLag(long l) { lag = l; busProvider.sendEvent(new LagChangedEvent(lag)); } - public long getLag() { - return lag; - } - public IgnoreListManager getIgnoreListManager() { return ignoreListManager; } diff --git a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java index 71baec598..2ae9a6547 100644 --- a/app/src/main/java/de/kuschku/libquassel/CoreConnection.java +++ b/app/src/main/java/de/kuschku/libquassel/CoreConnection.java @@ -21,6 +21,7 @@ import java.util.concurrent.Executors; import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.events.HandshakeFailedEvent; +import de.kuschku.libquassel.events.UnknownCertificateEvent; import de.kuschku.libquassel.functions.types.HandshakeFunction; import de.kuschku.libquassel.functions.types.Heartbeat; import de.kuschku.libquassel.objects.types.ClientInit; @@ -30,6 +31,8 @@ import de.kuschku.libquassel.primitives.types.Protocol; import de.kuschku.libquassel.protocols.DatastreamPeer; import de.kuschku.libquassel.protocols.LegacyPeer; import de.kuschku.libquassel.protocols.RemotePeer; +import de.kuschku.libquassel.ssl.CertificateManager; +import de.kuschku.libquassel.ssl.UnknownCertificateException; import de.kuschku.util.ServerAddress; import de.kuschku.util.niohelpers.WrappedChannel; @@ -66,11 +69,14 @@ public class CoreConnection { private ConnectionChangeEvent.Status status = ConnectionChangeEvent.Status.DISCONNECTED; @Nullable private Client client; + @NonNull + private CertificateManager certificateManager; - public CoreConnection(@NonNull final ServerAddress address, @NonNull final ClientData clientData, @NonNull final BusProvider busProvider) { + public CoreConnection(@NonNull final ServerAddress address, @NonNull final ClientData clientData, @NonNull final BusProvider busProvider, @NonNull CertificateManager certificateManager) { this.address = address; this.clientData = clientData; this.busProvider = busProvider; + this.certificateManager = certificateManager; } @NonNull @@ -107,10 +113,8 @@ public class CoreConnection { /** * Closes the connection and interrupts all threads this connection has spawned. - * - * @throws IOException */ - public void close() throws IOException { + public void close() { assertNotNull(client); client.setConnectionStatus(ConnectionChangeEvent.Status.DISCONNECTED); @@ -121,8 +125,12 @@ public class CoreConnection { if (outputExecutor != null) outputExecutor.shutdownNow(); // Which we do exactly here - if (channel != null) channel.close(); - if (socket != null) socket.close(); + try { + if (channel != null) channel.close(); + if (socket != null) socket.close(); + } catch (Exception e) { + // We won’t report these issues, as these don’t matter to us anyway anymore + } } @Nullable @@ -177,11 +185,7 @@ public class CoreConnection { } public void onEventAsync(HandshakeFailedEvent event) { - try { - this.close(); - } catch (IOException e) { - e.printStackTrace(); - } + this.close(); } public void onEventAsync(@NonNull ConnectionChangeEvent event) { @@ -189,7 +193,23 @@ public class CoreConnection { } public void setCompression(boolean supportsCompression) { - if (supportsCompression) channel = WrappedChannel.withCompression(getChannel()); + if (supportsCompression) + channel = WrappedChannel.withCompression(getChannel()); + } + + private void setSSL(boolean supportsSSL) { + if (supportsSSL) { + try { + channel = WrappedChannel.withSSL(getChannel(), certificateManager, address); + } catch (Exception e) { + if (e.getCause() instanceof UnknownCertificateException) { + busProvider.sendEvent(new UnknownCertificateEvent((UnknownCertificateException) e.getCause())); + } else { + busProvider.sendEvent(new GeneralErrorEvent(e)); + } + close(); + } + } } public void setClient(@NonNull Client client) { @@ -220,6 +240,8 @@ public class CoreConnection { final Protocol protocol = ProtocolSerializer.get().deserialize(buffer); + // Wrap socket in SSL context if ssl is enabled + setSSL(protocol.protocolFlags.supportsSSL); // Wrap socket in deflater if compression is enabled setCompression(protocol.protocolFlags.supportsCompression); diff --git a/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java b/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java index 82bfaae07..d94d35666 100644 --- a/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java +++ b/app/src/main/java/de/kuschku/libquassel/ProtocolHandler.java @@ -5,14 +5,11 @@ import android.util.Log; import org.joda.time.DateTime; -import java.util.List; - import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.events.HandshakeFailedEvent; import de.kuschku.libquassel.events.LoginFailedEvent; import de.kuschku.libquassel.events.LoginSuccessfulEvent; -import de.kuschku.libquassel.exceptions.UnknownTypeException; import de.kuschku.libquassel.functions.types.Heartbeat; import de.kuschku.libquassel.functions.types.HeartbeatReply; import de.kuschku.libquassel.functions.types.InitDataFunction; @@ -26,7 +23,6 @@ import de.kuschku.libquassel.objects.types.ClientLoginReject; import de.kuschku.libquassel.objects.types.SessionInit; import de.kuschku.libquassel.primitives.types.BufferInfo; import de.kuschku.libquassel.syncables.SyncableRegistry; -import de.kuschku.libquassel.syncables.types.BufferViewConfig; import de.kuschku.libquassel.syncables.types.Identity; import de.kuschku.libquassel.syncables.types.SyncableObject; import de.kuschku.util.AndroidAssert; @@ -89,7 +85,7 @@ public class ProtocolHandler implements IProtocolHandler { public void onEventMainThread(@NonNull SyncFunction packedFunc) { try { final Object syncable = client.getObjectByIdentifier(packedFunc.className, packedFunc.objectName); - AndroidAssert.assertNotNull("Object not found: " + packedFunc.className+":"+packedFunc.objectName, syncable); + AndroidAssert.assertNotNull("Object not found: " + packedFunc.className + ":" + packedFunc.objectName, syncable); ReflectionUtils.invokeMethod(syncable, packedFunc.methodName, packedFunc.params); } catch (Exception e) { busProvider.sendEvent(new GeneralErrorEvent(e, packedFunc.toString())); diff --git a/app/src/main/java/de/kuschku/libquassel/events/CertificateAcceptedEvent.java b/app/src/main/java/de/kuschku/libquassel/events/CertificateAcceptedEvent.java new file mode 100644 index 000000000..d654aeadb --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/events/CertificateAcceptedEvent.java @@ -0,0 +1,11 @@ +package de.kuschku.libquassel.events; + +import java.security.cert.X509Certificate; + +public class CertificateAcceptedEvent { + public final X509Certificate certificate; + + public CertificateAcceptedEvent(X509Certificate certificate) { + this.certificate = certificate; + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/events/UnknownCertificateEvent.java b/app/src/main/java/de/kuschku/libquassel/events/UnknownCertificateEvent.java new file mode 100644 index 000000000..93a6e7883 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/events/UnknownCertificateEvent.java @@ -0,0 +1,20 @@ +package de.kuschku.libquassel.events; + +import java.security.cert.X509Certificate; + +import de.kuschku.libquassel.ssl.UnknownCertificateException; +import de.kuschku.util.ServerAddress; + +public class UnknownCertificateEvent { + public final X509Certificate certificate; + public final ServerAddress address; + + public UnknownCertificateEvent(X509Certificate certificate, ServerAddress address) { + this.certificate = certificate; + this.address = address; + } + + public UnknownCertificateEvent(UnknownCertificateException cause) { + this(cause.certificate, cause.address); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java index ffc10823f..881016095 100644 --- a/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/functions/serializers/HeartbeatReplySerializer.java @@ -5,11 +5,9 @@ import android.support.annotation.NonNull; import org.joda.time.DateTime; import java.util.Arrays; -import java.util.Collections; import java.util.List; import de.kuschku.libquassel.functions.FunctionType; -import de.kuschku.libquassel.functions.types.Heartbeat; import de.kuschku.libquassel.functions.types.HeartbeatReply; import de.kuschku.libquassel.primitives.types.QVariant; diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java b/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java index c07356b14..64c6bcc8c 100644 --- a/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/QueryBuffer.java @@ -31,9 +31,9 @@ public class QueryBuffer implements Buffer { @Override public BufferInfo.BufferStatus getStatus() { - return (user == null) ? BufferInfo.BufferStatus.OFFLINE : - (user.isAway()) ? BufferInfo.BufferStatus.AWAY : - BufferInfo.BufferStatus.ONLINE; + return (user == null) ? BufferInfo.BufferStatus.OFFLINE : + (user.isAway()) ? BufferInfo.BufferStatus.AWAY : + BufferInfo.BufferStatus.ONLINE; } @Nullable diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java index d36073bf8..90a005a47 100644 --- a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogFilter.java @@ -136,6 +136,14 @@ public class BacklogFilter implements UICallback { } } + public int getFilters() { + int filters = 0x00000000; + for (Message.Type type : filteredTypes) { + filters |= type.value; + } + return filters; + } + public void setFilters(int filters) { Set<Message.Type> removed = new HashSet<>(); for (Message.Type type : filteredTypes) { @@ -152,12 +160,4 @@ public class BacklogFilter implements UICallback { } } } - - public int getFilters() { - int filters = 0x00000000; - for (Message.Type type : filteredTypes) { - filters |= type.value; - } - return filters; - } } diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java index df71178de..6e10fd5d4 100644 --- a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/BacklogManager.java @@ -2,15 +2,12 @@ package de.kuschku.libquassel.localtypes.backlogmanagers; import android.support.annotation.IntRange; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v7.widget.RecyclerView; import java.util.List; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.message.Message; import de.kuschku.libquassel.syncables.types.SyncableObject; -import de.kuschku.util.observables.AutoScroller; import de.kuschku.util.observables.lists.ObservableSortedList; public abstract class BacklogManager<T extends BacklogManager<T>> extends SyncableObject<T> { diff --git a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java index e8236cc7a..c3bd0e981 100644 --- a/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java +++ b/app/src/main/java/de/kuschku/libquassel/localtypes/backlogmanagers/SimpleBacklogManager.java @@ -83,8 +83,8 @@ public class SimpleBacklogManager extends BacklogManager<SimpleBacklogManager> { ObservableSortedList<Message> backlog = backlogs.get(bufferId); int messageId = (backlog == null) ? -1 : - (backlog.last() == null) ? -1 : - backlog.last().messageId; + (backlog.last() == null) ? -1 : + backlog.last().messageId; requestBacklog(bufferId, -1, messageId, count, 0); } diff --git a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java index abe83b399..a5a0870a3 100644 --- a/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/primitives/serializers/MessageSerializer.java @@ -3,7 +3,6 @@ package de.kuschku.libquassel.primitives.serializers; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import de.kuschku.libquassel.primitives.types.BufferInfo; import org.joda.time.DateTime; import java.io.IOException; @@ -11,6 +10,7 @@ import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import de.kuschku.libquassel.message.Message; +import de.kuschku.libquassel.primitives.types.BufferInfo; import static de.kuschku.util.AndroidAssert.assertNotNull; diff --git a/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java b/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java index ed1322d3f..46edc8cc6 100644 --- a/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java +++ b/app/src/main/java/de/kuschku/libquassel/protocols/DatastreamPeer.java @@ -2,7 +2,6 @@ package de.kuschku.libquassel.protocols; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; import com.google.common.base.Function; import com.google.common.collect.Lists; @@ -14,11 +13,9 @@ import java.nio.BufferOverflowException; import java.nio.BufferUnderflowException; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -52,6 +49,7 @@ import de.kuschku.libquassel.primitives.types.QVariant; import de.kuschku.util.niohelpers.Helper; import de.kuschku.util.niohelpers.WrappedChannel; +import static de.kuschku.util.AndroidAssert.assertFalse; import static de.kuschku.util.AndroidAssert.assertNotNull; /** @@ -120,6 +118,7 @@ public class DatastreamPeer implements RemotePeer { public void onEventBackgroundThread(@NonNull SyncFunction func) { assertNotNull(connection.getOutputExecutor()); + assertFalse(connection.getOutputExecutor().isShutdown()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<SyncFunction>get(), UnpackedSyncFunctionSerializer.get().serialize(func) @@ -128,6 +127,7 @@ public class DatastreamPeer implements RemotePeer { public void onEventBackgroundThread(@NonNull RpcCallFunction func) { assertNotNull(connection.getOutputExecutor()); + assertFalse(connection.getOutputExecutor().isShutdown()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<RpcCallFunction>get(), UnpackedRpcCallFunctionSerializer.get().serialize(func) @@ -136,6 +136,7 @@ public class DatastreamPeer implements RemotePeer { public void onEventBackgroundThread(@NonNull InitRequestFunction func) { assertNotNull(connection.getOutputExecutor()); + assertFalse(connection.getOutputExecutor().isShutdown()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<InitRequestFunction>get(), InitRequestFunctionSerializer.get().serializePacked(func) @@ -144,6 +145,7 @@ public class DatastreamPeer implements RemotePeer { public void onEventBackgroundThread(@NonNull InitDataFunction func) { assertNotNull(connection.getOutputExecutor()); + assertFalse(connection.getOutputExecutor().isShutdown()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<InitDataFunction>get(), InitDataFunctionSerializer.get().serialize(func) @@ -152,6 +154,7 @@ public class DatastreamPeer implements RemotePeer { public void onEventBackgroundThread(@NonNull Heartbeat func) { assertNotNull(connection.getOutputExecutor()); + assertFalse(connection.getOutputExecutor().isShutdown()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<InitDataFunction>get(), HeartbeatSerializer.get().serialize(func) @@ -160,6 +163,7 @@ public class DatastreamPeer implements RemotePeer { public void onEventBackgroundThread(@NonNull HeartbeatReply func) { assertNotNull(connection.getOutputExecutor()); + assertFalse(connection.getOutputExecutor().isShutdown()); connection.getOutputExecutor().submit(new OutputRunnable<>( VariantVariantListSerializer.<InitDataFunction>get(), HeartbeatReplySerializer.get().serialize(func) @@ -168,6 +172,7 @@ public class DatastreamPeer implements RemotePeer { public void onEventBackgroundThread(@NonNull HandshakeFunction func) { assertNotNull(connection.getOutputExecutor()); + assertFalse(connection.getOutputExecutor().isShutdown()); Map<String, QVariant> variantMap = MessageTypeRegistry.toVariantMap(func.data).data; assertNotNull(variantMap); connection.getOutputExecutor().submit(new OutputRunnable<>( @@ -273,6 +278,7 @@ public class DatastreamPeer implements RemotePeer { private class ParseRunnable implements Runnable { ByteBuffer buffer; + public ParseRunnable(ByteBuffer buffer) { this.buffer = buffer; } @@ -291,7 +297,7 @@ public class DatastreamPeer implements RemotePeer { } else { handlePackedFunc(data); } - } catch (BufferUnderflowException|BufferOverflowException e) { + } catch (BufferUnderflowException | BufferOverflowException e) { Helper.printHexDump(buffer.array()); busProvider.sendEvent(new GeneralErrorEvent(e)); } catch (Exception e) { diff --git a/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java b/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java index 603fdd0c1..1d88e0ccd 100644 --- a/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java +++ b/app/src/main/java/de/kuschku/libquassel/protocols/LegacyPeer.java @@ -13,7 +13,6 @@ import java.util.concurrent.Executors; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.CoreConnection; -import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.functions.FunctionType; import de.kuschku.libquassel.functions.serializers.HeartbeatReplySerializer; @@ -37,12 +36,11 @@ import de.kuschku.libquassel.primitives.serializers.PrimitiveSerializer; import de.kuschku.libquassel.primitives.serializers.VariantSerializer; import de.kuschku.libquassel.primitives.serializers.VariantVariantListSerializer; import de.kuschku.libquassel.primitives.types.QVariant; -import de.kuschku.util.AndroidAssert; import de.kuschku.util.niohelpers.WrappedChannel; import static de.kuschku.libquassel.primitives.QMetaType.Type.QVariantList; import static de.kuschku.libquassel.primitives.QMetaType.Type.QVariantMap; -import static de.kuschku.util.AndroidAssert.*; +import static de.kuschku.util.AndroidAssert.assertNotNull; /** * A helper class processing incoming and outgoing messages. @@ -175,6 +173,7 @@ public class LegacyPeer implements RemotePeer { private class ParseRunnable implements Runnable { ByteBuffer buffer; + public ParseRunnable(ByteBuffer buffer) { this.buffer = buffer; } diff --git a/app/src/main/java/de/kuschku/libquassel/ssl/CertificateManager.java b/app/src/main/java/de/kuschku/libquassel/ssl/CertificateManager.java new file mode 100644 index 000000000..dc9e947a3 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/ssl/CertificateManager.java @@ -0,0 +1,24 @@ +package de.kuschku.libquassel.ssl; + +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import de.kuschku.util.ServerAddress; + +public interface CertificateManager { + boolean isTrusted(X509Certificate certificate, ServerAddress core); + + boolean addCertificate(X509Certificate certificate, ServerAddress core); + + boolean removeCertificate(X509Certificate certificate, ServerAddress core); + + boolean removeAllCertificates(ServerAddress core); + + List<String> findCertificates(ServerAddress core); + + Map<String, Collection<String>> findAllCertificates(); + + void checkTrusted(X509Certificate certificate, ServerAddress address) throws UnknownCertificateException; +} diff --git a/app/src/main/java/de/kuschku/libquassel/ssl/QuasselTrustManager.java b/app/src/main/java/de/kuschku/libquassel/ssl/QuasselTrustManager.java new file mode 100644 index 000000000..bcff02275 --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/ssl/QuasselTrustManager.java @@ -0,0 +1,70 @@ +package de.kuschku.libquassel.ssl; + +import android.support.annotation.NonNull; +import android.util.Log; + +import java.security.GeneralSecurityException; +import java.security.KeyStore; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import de.kuschku.util.ServerAddress; +import de.kuschku.util.certificates.CertificateUtils; + +public class QuasselTrustManager implements X509TrustManager { + @NonNull + private final X509TrustManager wrapped; + @NonNull + private final CertificateManager certificateManager; + @NonNull + private ServerAddress address; + + public QuasselTrustManager(@NonNull X509TrustManager wrapped, @NonNull CertificateManager certificateManager, @NonNull ServerAddress address) { + this.wrapped = wrapped; + this.certificateManager = certificateManager; + this.address = address; + } + + public static QuasselTrustManager fromFactory(@NonNull TrustManagerFactory factory, @NonNull CertificateManager certificateManager, @NonNull ServerAddress address) throws GeneralSecurityException { + TrustManager[] managers = factory.getTrustManagers(); + for (TrustManager manager : managers) { + if (manager instanceof X509TrustManager) { + return new QuasselTrustManager((X509TrustManager) manager, certificateManager, address); + } + } + throw new GeneralSecurityException("Couldn’t find trustmanager provided by factory"); + } + + public static QuasselTrustManager fromDefault(@NonNull CertificateManager certificateManager, @NonNull ServerAddress address) throws GeneralSecurityException { + TrustManagerFactory factory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + factory.init((KeyStore) null); + return fromFactory(factory, certificateManager, address); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + wrapped.checkClientTrusted(chain, authType); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + try { + wrapped.checkServerTrusted(chain, authType); + chain[0].checkValidity(); + if (!CertificateUtils.getHostnames(chain[0]).contains(address.host)) + throw new CertificateException("Hostname not in certificate"); + } catch (CertificateException e) { + certificateManager.checkTrusted(chain[0], address); + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return wrapped.getAcceptedIssuers(); + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/ssl/UnknownCertificateException.java b/app/src/main/java/de/kuschku/libquassel/ssl/UnknownCertificateException.java new file mode 100644 index 000000000..bec64bc1d --- /dev/null +++ b/app/src/main/java/de/kuschku/libquassel/ssl/UnknownCertificateException.java @@ -0,0 +1,16 @@ +package de.kuschku.libquassel.ssl; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import de.kuschku.util.ServerAddress; + +public class UnknownCertificateException extends CertificateException { + public final X509Certificate certificate; + public final ServerAddress address; + + public UnknownCertificateException(X509Certificate certificate, ServerAddress address) { + this.certificate = certificate; + this.address = address; + } +} diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java index 2e089880b..9aab84c3f 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/AliasManagerSerializer.java @@ -17,15 +17,15 @@ public class AliasManagerSerializer implements ObjectSerializer<AliasManager> { @NonNull private static final AliasManagerSerializer serializer = new AliasManagerSerializer(); + private AliasManagerSerializer() { + + } + @NonNull public static AliasManagerSerializer get() { return serializer; } - private AliasManagerSerializer() { - - } - @Nullable @Override public QVariant<Map<String, QVariant>> toVariantMap(@NonNull AliasManager data) { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java index 2a114c537..dcd8885a5 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/IgnoreListManagerSerializer.java @@ -15,14 +15,15 @@ import de.kuschku.libquassel.syncables.types.IgnoreListManager; public class IgnoreListManagerSerializer implements ObjectSerializer<IgnoreListManager> { private static IgnoreListManagerSerializer serializer = new IgnoreListManagerSerializer(); - public static IgnoreListManagerSerializer get() { - return serializer; - } private IgnoreListManagerSerializer() { } + public static IgnoreListManagerSerializer get() { + return serializer; + } + @Nullable @Override public QVariant<Map<String, QVariant>> toVariantMap(@NonNull IgnoreListManager data) { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java index 277b08eaf..a0640ae66 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/serializers/NetworkConfigSerializer.java @@ -11,18 +11,18 @@ import de.kuschku.libquassel.functions.types.UnpackedFunction; import de.kuschku.libquassel.objects.serializers.ObjectSerializer; import de.kuschku.libquassel.primitives.types.QVariant; import de.kuschku.libquassel.syncables.types.NetworkConfig; -import de.kuschku.libquassel.syncables.types.SyncableObject; public class NetworkConfigSerializer implements ObjectSerializer<NetworkConfig> { private static NetworkConfigSerializer serializer = new NetworkConfigSerializer(); - public static NetworkConfigSerializer get() { - return serializer; - } private NetworkConfigSerializer() { } + public static NetworkConfigSerializer get() { + return serializer; + } + @Nullable @Override public QVariant<Map<String, QVariant>> toVariantMap(@NonNull NetworkConfig data) { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java index 40cae47dc..1e23c7246 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferSyncer.java @@ -11,7 +11,6 @@ import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.message.Message; import de.kuschku.libquassel.primitives.types.QVariant; -import de.kuschku.libquassel.syncables.serializers.AliasManagerSerializer; import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; import de.kuschku.util.observables.lists.ObservableSortedList; diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java index 177f3d878..45aefb051 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewConfig.java @@ -1,9 +1,7 @@ package de.kuschku.libquassel.syncables.types; import android.support.annotation.NonNull; -import android.util.Log; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -290,7 +288,6 @@ public class BufferViewConfig extends SyncableObject<BufferViewConfig> { } - @NonNull public IObservableList<ElementCallback<Integer>, Integer> getNetworkList() { return NetworkList; diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java index c6e927881..8c329eefa 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/BufferViewManager.java @@ -10,7 +10,6 @@ import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.primitives.types.QVariant; -import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; import de.kuschku.libquassel.syncables.serializers.BufferViewManagerSerializer; public class BufferViewManager extends SyncableObject<BufferViewManager> { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java index 49406768c..85d18f607 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/Identity.java @@ -2,7 +2,6 @@ package de.kuschku.libquassel.syncables.types; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.util.Log; import java.util.List; import java.util.Map; @@ -12,7 +11,6 @@ import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.primitives.types.QVariant; import de.kuschku.libquassel.syncables.Syncable; -import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; import de.kuschku.libquassel.syncables.serializers.IdentitySerializer; public class Identity extends SyncableObject<Identity> { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java index 8904263a1..876c9e565 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IgnoreListManager.java @@ -11,7 +11,6 @@ import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.message.Message; import de.kuschku.libquassel.primitives.types.QVariant; -import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; import de.kuschku.libquassel.syncables.serializers.IgnoreListManagerSerializer; import static de.kuschku.util.AndroidAssert.assertEquals; @@ -22,7 +21,7 @@ public class IgnoreListManager extends SyncableObject<IgnoreListManager> { public IgnoreListManager(List<Integer> scope, List<Integer> ignoreType, List<Boolean> isActive, List<String> scopeRule, List<Boolean> isRegEx, List<Integer> strictness, List<String> ignoreRule) { - assertEquals(scope.size(),ignoreType.size(), isActive.size(),scopeRule.size(), isRegEx.size(), strictness.size(), ignoreRule.size()); + assertEquals(scope.size(), ignoreType.size(), isActive.size(), scopeRule.size(), isRegEx.size(), strictness.size(), ignoreRule.size()); for (int i = 0; i < scope.size(); i++) { ignoreRules.add(new IgnoreRule( @@ -94,15 +93,21 @@ public class IgnoreListManager extends SyncableObject<IgnoreListManager> { HARD(2); public final int id; + Strictness(int id) { this.id = id; } + public static Strictness of(int id) { switch (id) { - case 0: return UNMATCHED; - case 1: return SOFT; - case 2: return HARD; - default: return INVALID; + case 0: + return UNMATCHED; + case 1: + return SOFT; + case 2: + return HARD; + default: + return INVALID; } } } @@ -114,15 +119,21 @@ public class IgnoreListManager extends SyncableObject<IgnoreListManager> { CTCP_IGNORE(2); public final int id; + Type(int id) { this.id = id; } + public static Type of(int id) { switch (id) { - case 0: return SENDER_IGNORE; - case 1: return MESSAGE_IGNORE; - case 2: return CTCP_IGNORE; - default: return INVALID; + case 0: + return SENDER_IGNORE; + case 1: + return MESSAGE_IGNORE; + case 2: + return CTCP_IGNORE; + default: + return INVALID; } } } @@ -134,15 +145,21 @@ public class IgnoreListManager extends SyncableObject<IgnoreListManager> { CHANNEL_SCOPE(2); public final int id; + Scope(int id) { this.id = id; } + public static Scope of(int id) { switch (id) { - case 0: return GLOBAL_SCOPE; - case 1: return NETWORK_SCOPE; - case 2: return CHANNEL_SCOPE; - default: return INVALID; + case 0: + return GLOBAL_SCOPE; + case 1: + return NETWORK_SCOPE; + case 2: + return CHANNEL_SCOPE; + default: + return INVALID; } } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java index ab47a9728..5a1b8e9b7 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcChannel.java @@ -11,7 +11,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; @@ -19,17 +18,22 @@ import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.primitives.types.QVariant; import de.kuschku.libquassel.syncables.Synced; import de.kuschku.libquassel.syncables.serializers.IrcChannelSerializer; -import de.kuschku.util.AndroidAssert; -import static de.kuschku.util.AndroidAssert.*; +import static de.kuschku.util.AndroidAssert.assertNotNull; public class IrcChannel extends SyncableObject<IrcChannel> { - @Synced private String name; - @Synced private String topic; - @Synced private String password; - @Synced private Map<String, String> UserModes; - @Synced private Map<String, Object> ChanModes; - @Synced private boolean encrypted; + @Synced + private String name; + @Synced + private String topic; + @Synced + private String password; + @Synced + private Map<String, String> UserModes; + @Synced + private Map<String, Object> ChanModes; + @Synced + private boolean encrypted; @Nullable private Network network; @@ -128,9 +132,11 @@ public class IrcChannel extends SyncableObject<IrcChannel> { public void addChannelMode(Character mode, String params) { addChannelMode(String.copyValueOf(new char[]{mode}), params); } + public void addChannelMode(char mode, String params) { addChannelMode(String.copyValueOf(new char[]{mode}), params); } + public void addChannelMode(String mode, String params) { assertNotNull(network); @@ -157,12 +163,15 @@ public class IrcChannel extends SyncableObject<IrcChannel> { break; } } + public void removeChannelMode(Character mode, String params) { removeChannelMode(String.copyValueOf(new char[]{mode}), params); } + public void removeChannelMode(char mode, String params) { removeChannelMode(String.copyValueOf(new char[]{mode}), params); } + public void removeChannelMode(String mode, String params) { assertNotNull(network); @@ -189,9 +198,11 @@ public class IrcChannel extends SyncableObject<IrcChannel> { public boolean hasMode(Character mode) { return hasMode(String.copyValueOf(new char[]{mode})); } + public boolean hasMode(char mode) { return hasMode(String.copyValueOf(new char[]{mode})); } + public boolean hasMode(String mode) { assertNotNull(network); diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java index 2aa1d62ab..e38da03c3 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/IrcUser.java @@ -12,7 +12,6 @@ import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.primitives.types.QVariant; -import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; import de.kuschku.libquassel.syncables.serializers.IrcUserSerializer; import static de.kuschku.util.AndroidAssert.assertNotNull; @@ -64,129 +63,129 @@ public class IrcUser extends SyncableObject<IrcUser> { return server; } - public String getIrcOperator() { - return ircOperator; + public void setServer(String server) { + this.server = server; } - public boolean isAway() { - return away; + public String getIrcOperator() { + return ircOperator; } - public int getLastAwayMessage() { - return lastAwayMessage; + public void setIrcOperator(String ircOperator) { + this.ircOperator = ircOperator; } - public DateTime getIdleTime() { - return idleTime; + public boolean isAway() { + return away; } - public String getWhoisServiceReply() { - return whoisServiceReply; + public void setAway(boolean away) { + this.away = away; } - public String getSuserHost() { - return suserHost; + public int getLastAwayMessage() { + return lastAwayMessage; } - public String getNick() { - return nick; + public void setLastAwayMessage(int lastAwayMessage) { + this.lastAwayMessage = lastAwayMessage; } - public String getRealName() { - return realName; + public DateTime getIdleTime() { + return idleTime; } - public String getAwayMessage() { - return awayMessage; + public void setIdleTime(DateTime idleTime) { + this.idleTime = idleTime; } - public DateTime getLoginTime() { - return loginTime; + public String getWhoisServiceReply() { + return whoisServiceReply; } - public boolean isEncrypted() { - return encrypted; + public void setWhoisServiceReply(String whoisServiceReply) { + this.whoisServiceReply = whoisServiceReply; } - @NonNull - public List<String> getChannels() { - return channels; + public String getSuserHost() { + return suserHost; } - public String getHost() { - return host; + public void setSuserHost(String suserHost) { + this.suserHost = suserHost; } - public String getUserModes() { - return userModes; + public String getNick() { + return nick; } - public String getUser() { - return user; + public void setNick(String nick) { + this.nick = nick; } /* BEGIN SYNC */ - public void setServer(String server) { - this.server = server; - } - - public void setIrcOperator(String ircOperator) { - this.ircOperator = ircOperator; - } - - public void setAway(boolean away) { - this.away = away; - } - - public void setLastAwayMessage(int lastAwayMessage) { - this.lastAwayMessage = lastAwayMessage; - } - - public void setIdleTime(DateTime idleTime) { - this.idleTime = idleTime; - } - - public void setWhoisServiceReply(String whoisServiceReply) { - this.whoisServiceReply = whoisServiceReply; - } - - public void setSuserHost(String suserHost) { - this.suserHost = suserHost; - } - - public void setNick(String nick) { - this.nick = nick; + public String getRealName() { + return realName; } public void setRealName(String realName) { this.realName = realName; } + public String getAwayMessage() { + return awayMessage; + } + public void setAwayMessage(String awayMessage) { this.awayMessage = awayMessage; } + public DateTime getLoginTime() { + return loginTime; + } + public void setLoginTime(DateTime loginTime) { this.loginTime = loginTime; } + public boolean isEncrypted() { + return encrypted; + } + public void setEncrypted(boolean encrypted) { this.encrypted = encrypted; } + @NonNull + public List<String> getChannels() { + return channels; + } + public void setChannels(@NonNull List<String> channels) { this.channels = channels; } + public String getHost() { + return host; + } + public void setHost(String host) { this.host = host; } + public String getUserModes() { + return userModes; + } + public void setUserModes(String userModes) { this.userModes = userModes; } + public String getUser() { + return user; + } + public void setUser(String user) { this.user = user; } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java index bb7ed09a3..cfb73a1c4 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/Network.java @@ -197,6 +197,10 @@ public class Network extends SyncableObject<Network> implements ContentComparabl return channels; } + public void setChannels(@NonNull Map<String, IrcChannel> channels) { + this.channels = channels; + } + public void addIrcChannel(String channelName) { IrcChannel ircChannel = new IrcChannel( channelName, @@ -210,10 +214,6 @@ public class Network extends SyncableObject<Network> implements ContentComparabl channels.put(channelName, ircChannel); } - public void setChannels(@NonNull Map<String, IrcChannel> channels) { - this.channels = channels; - } - @NonNull public List<NetworkServer> getServerList() { return ServerList; @@ -562,25 +562,6 @@ public class Network extends SyncableObject<Network> implements ContentComparabl this.client = client; } - public static class IrcMode { - public final int rank; - public final String prefix; - - public IrcMode(int rank, String prefix) { - this.rank = rank; - this.prefix = prefix; - } - - @NonNull - @Override - public String toString() { - return "IrcMode{" + - "rank=" + rank + - ", prefix='" + prefix + '\'' + - '}'; - } - } - public ChannelModeType channelModeType(char mode) { return channelModeType(String.copyValueOf(new char[]{mode})); } @@ -597,11 +578,16 @@ public class Network extends SyncableObject<Network> implements ContentComparabl for (int i = 0; i < chanModes.length; i++) { if (chanModes[i].contains(mode)) { switch (i) { - case 0: return ChannelModeType.A_CHANMODE; - case 1: return ChannelModeType.B_CHANMODE; - case 2: return ChannelModeType.C_CHANMODE; - case 3: return ChannelModeType.D_CHANMODE; - default: return ChannelModeType.NOT_A_CHANMODE; + case 0: + return ChannelModeType.A_CHANMODE; + case 1: + return ChannelModeType.B_CHANMODE; + case 2: + return ChannelModeType.C_CHANMODE; + case 3: + return ChannelModeType.D_CHANMODE; + default: + return ChannelModeType.NOT_A_CHANMODE; } } } @@ -618,4 +604,23 @@ public class Network extends SyncableObject<Network> implements ContentComparabl C_CHANMODE, D_CHANMODE } + + public static class IrcMode { + public final int rank; + public final String prefix; + + public IrcMode(int rank, String prefix) { + this.rank = rank; + this.prefix = prefix; + } + + @NonNull + @Override + public String toString() { + return "IrcMode{" + + "rank=" + rank + + ", prefix='" + prefix + '\'' + + '}'; + } + } } diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java index 1540f2994..32d885b45 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/NetworkConfig.java @@ -8,7 +8,6 @@ import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.primitives.types.QVariant; -import de.kuschku.libquassel.syncables.serializers.BufferSyncerSerializer; import de.kuschku.libquassel.syncables.serializers.NetworkConfigSerializer; public class NetworkConfig extends SyncableObject<NetworkConfig> { diff --git a/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java b/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java index 1b7830386..3e5f6251f 100644 --- a/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java +++ b/app/src/main/java/de/kuschku/libquassel/syncables/types/SyncableObject.java @@ -10,7 +10,6 @@ import de.kuschku.libquassel.BusProvider; import de.kuschku.libquassel.Client; import de.kuschku.libquassel.functions.types.InitDataFunction; import de.kuschku.libquassel.functions.types.SyncFunction; -import de.kuschku.libquassel.message.Message; import de.kuschku.libquassel.primitives.types.QVariant; import static de.kuschku.util.AndroidAssert.assertNotNull; @@ -51,8 +50,10 @@ public abstract class SyncableObject<T extends SyncableObject<T>> { public abstract void init(@NonNull InitDataFunction function, @NonNull BusProvider provider, @NonNull Client client); - public void doInit() {} + public void doInit() { + } public abstract void update(T from); + public abstract void update(Map<String, QVariant> from); } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/ClientBackgroundThread.java b/app/src/main/java/de/kuschku/quasseldroid_ng/service/ClientBackgroundThread.java index cf6958773..41d02734c 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/ClientBackgroundThread.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/ClientBackgroundThread.java @@ -1,5 +1,6 @@ package de.kuschku.quasseldroid_ng.service; +import android.content.Context; import android.support.annotation.NonNull; import java.io.IOException; @@ -10,13 +11,15 @@ import de.kuschku.libquassel.CoreConnection; import de.kuschku.libquassel.ProtocolHandler; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.protocols.RemotePeer; +import de.kuschku.libquassel.ssl.CertificateManager; import de.kuschku.util.CompatibilityUtils; import de.kuschku.util.ServerAddress; +import de.kuschku.util.certificates.SQLiteCertificateManager; public class ClientBackgroundThread implements Runnable { @NonNull private static final ClientData CLIENT_DATA = new ClientData( - new ClientData.FeatureFlags(false, true), + new ClientData.FeatureFlags(true, true), new byte[]{RemotePeer.DATASTREAM, RemotePeer.LEGACY}, "QuasselDroid-ng 0.1 | libquassel 0.2", RemotePeer.PROTOCOL_VERSION_LEGACY @@ -28,10 +31,13 @@ public class ClientBackgroundThread implements Runnable { public final CoreConnection connection; @NonNull public final ProtocolHandler handler; + @NonNull + public final CertificateManager certificateManager; - public ClientBackgroundThread(@NonNull BusProvider provider, @NonNull ServerAddress address) { + public ClientBackgroundThread(@NonNull BusProvider provider, @NonNull ServerAddress address, @NonNull Context context) { this.provider = provider; - this.connection = new CoreConnection(address, CLIENT_DATA, provider); + this.certificateManager = new SQLiteCertificateManager(context); + this.connection = new CoreConnection(address, CLIENT_DATA, provider, certificateManager); this.handler = new ProtocolHandler(provider); this.handler.client.setClientData(CLIENT_DATA); this.connection.setClient(handler.client); @@ -47,10 +53,6 @@ public class ClientBackgroundThread implements Runnable { } public void close() { - try { - connection.close(); - } catch (IOException e) { - provider.sendEvent(new GeneralErrorEvent(e)); - } + connection.close(); } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.java b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.java index 13fb9edac..b621d9aa2 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/service/QuasselService.java @@ -39,7 +39,7 @@ public class QuasselService extends Service { public class LocalBinder extends Binder { public void startBackgroundThread(@NonNull BusProvider provider, @NonNull ServerAddress address) { - bgThread = new ClientBackgroundThread(provider, address); + bgThread = new ClientBackgroundThread(provider, address, QuasselService.this); new Thread(bgThread).start(); } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java index 38fb5b2e5..2f07658ca 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/ChatActivity.java @@ -4,7 +4,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.res.Configuration; import android.os.Bundle; import android.os.IBinder; import android.support.annotation.IntRange; @@ -32,7 +31,6 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.google.common.base.Splitter; import com.mikepenz.fastadapter.FastAdapter; import com.mikepenz.fastadapter.IExpandable; -import com.mikepenz.fastadapter.IIdentifyable; import com.mikepenz.fastadapter.IItem; import com.mikepenz.fastadapter.adapters.ItemAdapter; import com.mikepenz.materialdrawer.AccountHeader; @@ -46,7 +44,6 @@ import com.sothree.slidinguppanel.SlidingUpPanelLayout; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -61,6 +58,7 @@ import de.kuschku.libquassel.events.BacklogReceivedEvent; import de.kuschku.libquassel.events.ConnectionChangeEvent; import de.kuschku.libquassel.events.GeneralErrorEvent; import de.kuschku.libquassel.events.LagChangedEvent; +import de.kuschku.libquassel.events.UnknownCertificateEvent; import de.kuschku.libquassel.localtypes.Buffer; import de.kuschku.libquassel.localtypes.ChannelBuffer; import de.kuschku.libquassel.localtypes.backlogmanagers.BacklogFilter; @@ -73,13 +71,12 @@ import de.kuschku.quasseldroid_ng.service.ClientBackgroundThread; import de.kuschku.quasseldroid_ng.service.QuasselService; import de.kuschku.quasseldroid_ng.ui.chat.chatview.MessageAdapter; import de.kuschku.quasseldroid_ng.ui.chat.drawer.BufferViewConfigWrapper; -import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkItem; import de.kuschku.quasseldroid_ng.ui.editor.AdvancedEditor; import de.kuschku.quasseldroid_ng.ui.theme.AppContext; import de.kuschku.quasseldroid_ng.ui.theme.AppTheme; import de.kuschku.quasseldroid_ng.ui.theme.ThemeUtil; import de.kuschku.util.ServerAddress; -import de.kuschku.util.backports.Stream; +import de.kuschku.util.certificates.CertificateUtils; import de.kuschku.util.instancestateutil.Storable; import de.kuschku.util.instancestateutil.Store; import de.kuschku.util.keyboardutils.DialogKeyboardUtil; @@ -91,85 +88,38 @@ import static de.kuschku.util.AndroidAssert.assertNotNull; @UiThread public class ChatActivity extends AppCompatActivity { + @NonNull + private final Status status = new Status(); @Bind(R.id.toolbar) Toolbar toolbar; @Bind(R.id.sliding_layout) SlidingUpPanelLayout slidingLayout; @Bind(R.id.sliding_layout_history) SlidingUpPanelLayout slidingLayoutHistory; - @Bind(R.id.chatline_scroller) ScrollView chatlineScroller; @Bind(R.id.chatline) AppCompatEditText chatline; @Bind(R.id.send) AppCompatImageButton send; - @Bind(R.id.msg_history) RecyclerView msgHistory; - @Bind(R.id.swipe_view) SwipeRefreshLayout swipeView; @Bind(R.id.messages) RecyclerView messages; - @Bind(R.id.formatting_menu) ActionMenuView formattingMenu; @Bind(R.id.formatting_toolbar) Toolbar formattingToolbar; - - @PreferenceWrapper(BuildConfig.APPLICATION_ID) - public static abstract class Settings { - @StringPreference("QUASSEL_LIGHT") - String theme; - @BooleanPreference(false) boolean fullHostmask; - @IntPreference(2) int textSize; - @BooleanPreference(true) boolean mircColors; - - @StringPreference("") String lastHost; - @IntPreference(4242) int lastPort; - @StringPreference("") String lastUsername; - @StringPreference("") String lastPassword; - } - private AppContext context = new AppContext(); - - @NonNull - private final Status status = new Status(); - private static class Status extends Storable { - @Store int bufferId = -1; - @Store int bufferViewConfigId = -1; - } - private ServiceInterface serviceInterface = new ServiceInterface(); - private class ServiceInterface { - private void connect(@NonNull ServerAddress address) { - assertNotNull(binder); - disconnect(); - - BusProvider provider = new BusProvider(); - provider.event.register(ChatActivity.this); - binder.startBackgroundThread(provider, address); - onConnectionEstablished(); - } - - private void disconnect() { - if (context.getProvider() != null) - context.getProvider().event.unregister(this); - context.setProvider(null); - context.setClient(null); - } - } - private QuasselService.LocalBinder binder; - private MessageAdapter messageAdapter; - private AccountHeader accountHeader; private Drawer drawerLeft; private BufferViewConfigWrapper wrapper; private AdvancedEditor editor; - private ServiceConnection serviceConnection = new ServiceConnection() { @UiThread public void onServiceConnected(@NonNull ComponentName cn, @NonNull IBinder service) { @@ -197,6 +147,20 @@ public class ChatActivity extends AppCompatActivity { } }; + private static void updateNoColor(Buffer buffer, Menu menu) { + boolean isNoColor = isNoColor(buffer); + menu.findItem(R.id.format_bold).setEnabled(!isNoColor); + menu.findItem(R.id.format_italic).setEnabled(!isNoColor); + menu.findItem(R.id.format_underline).setEnabled(!isNoColor); + menu.findItem(R.id.format_paint).setEnabled(!isNoColor); + menu.findItem(R.id.format_fill).setEnabled(!isNoColor); + } + + public static boolean isNoColor(Buffer buffer) { + return buffer instanceof ChannelBuffer && ((ChannelBuffer) buffer).getChannel() != null && + ((ChannelBuffer) buffer).getChannel().getD_ChanModes().contains("c"); + } + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { setupContext(); @@ -381,17 +345,6 @@ public class ChatActivity extends AppCompatActivity { return super.onCreateOptionsMenu(menu); } - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - // Checks whether a hardware keyboard is available - if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) { - - } else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) { - - } - } - @Override public boolean onOptionsItemSelected(MenuItem item) { List<Integer> filterSettings = Arrays.asList( @@ -403,7 +356,9 @@ public class ChatActivity extends AppCompatActivity { Message.Type.Topic.value ); int[] filterSettingsInts = new int[filterSettings.size()]; - for (int i = 0; i < filterSettingsInts.length; i++) { filterSettingsInts[i] = filterSettings.get(i); } + for (int i = 0; i < filterSettingsInts.length; i++) { + filterSettingsInts[i] = filterSettings.get(i); + } switch (item.getItemId()) { case R.id.action_hide_events: { @@ -436,12 +391,11 @@ public class ChatActivity extends AppCompatActivity { .onPositive((dialog, which) -> { int filters = 0x00000000; if (dialog.getSelectedIndices() != null) - for (int i : dialog.getSelectedIndices()) { - filters |= filterSettings.get(i); - } + for (int i : dialog.getSelectedIndices()) { + filters |= filterSettings.get(i); + } backlogFilter.setFilters(filters); }) - .buttonRippleColorAttr(R.attr.colorAccentFocus) .build() .show(); } @@ -498,7 +452,6 @@ public class ChatActivity extends AppCompatActivity { return true; }) .negativeColor(context.getThemeUtil().res.colorForeground) - .buttonRippleColor(context.getThemeUtil().res.colorAccentFocus) .build() .show(); } @@ -563,23 +516,6 @@ public class ChatActivity extends AppCompatActivity { } } - private static void updateNoColor(Buffer buffer, Menu menu) { - boolean isNoColor = isNoColor(buffer); - menu.findItem(R.id.format_bold).setEnabled(!isNoColor); - menu.findItem(R.id.format_italic).setEnabled(!isNoColor); - menu.findItem(R.id.format_underline).setEnabled(!isNoColor); - menu.findItem(R.id.format_paint).setEnabled(!isNoColor); - menu.findItem(R.id.format_fill).setEnabled(!isNoColor); - } - - public static boolean isNoColor(Buffer buffer) { - if (buffer instanceof ChannelBuffer && ((ChannelBuffer) buffer).getChannel() != null) { - return ((ChannelBuffer) buffer).getChannel().getD_ChanModes().contains("c"); - } else { - return false; - } - } - private void onConnectionEstablished() { assertNotNull(binder); assertNotNull(binder.getBackgroundThread()); @@ -653,7 +589,6 @@ public class ChatActivity extends AppCompatActivity { Log.e("TIME", String.valueOf(System.currentTimeMillis())); }) .negativeColor(context.getThemeUtil().res.colorForeground) - .buttonRippleColor(context.getThemeUtil().res.colorAccentFocus) .positiveText("Login") .negativeText("Cancel") .build(); @@ -668,7 +603,8 @@ public class ChatActivity extends AppCompatActivity { .title("Address") .customView(R.layout.dialog_address, false) .onPositive((dialog1, which) -> { - if (binder != null && binder.getBackgroundThread() != null) binder.stopBackgroundThread(); + if (binder != null && binder.getBackgroundThread() != null) + binder.stopBackgroundThread(); View parent = dialog1.getCustomView(); AppCompatEditText hostField = (AppCompatEditText) parent.findViewById(R.id.host); @@ -699,6 +635,22 @@ public class ChatActivity extends AppCompatActivity { } } + public void onEventMainThread(UnknownCertificateEvent event) { + new MaterialDialog.Builder(this) + .content("Do you trust this certificate?\n" + CertificateUtils.certificateToFingerprint(event.certificate, "")) + .title("Unknown Certificate") + .onPositive((dialog, which) -> { + if (binder.getBackgroundThread() != null) { + binder.getBackgroundThread().certificateManager.addCertificate(event.certificate, event.address); + } + }) + .negativeColor(context.getThemeUtil().res.colorForeground) + .positiveText("Yes") + .negativeText("No") + .build() + .show(); + } + public void onEventMainThread(GeneralErrorEvent event) { Snackbar.make(messages, event.toString(), Snackbar.LENGTH_LONG).show(); for (String line : Splitter.fixedLength(2048).split(event.toString())) { @@ -719,4 +671,51 @@ public class ChatActivity extends AppCompatActivity { toolbar.setSubtitle(""); } } + + @PreferenceWrapper(BuildConfig.APPLICATION_ID) + public static abstract class Settings { + @StringPreference("QUASSEL_LIGHT") + String theme; + @BooleanPreference(false) + boolean fullHostmask; + @IntPreference(2) + int textSize; + @BooleanPreference(true) + boolean mircColors; + + @StringPreference("") + String lastHost; + @IntPreference(4242) + int lastPort; + @StringPreference("") + String lastUsername; + @StringPreference("") + String lastPassword; + } + + private static class Status extends Storable { + @Store + int bufferId = -1; + @Store + int bufferViewConfigId = -1; + } + + private class ServiceInterface { + private void connect(@NonNull ServerAddress address) { + assertNotNull(binder); + disconnect(); + + BusProvider provider = new BusProvider(); + provider.event.register(ChatActivity.this); + binder.startBackgroundThread(provider, address); + onConnectionEstablished(); + } + + private void disconnect() { + if (context.getProvider() != null) + context.getProvider().event.unregister(this); + context.setProvider(null); + context.setClient(null); + } + } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java index cb07ea5ad..4000bcf78 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/ChatMessageRenderer.java @@ -4,21 +4,13 @@ import android.content.Context; import android.graphics.Typeface; import android.support.annotation.ColorInt; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.annotation.UiThread; -import android.util.Log; import de.kuschku.libquassel.localtypes.Buffer; import de.kuschku.libquassel.message.Message; -import de.kuschku.quasseldroid_ng.R; import de.kuschku.quasseldroid_ng.ui.theme.AppContext; -import de.kuschku.util.annotationbind.AutoBinder; -import de.kuschku.util.annotationbind.AutoString; import de.kuschku.util.irc.IrcFormatHelper; import de.kuschku.util.irc.IrcUserUtils; -import de.kuschku.util.ui.MessageUtil; -import de.kuschku.util.ui.SpanFormatter; -import de.kuschku.quasseldroid_ng.ui.theme.ThemeUtil; import static de.kuschku.util.AndroidAssert.assertNotNull; diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java index 92ded6f6a..9a4d6deae 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/chatview/MessageAdapter.java @@ -22,6 +22,7 @@ import static de.kuschku.util.AndroidAssert.assertNotNull; @UiThread public class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> { + private static ObservableSortedList<Message> emptyList = new ObservableComparableSortedList<Message>(Message.class); @NonNull private final ChatMessageRenderer renderer; @NonNull @@ -37,6 +38,10 @@ public class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> { this.callback = new AdapterUICallbackWrapper(this, scroller); } + public static ObservableSortedList<Message> emptyList() { + return emptyList; + } + public void setMessageList(@NonNull ObservableSortedList<Message> messageList) { this.messageList.removeCallback(callback); this.messageList = messageList; @@ -62,9 +67,4 @@ public class MessageAdapter extends RecyclerView.Adapter<MessageViewHolder> { public int getItemCount() { return messageList.size(); } - - private static ObservableSortedList<Message> emptyList = new ObservableComparableSortedList<Message>(Message.class); - public static ObservableSortedList<Message> emptyList() { - return emptyList; - } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java index ea259ca33..ac9816842 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferItem.java @@ -117,7 +117,7 @@ public class BufferItem extends SecondaryDrawerItem implements IObservable<Gener @Override public ColorHolder getIconColor() { - return buffer.getStatus() == BufferInfo.BufferStatus.ONLINE ? + return buffer.getStatus() == BufferInfo.BufferStatus.ONLINE ? ColorHolder.fromColor(context.getThemeUtil().res.colorAccent) : new ColorHolder(); } @@ -151,10 +151,10 @@ public class BufferItem extends SecondaryDrawerItem implements IObservable<Gener super.onPostBindView(drawerItem, view); if (getDescription() != null && getDescription().getText() != null) - ((TextView) view.findViewById(R.id.material_drawer_description)).setText(MessageUtil.parseStyleCodes( - context.getThemeUtil(), - getDescription().getText(), - context.getSettings().mircColors.or(true) - )); + ((TextView) view.findViewById(R.id.material_drawer_description)).setText(MessageUtil.parseStyleCodes( + context.getThemeUtil(), + getDescription().getText(), + context.getSettings().mircColors.or(true) + )); } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigWrapper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigWrapper.java index 903e76bb3..83e4c0c57 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigWrapper.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/BufferViewConfigWrapper.java @@ -1,22 +1,13 @@ package de.kuschku.quasseldroid_ng.ui.chat.drawer; -import android.util.Log; - -import com.mikepenz.fastadapter.IIdentifyable; import com.mikepenz.materialdrawer.Drawer; import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; import de.kuschku.libquassel.syncables.types.BufferViewConfig; import de.kuschku.libquassel.syncables.types.Network; import de.kuschku.quasseldroid_ng.ui.theme.AppContext; -import de.kuschku.quasseldroid_ng.ui.chat.drawer.NetworkItem; -import de.kuschku.util.backports.Stream; import de.kuschku.util.observables.callbacks.ElementCallback; import de.kuschku.util.observables.lists.ObservableSortedList; diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java index 36706986d..ea4bfec59 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/chat/drawer/NetworkItem.java @@ -1,6 +1,5 @@ package de.kuschku.quasseldroid_ng.ui.chat.drawer; -import android.util.Log; import android.util.SparseArray; import com.mikepenz.materialdrawer.holder.ColorHolder; @@ -172,7 +171,7 @@ public class NetworkItem extends PrimaryDrawerItem implements IObservable<Genera return item1.getBuffer().getInfo().id == item2.getBuffer().getInfo().id; } } - + class NoneComparator implements ObservableSortedList.ItemComparator<BufferItem> { @Override public int compare(BufferItem o1, BufferItem o2) { diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/AdvancedEditor.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/AdvancedEditor.java index 8b1ed6384..ab9884be5 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/AdvancedEditor.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/AdvancedEditor.java @@ -22,6 +22,7 @@ public class AdvancedEditor { public void toggleUnderline() { toggleUnderline(editText.getSelectionStart(), editText.getSelectionEnd()); } + public void toggleUnderline(int start, int end) { boolean isUnderline = false; for (UnderlineSpan span : editText.getText().getSpans(start, end, UnderlineSpan.class)) { @@ -41,6 +42,7 @@ public class AdvancedEditor { public void toggleBold() { toggleBold(editText.getSelectionStart(), editText.getSelectionEnd()); } + public void toggleBold(int start, int end) { boolean isBold = false; for (BoldSpan span : editText.getText().getSpans(start, end, BoldSpan.class)) { @@ -59,6 +61,7 @@ public class AdvancedEditor { public void toggleItalic() { toggleItalic(editText.getSelectionStart(), editText.getSelectionEnd()); } + public void toggleItalic(int start, int end) { boolean isItalic = false; for (ItalicSpan span : editText.getText().getSpans(start, end, ItalicSpan.class)) { @@ -77,6 +80,7 @@ public class AdvancedEditor { public void toggleForeground(@IntRange(from = 0, to = 15) int color) { toggleForeground(editText.getSelectionStart(), editText.getSelectionEnd(), color); } + public void toggleForeground(int start, int end, @ColorInt int color) { boolean isColored = false; for (ForegroundColorSpan span : editText.getText().getSpans(start, end, ForegroundColorSpan.class)) { @@ -95,6 +99,7 @@ public class AdvancedEditor { public void toggleBackground(@IntRange(from = 0, to = 15) int color) { toggleBackground(editText.getSelectionStart(), editText.getSelectionEnd(), color); } + public void toggleBackground(int start, int end, @ColorInt int color) { boolean isColored = false; for (BackgroundColorSpan span : editText.getText().getSpans(start, end, BackgroundColorSpan.class)) { diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/FormattingHelper.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/FormattingHelper.java index 1e6be5d2b..74780cc7a 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/FormattingHelper.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/editor/FormattingHelper.java @@ -31,7 +31,7 @@ public class FormattingHelper { } private void withinParagraph(StringBuilder out, Spanned text, - int start, int end) { + int start, int end) { int next; for (int i = start; i < end; i = next) { next = text.nextSpanTransition(i, end, CharacterStyle.class); @@ -87,7 +87,7 @@ public class FormattingHelper { } } - out.append(text.subSequence(i,next)); + out.append(text.subSequence(i, next)); for (int j = style.length - 1; j >= 0; j--) { if ((text.getSpanFlags(style[j]) & Spanned.SPAN_COMPOSING) != 0) diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/AppTheme.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/AppTheme.java index 19f7f5231..7bfbbc77f 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/AppTheme.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/AppTheme.java @@ -23,12 +23,16 @@ public enum AppTheme { public static AppTheme themeFromString(String s) { if (s == null) s = ""; switch (s) { - case "MATERIAL_DARK": return MATERIAL_DARK; - case "MATERIAL_LIGHT": return MATERIAL_LIGHT; - case "QUASSEL_DARK": return QUASSEL_DARK; + case "MATERIAL_DARK": + return MATERIAL_DARK; + case "MATERIAL_LIGHT": + return MATERIAL_LIGHT; + case "QUASSEL_DARK": + return QUASSEL_DARK; default: - case "QUASSEL_LIGHT": return QUASSEL_LIGHT; + case "QUASSEL_LIGHT": + return QUASSEL_LIGHT; } } diff --git a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java index fa55f28ff..336fa5268 100644 --- a/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java +++ b/app/src/main/java/de/kuschku/quasseldroid_ng/ui/theme/ThemeUtil.java @@ -6,7 +6,6 @@ import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v7.view.ContextThemeWrapper; -import android.util.Log; import de.kuschku.quasseldroid_ng.R; import de.kuschku.util.annotationbind.AutoBinder; diff --git a/app/src/main/java/de/kuschku/util/ServerAddress.java b/app/src/main/java/de/kuschku/util/ServerAddress.java index 33ba15f12..2d8f9b049 100644 --- a/app/src/main/java/de/kuschku/util/ServerAddress.java +++ b/app/src/main/java/de/kuschku/util/ServerAddress.java @@ -8,4 +8,8 @@ public class ServerAddress { this.host = host; this.port = port; } + + public String print() { + return String.format("%s:%s", host, port); + } } diff --git a/app/src/main/java/de/kuschku/util/certificates/CertificateDatabaseHandler.java b/app/src/main/java/de/kuschku/util/certificates/CertificateDatabaseHandler.java new file mode 100644 index 000000000..f06452b5c --- /dev/null +++ b/app/src/main/java/de/kuschku/util/certificates/CertificateDatabaseHandler.java @@ -0,0 +1,152 @@ +package de.kuschku.util.certificates; + +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +public class CertificateDatabaseHandler extends SQLiteOpenHelper { + private static final int DATABASE_VERSION = 1; + private static final String DATABASE_NAME = "certificates"; + private static final String TABLE_CERTIFICATES = "certificates"; + + private static final String KEY_CORE_ADDRESS = "core_address"; + private static final String KEY_FINGERPRINT = "fingerprint"; + + // Again we can only use String.format, as SQL doesn’t support table or column names to be bound + // in prepared statements + private static final String STATEMENT_INSERT = + String.format("INSERT OR IGNORE INTO %s(%s, %s) VALUES (?, ?)", + TABLE_CERTIFICATES, KEY_CORE_ADDRESS, KEY_FINGERPRINT); + private static final String STATEMENT_DELETE = + String.format("DELETE FROM %s WHERE %s = ? AND %s = ?", + TABLE_CERTIFICATES, KEY_CORE_ADDRESS, KEY_FINGERPRINT); + private static final String STATEMENT_DELETE_ALL = + String.format("DELETE FROM %s WHERE %s = ?", + TABLE_CERTIFICATES, KEY_CORE_ADDRESS); + + private static final String SPECIFIER_FIND_ALL = String.format("%s = ?", KEY_CORE_ADDRESS); + + public CertificateDatabaseHandler(Context context) { + super(context, DATABASE_NAME, null, DATABASE_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase db) { + // Why do we use String.format and not prepared statements? Because we can’t bind table or + // column names in prepared statements + String statement = String.format("CREATE TABLE %s (%s, %s, PRIMARY KEY (%s, %s), UNIQUE(%s, %s));", + TABLE_CERTIFICATES, + KEY_CORE_ADDRESS, KEY_FINGERPRINT, + KEY_CORE_ADDRESS, KEY_FINGERPRINT, + KEY_CORE_ADDRESS, KEY_FINGERPRINT); + db.execSQL(statement); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + + } + + public boolean addCertificate(String fingerprint, String coreAddress) { + SQLiteDatabase db = this.getWritableDatabase(); + SQLiteStatement statement = db.compileStatement(STATEMENT_INSERT); + statement.bindString(1, coreAddress); + statement.bindString(2, fingerprint); + // executeInsert returns -1 if unsuccessful + return statement.executeInsert() != -1; + } + + public boolean removeCertificate(String fingerprint, String coreAddress) { + SQLiteDatabase db = this.getWritableDatabase(); + SQLiteStatement statement = db.compileStatement(STATEMENT_DELETE); + statement.bindString(1, coreAddress); + statement.bindString(2, fingerprint); + // executeUpdateDelete returns amount of modified rows + return statement.executeUpdateDelete() > 0; + } + + public boolean removeCertificates(String coreAddress) { + SQLiteDatabase db = this.getWritableDatabase(); + SQLiteStatement statement = db.compileStatement(STATEMENT_DELETE_ALL); + statement.bindString(1, coreAddress); + // executeUpdateDelete returns amount of modified rows + return statement.executeUpdateDelete() > 0; + } + + public Cursor cursorFindCertificates(String coreAddress) { + SQLiteDatabase db = this.getReadableDatabase(); + return db.query( + // table name + TABLE_CERTIFICATES, + // column names + new String[]{KEY_FINGERPRINT}, + // where clause + SPECIFIER_FIND_ALL, + // binds for where clause + new String[]{coreAddress}, + null, + null, + null, + null + ); + } + + public Cursor cursorFindAllCertificates() { + SQLiteDatabase db = this.getReadableDatabase(); + return db.query( + // table name + TABLE_CERTIFICATES, + // column names + new String[]{KEY_CORE_ADDRESS, KEY_FINGERPRINT}, + // where clause + null, + // binds for where clause + new String[0], + null, + null, + null, + null + ); + } + + public List<String> findCertificates(String coreAddress) { + Cursor cursor = cursorFindCertificates(coreAddress); + List<String> certificates = new ArrayList<>(); + + if (cursor != null && cursor.moveToFirst()) { + do { + certificates.add(cursor.getString(0)); + } while (cursor.moveToNext()); + } + + return certificates; + } + + public Map<String, Collection<String>> findAllCertificates() { + Cursor cursor = cursorFindAllCertificates(); + + Map<String, Collection<String>> certificates = new HashMap<>(); + + if (cursor != null && cursor.moveToFirst()) { + do { + String coreid = cursor.getString(0); + String fingerprint = cursor.getString(1); + if (certificates.get(coreid) == null) + certificates.put(coreid, new HashSet<>()); + + certificates.get(coreid).add(fingerprint); + } while (cursor.moveToNext()); + } + + return certificates; + } +} diff --git a/app/src/main/java/de/kuschku/util/certificates/CertificateUtils.java b/app/src/main/java/de/kuschku/util/certificates/CertificateUtils.java new file mode 100644 index 000000000..2cdcef68e --- /dev/null +++ b/app/src/main/java/de/kuschku/util/certificates/CertificateUtils.java @@ -0,0 +1,56 @@ +package de.kuschku.util.certificates; + +import com.google.common.base.Joiner; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import de.kuschku.util.Objects; + +public class CertificateUtils { + private CertificateUtils() { + } + + public static String certificateToFingerprint(X509Certificate certificate) throws NoSuchAlgorithmException, CertificateEncodingException { + return hashToFingerprint(getHash(certificate)); + } + + public static String certificateToFingerprint(X509Certificate certificate, String defaultValue) { + try { + return certificateToFingerprint(certificate); + } catch (Exception e) { + return defaultValue; + } + } + + private static byte[] getHash(X509Certificate certificate) throws NoSuchAlgorithmException, CertificateEncodingException { + MessageDigest digest = java.security.MessageDigest.getInstance("SHA1"); + digest.update(certificate.getEncoded()); + return digest.digest(); + } + + public static String hashToFingerprint(byte[] hash) { + String[] formattedBytes = new String[hash.length]; + for (int i = 0; i < hash.length; i++) { + // Format each byte as hex string + formattedBytes[i] = Integer.toHexString(hash[i] & 0xff); + } + return Joiner.on(":").join(formattedBytes); + } + + public static Collection<String> getHostnames(X509Certificate certificate) throws CertificateParsingException { + Set<String> hostnames = new HashSet<>(); + for (List<?> data : certificate.getSubjectAlternativeNames()) { + if (Objects.equals(data.get(0), 2) && data.get(1) instanceof String) + hostnames.add((String) data.get(1)); + } + return hostnames; + } +} diff --git a/app/src/main/java/de/kuschku/util/certificates/SQLiteCertificateManager.java b/app/src/main/java/de/kuschku/util/certificates/SQLiteCertificateManager.java new file mode 100644 index 000000000..86c74a357 --- /dev/null +++ b/app/src/main/java/de/kuschku/util/certificates/SQLiteCertificateManager.java @@ -0,0 +1,67 @@ +package de.kuschku.util.certificates; + +import android.content.Context; + +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import de.kuschku.libquassel.ssl.CertificateManager; +import de.kuschku.libquassel.ssl.UnknownCertificateException; +import de.kuschku.util.ServerAddress; + +public class SQLiteCertificateManager implements CertificateManager { + private CertificateDatabaseHandler handler; + + public SQLiteCertificateManager(Context context) { + this.handler = new CertificateDatabaseHandler(context); + } + + @Override + public boolean isTrusted(X509Certificate certificate, ServerAddress core) { + try { + certificate.checkValidity(); + return handler.findCertificates(core.host).contains(CertificateUtils.certificateToFingerprint(certificate)); + } catch (Exception e) { + return false; + } + } + + @Override + public boolean addCertificate(X509Certificate certificate, ServerAddress core) { + try { + return handler.addCertificate(CertificateUtils.certificateToFingerprint(certificate), core.host); + } catch (Exception e) { + return false; + } + } + + @Override + public boolean removeCertificate(X509Certificate certificate, ServerAddress core) { + try { + return handler.removeCertificate(CertificateUtils.certificateToFingerprint(certificate), core.host); + } catch (Exception e) { + return false; + } + } + + @Override + public boolean removeAllCertificates(ServerAddress core) { + return handler.removeCertificates(core.host); + } + + @Override + public void checkTrusted(X509Certificate certificate, ServerAddress address) throws UnknownCertificateException { + if (!isTrusted(certificate, address)) + throw new UnknownCertificateException(certificate, address); + } + + public List<String> findCertificates(ServerAddress core) { + return handler.findCertificates(core.host); + } + + public Map<String, Collection<String>> findAllCertificates() { + return handler.findAllCertificates(); + } +} diff --git a/app/src/main/java/de/kuschku/util/irc/IrcFormatHelper.java b/app/src/main/java/de/kuschku/util/irc/IrcFormatHelper.java index fc05b3521..adf2e8988 100644 --- a/app/src/main/java/de/kuschku/util/irc/IrcFormatHelper.java +++ b/app/src/main/java/de/kuschku/util/irc/IrcFormatHelper.java @@ -25,7 +25,6 @@ import java.util.regex.Pattern; import de.kuschku.quasseldroid_ng.R; import de.kuschku.quasseldroid_ng.ui.theme.AppContext; -import de.kuschku.quasseldroid_ng.ui.theme.ThemeUtil; import de.kuschku.util.ui.MessageUtil; public class IrcFormatHelper { diff --git a/app/src/main/java/de/kuschku/util/niohelpers/WrappedChannel.java b/app/src/main/java/de/kuschku/util/niohelpers/WrappedChannel.java index 038786538..33769496b 100644 --- a/app/src/main/java/de/kuschku/util/niohelpers/WrappedChannel.java +++ b/app/src/main/java/de/kuschku/util/niohelpers/WrappedChannel.java @@ -14,10 +14,19 @@ import java.nio.ByteBuffer; import java.nio.channels.ByteChannel; import java.nio.channels.ClosedChannelException; import java.nio.channels.InterruptibleChannel; +import java.security.GeneralSecurityException; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +import de.kuschku.libquassel.ssl.CertificateManager; +import de.kuschku.libquassel.ssl.QuasselTrustManager; import de.kuschku.util.CompatibilityUtils; +import de.kuschku.util.ServerAddress; public class WrappedChannel implements Flushable, ByteChannel, InterruptibleChannel { @Nullable @@ -29,6 +38,9 @@ public class WrappedChannel implements Flushable, ByteChannel, InterruptibleChan @Nullable private DataOutputStream out; + @Nullable + private Socket socket = null; + private WrappedChannel(@Nullable InputStream in, @Nullable OutputStream out) { this.rawIn = in; this.rawOut = out; @@ -36,6 +48,11 @@ public class WrappedChannel implements Flushable, ByteChannel, InterruptibleChan if (this.rawOut != null) this.out = new DataOutputStream(rawOut); } + public WrappedChannel(Socket s) throws IOException { + this(s.getInputStream(), s.getOutputStream()); + this.socket = s; + } + @NonNull public static WrappedChannel ofStreams(@Nullable InputStream in, @Nullable OutputStream out) { return new WrappedChannel(in, out); @@ -43,7 +60,7 @@ public class WrappedChannel implements Flushable, ByteChannel, InterruptibleChan @NonNull public static WrappedChannel ofSocket(@NonNull Socket s) throws IOException { - return new WrappedChannel(s.getInputStream(), s.getOutputStream()); + return new WrappedChannel(s); } @Nullable @@ -54,6 +71,20 @@ public class WrappedChannel implements Flushable, ByteChannel, InterruptibleChan ); } + + public static WrappedChannel withSSL(@NonNull WrappedChannel channel, + @NonNull CertificateManager certificateManager, + @NonNull ServerAddress address) throws GeneralSecurityException, IOException { + SSLContext context = SSLContext.getInstance("TLSv1.2"); + TrustManager[] managers = new TrustManager[]{QuasselTrustManager.fromDefault(certificateManager, address)}; + context.init(null, managers, null); + SSLSocketFactory factory = context.getSocketFactory(); + SSLSocket socket = (SSLSocket) factory.createSocket(channel.socket, address.host, address.port, true); + socket.setUseClientMode(true); + socket.startHandshake(); + return WrappedChannel.ofSocket(socket); + } + /** * Reads a sequence of bytes from this channel into the given buffer. * <p> diff --git a/app/src/main/java/de/kuschku/util/observables/ContentComparable.java b/app/src/main/java/de/kuschku/util/observables/ContentComparable.java index 952ca7fea..7b6cb6204 100644 --- a/app/src/main/java/de/kuschku/util/observables/ContentComparable.java +++ b/app/src/main/java/de/kuschku/util/observables/ContentComparable.java @@ -2,5 +2,6 @@ package de.kuschku.util.observables; public interface ContentComparable<T extends ContentComparable<T>> extends Comparable<T> { boolean areItemsTheSame(T other); + boolean areContentsTheSame(T other); } diff --git a/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java b/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java index 6485c2fd2..45466867a 100644 --- a/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java +++ b/app/src/main/java/de/kuschku/util/observables/lists/ChildParentObservableSortedList.java @@ -3,7 +3,6 @@ package de.kuschku.util.observables.lists; import android.support.annotation.NonNull; import de.kuschku.util.observables.IObservable; -import de.kuschku.util.observables.callbacks.UICallback; import de.kuschku.util.observables.callbacks.UIChildCallback; import de.kuschku.util.observables.callbacks.UIChildParentCallback; import de.kuschku.util.observables.callbacks.UIParentCallback; diff --git a/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java b/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java index 33a0de54b..41c8ffb83 100644 --- a/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java +++ b/app/src/main/java/de/kuschku/util/observables/lists/ObservableSortedList.java @@ -213,6 +213,11 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> { callback.notifyItemChanged(position); } + @Override + public String toString() { + return Arrays.toString(toArray()); + } + public interface ItemComparator<T> { int compare(T o1, T o2); @@ -221,11 +226,6 @@ public class ObservableSortedList<T> implements IObservableList<UICallback, T> { boolean areItemsTheSame(T item1, T item2); } - @Override - public String toString() { - return Arrays.toString(toArray()); - } - class Callback extends SortedList.Callback<T> { @Override public int compare(T o1, T o2) { diff --git a/app/src/main/java/de/kuschku/util/ui/DateTimeFormatHelper.java b/app/src/main/java/de/kuschku/util/ui/DateTimeFormatHelper.java index bca319fad..9f9f324ea 100644 --- a/app/src/main/java/de/kuschku/util/ui/DateTimeFormatHelper.java +++ b/app/src/main/java/de/kuschku/util/ui/DateTimeFormatHelper.java @@ -17,42 +17,42 @@ public class DateTimeFormatHelper { } @NonNull - public DateTimeFormatter getTimeFormatter() { - return getTimeFormatter(context); + public static DateTimeFormatter getTimeFormatter(Context ctx) { + return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getTimeFormat(ctx)).toLocalizedPattern()); } @NonNull - public DateTimeFormatter getDateFormatter() { - return getDateFormatter(context); + public static DateTimeFormatter getDateFormatter(Context ctx) { + return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getDateFormat(ctx)).toLocalizedPattern()); } @NonNull - public DateTimeFormatter getLongDateFormatter() { - return getLongDateFormatter(context); + public static DateTimeFormatter getLongDateFormatter(Context ctx) { + return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getLongDateFormat(ctx)).toLocalizedPattern()); } @NonNull - public DateTimeFormatter getMediumDateFormatter() { - return getMediumDateFormatter(context); + public static DateTimeFormatter getMediumDateFormatter(Context ctx) { + return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getMediumDateFormat(ctx)).toLocalizedPattern()); } @NonNull - public static DateTimeFormatter getTimeFormatter(Context ctx) { - return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getTimeFormat(ctx)).toLocalizedPattern()); + public DateTimeFormatter getTimeFormatter() { + return getTimeFormatter(context); } @NonNull - public static DateTimeFormatter getDateFormatter(Context ctx) { - return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getDateFormat(ctx)).toLocalizedPattern()); + public DateTimeFormatter getDateFormatter() { + return getDateFormatter(context); } @NonNull - public static DateTimeFormatter getLongDateFormatter(Context ctx) { - return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getLongDateFormat(ctx)).toLocalizedPattern()); + public DateTimeFormatter getLongDateFormatter() { + return getLongDateFormatter(context); } @NonNull - public static DateTimeFormatter getMediumDateFormatter(Context ctx) { - return DateTimeFormat.forPattern(((SimpleDateFormat) android.text.format.DateFormat.getMediumDateFormat(ctx)).toLocalizedPattern()); + public DateTimeFormatter getMediumDateFormatter() { + return getMediumDateFormatter(context); } } diff --git a/app/src/main/java/de/kuschku/util/ui/MessageUtil.java b/app/src/main/java/de/kuschku/util/ui/MessageUtil.java index cd301f37a..e14b2ea81 100644 --- a/app/src/main/java/de/kuschku/util/ui/MessageUtil.java +++ b/app/src/main/java/de/kuschku/util/ui/MessageUtil.java @@ -21,12 +21,12 @@ public class MessageUtil { public static SpannableString parseStyleCodes(ThemeUtil themeUtil, String content, boolean parse) { if (!parse) { return new SpannableString(content - .replaceAll("\\x02","") - .replaceAll("\\x0F","") - .replaceAll("\\x1D","") - .replaceAll("\\x1F","") - .replaceAll("\\x03[0-9]{1,2}(,[0-9]{1,2})?","") - .replaceAll("\\x03","")); + .replaceAll("\\x02", "") + .replaceAll("\\x0F", "") + .replaceAll("\\x1D", "") + .replaceAll("\\x1F", "") + .replaceAll("\\x03[0-9]{1,2}(,[0-9]{1,2})?", "") + .replaceAll("\\x03", "")); } final char boldIndicator = 2; -- GitLab