diff --git a/app/src/main/java/io/mrarm/irc/EditIgnoreEntryActivity.java b/app/src/main/java/io/mrarm/irc/EditIgnoreEntryActivity.java index 2b846f12..0dd7279c 100644 --- a/app/src/main/java/io/mrarm/irc/EditIgnoreEntryActivity.java +++ b/app/src/main/java/io/mrarm/irc/EditIgnoreEntryActivity.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.UUID; +import java.util.regex.PatternSyntaxException; import io.mrarm.irc.config.ServerConfigData; import io.mrarm.irc.config.ServerConfigManager; @@ -25,6 +26,7 @@ public class EditIgnoreEntryActivity extends ThemedActivity { private EditText mNick; private EditText mUser; private EditText mHost; + private EditText mMesg; private EditText mComment; private CheckBox mChannelMessages; private CheckBox mChannelNotices; @@ -45,6 +47,7 @@ protected void onCreate(Bundle savedInstanceState) { mNick = findViewById(R.id.nick); mUser = findViewById(R.id.user); mHost = findViewById(R.id.host); + mMesg = findViewById(R.id.mesg); mComment = findViewById(R.id.comment); mChannelMessages = findViewById(R.id.channel_messages); @@ -56,6 +59,7 @@ protected void onCreate(Bundle savedInstanceState) { mNick.setText(mEntry.nick); mUser.setText(mEntry.user); mHost.setText(mEntry.host); + mMesg.setText(mEntry.mesg); mComment.setText(mEntry.comment); mChannelMessages.setChecked(mEntry.matchChannelMessages); mChannelNotices.setChecked(mEntry.matchChannelNotices); @@ -76,12 +80,17 @@ public boolean save() { mEntry.nick = mNick.getText().length() > 0 ? mNick.getText().toString() : null; mEntry.user = mUser.getText().length() > 0 ? mUser.getText().toString() : null; mEntry.host = mHost.getText().length() > 0 ? mHost.getText().toString() : null; + mEntry.mesg = mMesg.getText().length() > 0 ? mMesg.getText().toString() : null; mEntry.comment = mComment.getText().length() > 0 ? mComment.getText().toString() : null; mEntry.matchChannelMessages = mChannelMessages.isChecked(); mEntry.matchChannelNotices = mChannelNotices.isChecked(); mEntry.matchDirectMessages = mDirectMessages.isChecked(); mEntry.matchDirectNotices = mDirectNotices.isChecked(); - mEntry.updateRegexes(); + try { + mEntry.updatePatterns(); + } catch (PatternSyntaxException e) { + Toast.makeText(this, this.getResources().getString(R.string.notification_regex_pattern_error, e.getPattern(), e.getMessage()), Toast.LENGTH_LONG).show(); + } try { ServerConfigManager.getInstance(this).saveServer(mServer); } catch (IOException e) { diff --git a/app/src/main/java/io/mrarm/irc/EditServerActivity.java b/app/src/main/java/io/mrarm/irc/EditServerActivity.java index d0666440..92bc0c18 100644 --- a/app/src/main/java/io/mrarm/irc/EditServerActivity.java +++ b/app/src/main/java/io/mrarm/irc/EditServerActivity.java @@ -85,6 +85,7 @@ public class EditServerActivity extends ThemedActivity { private EditText mServerPort; private TextInputLayout mServerPortCtr; private CheckBox mServerSSL; + private CheckBox mServerCtcpVersion; private View mServerSSLCertsButton; private TextView mServerSSLCertsLbl; private ResettablePasswordHelper mServerPass; @@ -146,6 +147,7 @@ protected void onCreate(Bundle savedInstanceState) { mServerPort = findViewById(R.id.server_address_port); mServerPortCtr = findViewById(R.id.server_address_port_ctr); mServerSSL = findViewById(R.id.server_ssl_checkbox); + mServerCtcpVersion = findViewById(R.id.server_ctcp_mode_checkbox); mServerSSLCertsButton = findViewById(R.id.server_ssl_certs); mServerSSLCertsLbl = findViewById(R.id.server_ssl_cert_lbl); mServerPass = new ResettablePasswordHelper( @@ -248,6 +250,7 @@ public void onNothingSelected(AdapterView parent) { mServerName.setText(mEditServer.name); mServerAddress.setText(mEditServer.address); mServerSSL.setChecked(mEditServer.ssl); + mServerCtcpVersion.setChecked(mEditServer.censeCtcpVersion); mServerPort.setText(String.valueOf(mEditServer.port)); mServerRejoinChannels.setChecked(mEditServer.rejoinChannels); @@ -384,6 +387,7 @@ private boolean save() { mEditServer.address = mServerAddress.getText().toString(); mEditServer.port = Integer.parseInt(mServerPort.getText().toString()); mEditServer.ssl = mServerSSL.isChecked(); + mEditServer.censeCtcpVersion = mServerCtcpVersion.isChecked(); mEditServer.nicks = Arrays.asList(mServerNick.getItems()); if (mEditServer.nicks.size() == 0) mEditServer.nicks = null; diff --git a/app/src/main/java/io/mrarm/irc/IgnoreListAdapter.java b/app/src/main/java/io/mrarm/irc/IgnoreListAdapter.java index 01003717..07e270b2 100644 --- a/app/src/main/java/io/mrarm/irc/IgnoreListAdapter.java +++ b/app/src/main/java/io/mrarm/irc/IgnoreListAdapter.java @@ -4,7 +4,12 @@ import android.content.Intent; import android.graphics.Color; import androidx.recyclerview.widget.RecyclerView; + +import android.graphics.Typeface; +import android.graphics.fonts.Font; +import android.text.style.CharacterStyle; import android.text.style.ForegroundColorSpan; +import android.text.style.StyleSpan; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -13,6 +18,7 @@ import java.io.IOException; +import io.mrarm.arsc.chunks.ResValue; import io.mrarm.irc.config.ServerConfigData; import io.mrarm.irc.config.ServerConfigManager; import io.mrarm.irc.dialog.MenuBottomSheetDialog; @@ -94,6 +100,10 @@ private void startEdit() { public void bind(ServerConfigData.IgnoreEntry entry) { ColoredTextBuilder builder = new ColoredTextBuilder(); + if (entry.isBad) + builder.append(mText.getContext().getString(R.string.notifcation_ignore_list_adapter_badre_marker), + new ForegroundColorSpan(Color.RED), new StyleSpan(Typeface.BOLD)); + if (entry.nick == null || entry.nick.equals("*")) builder.append("*", new ForegroundColorSpan(mTextColorSecondary)); else @@ -111,10 +121,17 @@ public void bind(ServerConfigData.IgnoreEntry entry) { else builder.append(entry.host, new ForegroundColorSpan(mTextColorHost)); + if (entry.mesg != null) { + if (builder.getSpannable().length() != 0) + builder.append(" " ); + builder.append(entry.mesg); + } + if (entry.comment != null) { builder.append(" "); builder.append(entry.comment, new ForegroundColorSpan(mTextColorSecondary)); } + mText.setText(builder.getSpannable()); } diff --git a/app/src/main/java/io/mrarm/irc/ServerConnectionInfo.java b/app/src/main/java/io/mrarm/irc/ServerConnectionInfo.java index 5f31a590..77ad9b1e 100644 --- a/app/src/main/java/io/mrarm/irc/ServerConnectionInfo.java +++ b/app/src/main/java/io/mrarm/irc/ServerConnectionInfo.java @@ -1,8 +1,10 @@ package io.mrarm.irc; +import android.content.Context; import android.os.Handler; import android.os.Looper; import android.util.Log; +import android.widget.Toast; import java.util.ArrayList; import java.util.Collections; @@ -129,12 +131,29 @@ public void connect() { DCCManager dccManager = DCCManager.getInstance(getConnectionManager().getContext()); messageHandler.setDCCServerManager(dccManager.getServer()); messageHandler.setDCCClientManager(dccManager.createClient(this)); - messageHandler.setCtcpVersionReply(mManager.getContext() - .getString(R.string.app_name), BuildConfig.VERSION_NAME, "Android"); + { + String appname; + String appver; + + if (mServerConfig.censeCtcpVersion) { + appname = "IRC client"; + appver = "default"; + } else { + appname = mManager.getContext().getString(R.string.app_name); + appver = BuildConfig.VERSION_NAME; + } + messageHandler.setCtcpVersionReply(appname, appver, "Android"); + } connection.addDisconnectListener((IRCConnection conn, Exception reason) -> { notifyDisconnected(); }); createdNewConnection = true; + if (ServerConfigData.checkCompileIgnoreListEntries(mServerConfig) != 0) { + Context ctx = this.getConnectionManager().getContext(); + String errstr = ctx.getString(R.string.notification_server_connection_regexp_badcount, mServerConfig.badCount); + Toast.makeText(ctx, errstr, Toast.LENGTH_LONG).show(); + Log.d("ServerConnectionInfo", errstr); + } } else { connection = (IRCConnection) mApi; } diff --git a/app/src/main/java/io/mrarm/irc/config/ServerConfigData.java b/app/src/main/java/io/mrarm/irc/config/ServerConfigData.java index 17939bbc..0bb56826 100644 --- a/app/src/main/java/io/mrarm/irc/config/ServerConfigData.java +++ b/app/src/main/java/io/mrarm/irc/config/ServerConfigData.java @@ -1,5 +1,6 @@ package io.mrarm.irc.config; +import android.app.Application; import android.util.Log; import java.io.ByteArrayInputStream; @@ -9,13 +10,12 @@ import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.security.spec.EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.util.List; import java.util.UUID; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; -import io.mrarm.irc.R; import io.mrarm.irc.util.SimpleWildcardPattern; public class ServerConfigData { @@ -47,6 +47,9 @@ public class ServerConfigData { public boolean rejoinChannels = true; public List execCommandsConnected; + public boolean censeCtcpVersion = false; + public transient int badCount = 0; + public List ignoreList; public long storageLimit; @@ -87,27 +90,66 @@ public PrivateKey getAuthPrivateKey() { return null; } + public static int checkCompileIgnoreListEntries(ServerConfigData scfgd) { + scfgd.badCount = 0; + if (scfgd.ignoreList != null) { + for (ServerConfigData.IgnoreEntry entry : scfgd.ignoreList) { + if ( (entry.nick != null && entry.nickRegex == null) + || (entry.user != null && entry.userRegex == null) + || (entry.host != null && entry.userRegex == null) + || (entry.mesg != null && entry.mesgRegex == null) + ) { + try { + entry.updatePatterns(); + } catch (PatternSyntaxException e) { + ++scfgd.badCount; + } + } + } + } + return scfgd.badCount; + } + public static class IgnoreEntry { public String nick; public String user; public String host; + public String mesg; public String comment; public transient Pattern nickRegex; public transient Pattern userRegex; public transient Pattern hostRegex; + public transient Pattern mesgRegex; public boolean matchDirectMessages = true; public boolean matchDirectNotices = true; public boolean matchChannelMessages = true; public boolean matchChannelNotices = true; - public void updateRegexes() { - nickRegex = SimpleWildcardPattern.compile(nick); - userRegex = SimpleWildcardPattern.compile(user); - hostRegex = SimpleWildcardPattern.compile(host); - } + public boolean isBad = false; + public void resetPatterns() { + nickRegex = null; + userRegex = null; + hostRegex = null; + mesgRegex = null; + } + + public void updatePatterns() throws PatternSyntaxException { + resetPatterns(); + isBad = false; + try { + if (nick != null) nickRegex = SimpleWildcardPattern.pattCompile(nick); + if (user != null) userRegex = SimpleWildcardPattern.pattCompile(user); + if (host != null) hostRegex = SimpleWildcardPattern.pattCompile(host); + if (mesg != null) mesgRegex = SimpleWildcardPattern.pattCompile(mesg); + } catch (PatternSyntaxException e) { + resetPatterns(); + isBad = true; + throw e; + } + } } } diff --git a/app/src/main/java/io/mrarm/irc/util/IgnoreListMessageFilter.java b/app/src/main/java/io/mrarm/irc/util/IgnoreListMessageFilter.java index a4c35f0a..c3a42dd6 100644 --- a/app/src/main/java/io/mrarm/irc/util/IgnoreListMessageFilter.java +++ b/app/src/main/java/io/mrarm/irc/util/IgnoreListMessageFilter.java @@ -2,6 +2,8 @@ import android.util.Log; +import java.util.regex.PatternSyntaxException; + import io.mrarm.chatlib.dto.MessageInfo; import io.mrarm.chatlib.irc.MessageFilter; import io.mrarm.chatlib.irc.ServerConnectionData; @@ -19,16 +21,16 @@ public IgnoreListMessageFilter(ServerConfigData server) { public boolean filter(ServerConnectionData serverConnectionData, String channel, MessageInfo message) { if (mConfig.ignoreList != null && message.getSender() != null) { for (ServerConfigData.IgnoreEntry entry : mConfig.ignoreList) { - if (entry.nick == null && entry.user == null && entry.host == null) + if (entry.isBad || ((entry.nick == null && entry.user == null && entry.host == null && entry.mesg == null))) continue; - if (entry.nickRegex == null && entry.userRegex == null && entry.hostRegex == null) - entry.updateRegexes(); if (entry.nickRegex != null && !entry.nickRegex.matcher(message.getSender().getNick()).matches()) continue; if (entry.userRegex != null && (message.getSender().getUser() == null || !entry.userRegex.matcher(message.getSender().getUser()).matches())) continue; if (entry.hostRegex != null && (message.getSender().getHost() == null || !entry.hostRegex.matcher(message.getSender().getHost()).matches())) continue; + if (entry.mesgRegex != null && (message.getMessage() == null || !entry.mesgRegex.matcher(message.getMessage()).matches())) + continue; Log.d("IgnoreListMessageFilter", "Ignore message: " + message.getSender().getNick() + " " + message.getMessage()); return false; } diff --git a/app/src/main/java/io/mrarm/irc/util/SimpleWildcardPattern.java b/app/src/main/java/io/mrarm/irc/util/SimpleWildcardPattern.java index 001fd0a3..cb2ace79 100644 --- a/app/src/main/java/io/mrarm/irc/util/SimpleWildcardPattern.java +++ b/app/src/main/java/io/mrarm/irc/util/SimpleWildcardPattern.java @@ -1,10 +1,11 @@ package io.mrarm.irc.util; import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; public class SimpleWildcardPattern { - public static Pattern compile(String str) { + private static String parseGlob(String str) { if (str == null) return null; StringBuilder ret = new StringBuilder(); @@ -25,7 +26,18 @@ public static Pattern compile(String str) { if (str.length() > pi) ret.append(Pattern.quote(str.substring(pi))); ret.append('$'); - return Pattern.compile(ret.toString()); + return ret.toString(); } + private static String strGlobRe(String str) { + if (str.startsWith("/") && str.endsWith("/")) + return str.substring(1, str.length() - 1); + if (str.startsWith("?") && str.endsWith("?")) + return parseGlob(str.substring(1, str.length() - 1)); + return parseGlob(str); + } + + public static Pattern pattCompile(String str) throws PatternSyntaxException { + return Pattern.compile(strGlobRe(str)); + } } diff --git a/app/src/main/res/layout/activity_edit_ignore_entry.xml b/app/src/main/res/layout/activity_edit_ignore_entry.xml index bf1a3b58..9d3b7a05 100644 --- a/app/src/main/res/layout/activity_edit_ignore_entry.xml +++ b/app/src/main/res/layout/activity_edit_ignore_entry.xml @@ -23,7 +23,7 @@ android:id="@+id/nick" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/value_any" /> + android:hint="@string/value_any_re_glob" /> @@ -42,7 +42,7 @@ android:id="@+id/user" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/value_any" /> + android:hint="@string/value_any_re_glob" /> @@ -61,7 +61,7 @@ android:id="@+id/host" android:layout_width="match_parent" android:layout_height="wrap_content" - android:hint="@string/value_any" /> + android:hint="@string/value_any_re_glob" /> @@ -69,7 +69,7 @@ android:id="@+id/channel_messages" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@+id/host_ctr" + android:layout_below="@+id/mesg_ctr" android:layout_marginTop="@dimen/edit_command_alias_spacing" android:layout_marginLeft="@dimen/activity_horizontal_margin_s4" android:layout_marginRight="@dimen/activity_horizontal_margin_s4" @@ -106,6 +106,24 @@ android:text="@string/notification_rule_notice" android:checked="true" /> + + + + + - + + Manage custom certificates Unchanged Authentication mode + Restrict CTCP VERSION Auto-join channels Rejoin opened channels Auto-run commands @@ -259,6 +260,7 @@ Custom (%s) None Any + Any ?glob? or /re/ Always Never Preset @@ -474,7 +476,11 @@ There are no custom rules. You can add one with the plus button in the bottom right corner. Notification rule deleted. A notification rule with this name already exists - Invalid Regex + + Invalid regexp + Regexp pattern errors: %d.\nPlease edit the Ignore List + Bad: + Regexp pattern error in:\n%s\n%s Nick mentions Channel messages @@ -663,6 +669,10 @@ Copied %s to clipboard. + + + Message text + Pick a color