0
0
mirror of https://github.com/TrianguloY/UrlChecker.git synced 2024-09-19 20:02:16 +02:00

Merge pull request #243 from the-blank-x/optional-groups

Pattern Checker: Fix optional groups giving a null instead of an empty string
This commit is contained in:
TrianguloY 2023-06-10 13:42:48 +02:00 committed by GitHub
commit 75f282e8bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 158 additions and 11 deletions

View File

@ -16,9 +16,9 @@ import com.trianguloy.urlchecker.url.UrlData;
import com.trianguloy.urlchecker.utilities.AndroidUtils;
import com.trianguloy.urlchecker.utilities.Inflater;
import com.trianguloy.urlchecker.utilities.JavaUtils;
import com.trianguloy.urlchecker.utilities.RegexFix;
import org.json.JSONArray;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
@ -73,6 +73,7 @@ class PatternConfig extends AModuleConfig {
R.string.mPttrn_userContent,
"https://github.com/TrianguloY/UrlChecker/wiki/Custom-patterns"
));
RegexFix.attachSetting(views.findViewById(R.id.regex_fix));
}
}
@ -84,12 +85,14 @@ class PatternDialog extends AModuleDialog {
private LinearLayout box;
private final PatternCatalog catalog;
private final RegexFix regexFix;
private final List<Message> messages = new ArrayList<>();
public PatternDialog(MainDialog dialog) {
super(dialog);
catalog = new PatternCatalog(dialog);
regexFix = new RegexFix(dialog);
}
@Override
@ -107,14 +110,14 @@ class PatternDialog extends AModuleDialog {
public void onModifyUrl(UrlData urlData, JavaUtils.Function<UrlData, Boolean> setNewUrl) {
// init
messages.clear();
String url = urlData.url;
var url = urlData.url;
// check each pattern
JSONObject patterns = catalog.getCatalog();
for (String pattern : JavaUtils.toList(patterns.keys())) {
var patterns = catalog.getCatalog();
for (var pattern : JavaUtils.toList(patterns.keys())) {
try {
JSONObject data = patterns.optJSONObject(pattern);
Message message = new Message(pattern);
var data = patterns.optJSONObject(pattern);
var message = new Message(pattern);
// enabled?
if (data == null) continue;
@ -122,7 +125,7 @@ class PatternDialog extends AModuleDialog {
// get regex (must exists)
if (!data.has("regex")) continue;
var regex = data.getString("regex");
var regex_matcher = Pattern.compile(data.getString("regex")).matcher(url);
// 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.find();
if (matches && data.has("excludeRegex")) {
// if 'excludeRegex' doesn't exist, the pattern can match
// if 'excludeRegex' matches, the pattern doesn't matches
@ -143,12 +146,12 @@ class PatternDialog extends AModuleDialog {
// check replacements
String replacement = null;
Object replacements = data.opt("replacement");
var replacements = data.opt("replacement");
if (replacements != null) {
// data exists
if (replacements instanceof JSONArray) {
// array, get random
JSONArray replacementsArray = (JSONArray) replacements;
var replacementsArray = (JSONArray) replacements;
replacement = replacementsArray.getString(new Random().nextInt(replacementsArray.length()));
} else {
// single data, get that one
@ -158,7 +161,7 @@ class PatternDialog extends AModuleDialog {
if (replacement != null) {
// replace url
message.newUrl = url.replaceAll(regex, replacement);
message.newUrl = regexFix.replaceAll(url, regex_matcher, replacement);
// automatic? apply
if (data.optBoolean("automatic")) {

View File

@ -0,0 +1,137 @@
package com.trianguloy.urlchecker.utilities;
import android.content.Context;
import android.os.Build;
import android.view.View;
import android.widget.Switch;
import java.util.regex.Matcher;
/**
* 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 a newer version of Android
* https://github.com/TrianguloY/UrlChecker/issues/237
*/
public class RegexFix {
/**
* Android 11 and up have already the fix, disable in those cases
*/
public static final boolean IS_ANDROID_FIXED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R;
private final GenericPref.Bool pttrn_regexfix;
public RegexFix(Context cntx) {
// fix enabled by default
pttrn_regexfix = new GenericPref.Bool("pttrn_regexfix", true, cntx);
}
/**
* Attach the setting to a given switch view (or disabled if not needed)
*/
public static void attachSetting(Switch view) {
if (IS_ANDROID_FIXED) {
// hide, already native
view.setVisibility(View.GONE);
} else {
// show, required
new RegexFix(view.getContext()).pttrn_regexfix.attachToSwitch(view);
view.setVisibility(View.VISIBLE);
}
}
/**
* Use this instead of: matcher = pattern.matcher(text); result = matcher.replaceAll(replacement)
*/
public String replaceAll(String text, Matcher matcher, String replacement) {
if (IS_ANDROID_FIXED || !pttrn_regexfix.get()) {
// no fix required or explicitly disabled, just use native function
return matcher.replaceAll(replacement);
}
// Copied from https://android.googlesource.com/platform/libcore/+/refs/heads/android13-release/ojluni/src/main/java/java/util/regex/Matcher.java#837
boolean result = matcher.reset().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;
}
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);
return matcher.end();
}
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) {
escapeNamedGroup = true;
escapeNamedGroupStart = i;
} else if (c == '}' && dollar && escapeNamedGroup) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
throw new IllegalArgumentException("your android version does not support named-capturing groups");
}
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;
}
}

View File

@ -16,6 +16,12 @@
android:layout_height="wrap_content"
android:text="@string/json_edit" />
<Switch
android:id="@+id/regex_fix"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/mPttrn_regexfix" />
<TextView
android:id="@+id/user_content"
android:layout_width="match_parent"

View File

@ -119,6 +119,7 @@ Built-in patterns include:
- Warning when contains non-ascii characters like greek letters. This can be used for phishing: googĺe.com vs google.com
- Suggest replacing 'http' with 'https'
- Suggest replacing Youtube, Reddit or Twitter with privacy-friendly alternatives [disabled by default]"</string>
<string name="mPttrn_regexfix">Fix optional regex groups (disable if regex is not working correctly)</string>
<string name="mPttrn_userContent">List of other useful user-created patterns: %s</string>
<string name="mPttrn_fix">Apply</string>
<string name="mPttrn_fixed">Applied - %s</string>