diff --git a/app/src/main/java/com/trianguloy/urlchecker/modules/list/PatternModule.java b/app/src/main/java/com/trianguloy/urlchecker/modules/list/PatternModule.java index 59891f1..614743e 100644 --- a/app/src/main/java/com/trianguloy/urlchecker/modules/list/PatternModule.java +++ b/app/src/main/java/com/trianguloy/urlchecker/modules/list/PatternModule.java @@ -1,5 +1,7 @@ package com.trianguloy.urlchecker.modules.list; +import android.annotation.TargetApi; +import android.os.Build; import android.view.View; import android.widget.Button; import android.widget.LinearLayout; @@ -24,6 +26,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.regex.Pattern; +import java.util.regex.Matcher; /** * This module checks for patterns characters in the url @@ -122,7 +125,7 @@ class PatternDialog extends AModuleDialog { // get regex (must exists) if (!data.has("regex")) continue; - var regex = data.getString("regex"); + Pattern regex = Pattern.compile(data.getString("regex")); // applied? message.applied = urlData.getData(APPLIED + pattern) != null; @@ -130,7 +133,7 @@ class PatternDialog extends AModuleDialog { // check matches // if 'regexp' matches, the pattern can match // if 'regexp' doesn't match, the patter doesn't match - var matches = Pattern.compile(regex).matcher(url).find(); + var matches = regex.matcher(url).find(); if (matches && data.has("excludeRegex")) { // if 'excludeRegex' doesn't exist, the pattern can match // if 'excludeRegex' matches, the pattern doesn't matches @@ -158,7 +161,7 @@ class PatternDialog extends AModuleDialog { if (replacement != null) { // replace url - message.newUrl = url.replaceAll(regex, replacement); + message.newUrl = replaceAll(url, regex, replacement); // automatic? apply if (data.optBoolean("automatic")) { @@ -225,4 +228,100 @@ class PatternDialog extends AModuleDialog { this.pattern = pattern; } } + + /** + * On Android 10 and under, optional groups may yield a "null" in the replacement output instead of an empty string. + * Therefore, we just copy the implementation from newer versions + * https://github.com/TrianguloY/UrlChecker/issues/237 + */ + private static String replaceAll(String text, Pattern pattern, String replacement) { + // Copied from https://android.googlesource.com/platform/libcore/+/refs/heads/android13-release/ojluni/src/main/java/java/util/regex/Matcher.java#837 + Matcher matcher = pattern.matcher(text); + boolean result = matcher.find(); + if (result) { + StringBuffer sb = new StringBuffer(); + int appendPos = 0; + do { + appendPos = appendReplacement(text, matcher, sb, replacement, appendPos); + result = matcher.find(); + } while (result); + appendTail(text, sb, appendPos); + return sb.toString(); + } + return text.toString(); + } + + private static int appendReplacement(String text, Matcher matcher, StringBuffer sb, String replacement, int appendPos) { + // Copied from https://android.googlesource.com/platform/libcore/+/refs/heads/android13-release/ojluni/src/main/java/java/util/regex/Matcher.java#714 + sb.append(text.substring(appendPos, matcher.start())); + appendEvaluated(matcher, sb, replacement); + appendPos = matcher.end(); + + return appendPos; + } + + @TargetApi(Build.VERSION_CODES.O) + private static void appendEvaluated(Matcher matcher, StringBuffer buffer, String s) { + // Copied from https://android.googlesource.com/platform/libcore/+/refs/heads/android13-release/ojluni/src/main/java/java/util/regex/Matcher.java#731 + boolean escape = false; + boolean dollar = false; + boolean escapeNamedGroup = false; + int escapeNamedGroupStart = -1; + + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c == '\\' && !escape) { + escape = true; + } else if (c == '$' && !escape) { + dollar = true; + } else if (c >= '0' && c <= '9' && dollar && !escapeNamedGroup) { + String groupValue = matcher.group(c - '0'); + if (groupValue != null) { + buffer.append(groupValue); + } + dollar = false; + } else if (c == '{' && dollar) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + throw new IllegalArgumentException("your android version does not support named-capturing groups"); + } + escapeNamedGroup = true; + escapeNamedGroupStart = i; + } else if (c == '}' && dollar && escapeNamedGroup) { + String groupValue = matcher.group(s.substring(escapeNamedGroupStart + 1, i)); + if (groupValue != null) { + buffer.append(groupValue); + } + dollar = false; + escapeNamedGroup = false; + } else if (c != '}' && dollar && escapeNamedGroup) { + continue; + } else { + buffer.append(c); + dollar = false; + escape = false; + escapeNamedGroup = false; + } + } + + if (escape) { + throw new IllegalArgumentException("character to be escaped is missing"); + } + + if (dollar) { + throw new IllegalArgumentException("Illegal group reference: group index is missing"); + } + + if (escapeNamedGroup) { + throw new IllegalArgumentException("Missing ending brace '}' from replacement string"); + } + } + + private static StringBuffer appendTail(String text, StringBuffer sb, int appendPos) { + // Copied from https://android.googlesource.com/platform/libcore/+/refs/heads/android13-release/ojluni/src/main/java/java/util/regex/Matcher.java#796 + int to = text.length(); + if (appendPos < to) { + sb.append(text.substring(appendPos, to)); + } + return sb; + } }