Add support for different regex parsers

This commit is contained in:
Paul Schaub 2022-11-29 14:21:44 +01:00
parent 3f10efac7a
commit 21f8ba8ccf
8 changed files with 270 additions and 1 deletions

28
hsregex/build.gradle Normal file
View File

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
plugins {
id 'java-library'
}
group 'org.pgpainless'
repositories {
mavenCentral()
mavenLocal()
}
dependencies {
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitVersion"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitVersion"
implementation(project(":pgpainless-core"))
// Henry Spencers Regular Expression (RegEx packets)
implementation 'com.basistech.tclre:tcl-regex:0.14.5'
}
test {
useJUnitPlatform()
}

View File

@ -0,0 +1,33 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import com.basistech.tclre.HsrePattern;
import com.basistech.tclre.PatternFlags;
import com.basistech.tclre.RePattern;
import com.basistech.tclre.RegexException;
public class HSRegexInterpreterFactory extends RegexInterpreterFactory {
public Regex instantiate(String regex) {
return new Regex() {
private final RePattern pattern;
{
try {
pattern = HsrePattern.compile(regex, PatternFlags.ADVANCED);
} catch (RegexException e) {
throw new IllegalArgumentException(e);
}
}
@Override
public boolean matches(String string) {
return pattern.matcher(string).find();
}
};
}
}

View File

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
/**
* Regex interpreter implementation based on Henry Spencers Regular Expression library.
*
* @see <a href="https://www.rfc-editor.org/rfc/rfc4880#section-8">RFC4880 - §8. Regular Expressions</a>
*/
package org.pgpainless.algorithm;

View File

@ -30,4 +30,7 @@ dependencies {
// @Nullable, @Nonnull annotations
implementation "com.google.code.findbugs:jsr305:3.0.2"
// HSRE regex for tests
testImplementation project(":hsregex")
}

View File

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import org.pgpainless.key.util.UserId;
public interface Regex {
/**
* Return true, if the regex matches the given user-id.
*
* @param userId userId
* @return true if matches, false otherwise
*/
default boolean matches(UserId userId) {
return matches(userId.toString());
}
/**
* Return true, if the regex matches the given string.
*
* @param string string
* @return true if matches, false otherwise
*/
boolean matches(String string);
}

View File

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import javax.annotation.Nonnull;
import java.util.regex.Pattern;
public abstract class RegexInterpreterFactory {
private static RegexInterpreterFactory INSTANCE;
public static RegexInterpreterFactory getInstance() {
if (INSTANCE == null) {
INSTANCE = new JavaRegexInterpreterFactory();
}
return INSTANCE;
}
public static void setInstance(@Nonnull RegexInterpreterFactory instance) {
INSTANCE = instance;
}
public static Regex create(String regex) {
return getInstance().instantiate(regex);
}
public abstract Regex instantiate(String regex) throws IllegalArgumentException;
public static class JavaRegexInterpreterFactory extends RegexInterpreterFactory {
@Override
public Regex instantiate(String regex) {
return new Regex() {
private final Pattern pattern = Pattern.compile(regex);
@Override
public boolean matches(String string) {
return pattern.matcher(string).find();
}
};
}
}
}

View File

@ -0,0 +1,120 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.algorithm;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.stream.Stream;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.pgpainless.key.util.UserId;
public class RegexTest {
private static Stream<Arguments> provideRegexInterpreterFactories() {
return Stream.of(
Arguments.of(Named.of("Default JavaRegexInterpreterFactory",
new RegexInterpreterFactory.JavaRegexInterpreterFactory())),
Arguments.of(Named.of("HSRegexInterpreterFactory",
new HSRegexInterpreterFactory()))
);
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void simpleTest(RegexInterpreterFactory factory) {
Regex regex = factory.instantiate("Alice|Bob");
assertTrue(regex.matches("Alice"));
assertTrue(regex.matches("Bob"));
assertFalse(regex.matches("Charlie"));
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testEmailRegexMatchesDomain(RegexInterpreterFactory factory) {
Regex regex = factory.instantiate("<[^>]+[@.]pgpainless\\.org>$");
assertTrue(regex.matches("Alice <alice@pgpainless.org>"));
assertTrue(regex.matches("Bob <bob@pgpainless.org>"));
assertFalse(regex.matches("Alice <alice@example.com>"), "wrong domain");
assertFalse(regex.matches("Bob <bob@example.com>"), "wrong domain");
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testEmailRegexMatchesOnlyWrappedAddresses(RegexInterpreterFactory factory) {
Regex regex = factory.instantiate("<[^>]+[@.]pgpainless\\.org>$");
assertTrue(regex.matches("<alice@pgpainless.org>"));
assertFalse(regex.matches("alice@pgpainless.org"), "only match mails in <>");
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testCaseSensitivity(RegexInterpreterFactory factory) {
Regex regex = factory.instantiate("<[^>]+[@.]pgpainless\\.org>$");
assertFalse(regex.matches("Alice <alice@PGPAINLESS.ORG>"));
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testWildCard(RegexInterpreterFactory factory) {
Regex regex = factory.instantiate(".*");
assertTrue(regex.matches(""));
assertTrue(regex.matches("Alice"));
assertTrue(regex.matches("<alice@pgpainless.org>"));
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testExclusion(RegexInterpreterFactory factory) {
// Test [^>] matches all but '>'
Regex regex = factory.instantiate("<[^>]+[@.]pgpainless\\.org>$");
assertFalse(regex.matches("<alice>appendix@pgpainless.org>"));
assertFalse(regex.matches("<>alice@pgpainless.org>"));
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testOnlyMatchAtTheEnd(RegexInterpreterFactory factory) {
Regex regex = factory.instantiate("<[^>]+[@.]pgpainless\\.org>$");
assertFalse(regex.matches("Alice <alice@pgpainless.org><bob@example.org>"));
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testRanges(RegexInterpreterFactory factory) {
Regex regex = factory.instantiate("<[^>]+[0-9][@.]pgpainless\\.org>$");
for (int i = 0; i < 10; i++) {
String mail = "<user" + i + "@pgpainless.org>";
assertTrue(regex.matches(mail));
}
assertFalse(regex.matches("<user@pgpainless.org>"));
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testExactMailMatch(RegexInterpreterFactory factory) {
Regex exactMail = factory.instantiate("<exact@pgpainless\\.org>$");
assertTrue(exactMail.matches("<exact@pgpainless.org>"));
assertTrue(exactMail.matches("Exact Match <exact@pgpainless.org>"));
assertFalse(exactMail.matches("<roughly-exact@pgpainless.org>"));
}
@ParameterizedTest
@MethodSource("provideRegexInterpreterFactories")
public void testSetInstance(RegexInterpreterFactory factory) {
RegexInterpreterFactory before = RegexInterpreterFactory.getInstance();
RegexInterpreterFactory.setInstance(factory);
Regex regex = RegexInterpreterFactory.create("<[^>]+[@.]pgpainless\\.org>$");
assertTrue(regex.matches(UserId.nameAndEmail("Alice", "alice@pgpainless.org")));
RegexInterpreterFactory.setInstance(before);
}
}

View File

@ -6,5 +6,6 @@ rootProject.name = 'PGPainless'
include 'pgpainless-core',
'pgpainless-sop',
'pgpainless-cli'
'pgpainless-cli',
'hsregex'