diff --git a/pom.xml b/pom.xml index 857ed68..c65d27c 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,12 @@ slf4j-simple ${slf4j-version} + + org.junit.jupiter + junit-jupiter-api + 5.10.2 + test + @@ -42,6 +48,11 @@ 21 + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.2 + org.apache.maven.plugins maven-assembly-plugin diff --git a/src/main/java/io/github/danthe1st/httpsintercept/control/HostMatcher.java b/src/main/java/io/github/danthe1st/httpsintercept/control/HostMatcher.java new file mode 100644 index 0000000..9f10420 --- /dev/null +++ b/src/main/java/io/github/danthe1st/httpsintercept/control/HostMatcher.java @@ -0,0 +1,68 @@ +package io.github.danthe1st.httpsintercept.control; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class HostMatcher { + private Set exactHosts = new HashSet<>(); + private Set hostParts = new HashSet<>(); + + public static HostMatcher load(Path config) throws IOException { + if(!Files.exists(config)){ + Files.createFile(config); + return load(Collections.emptyList()); + } + List hostDeclarations = Files.readAllLines(config); + return load(hostDeclarations); + } + + static HostMatcher load(List hostDeclarations) { + Set exactHosts = new HashSet<>(); + Set hostParts = new HashSet<>(); + for(String ignored : hostDeclarations){ + if(ignored.startsWith("*.")){ + hostParts.add(ignored.substring(2)); + }else{ + exactHosts.add(ignored); + } + } + return new HostMatcher(exactHosts, hostParts); + } + + HostMatcher(Set exactHosts, Set hostParts) { + this.exactHosts = Set.copyOf(exactHosts); + this.hostParts = Set.copyOf(hostParts); + } + + public boolean matches(String hostname) { + return exactHosts.contains(hostname) || doesMatchPart(hostname); + } + + private boolean doesMatchPart(String hostname) { + if(hostParts.isEmpty()){ + return false; + } + + int index = 0; + while((index = hostname.indexOf('.', index) + 1) != 0 && index < hostname.length()){ + if(hostParts.contains(hostname.substring(index))){ + return true; + } + } + + return false; + } + + Set getExactHosts() { + return Collections.unmodifiableSet(exactHosts); + } + + Set getHostParts() { + return Collections.unmodifiableSet(hostParts); + } +} diff --git a/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/CustomSniHandler.java b/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/CustomSniHandler.java index 2d6e25e..f4f712f 100644 --- a/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/CustomSniHandler.java +++ b/src/main/java/io/github/danthe1st/httpsintercept/handler/sni/CustomSniHandler.java @@ -10,6 +10,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import io.github.danthe1st.httpsintercept.control.HostMatcher; import io.github.danthe1st.httpsintercept.handler.raw.RawForwardIncomingRequestHandler; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelHandler; @@ -27,13 +28,13 @@ public class CustomSniHandler extends SniHandler { private final Bootstrap clientBootstrapTemplate; - private final Set ignoredHosts; + private final HostMatcher ignoredHosts; public CustomSniHandler(Mapping mapping, Bootstrap clientBootstrapTemplate) throws IOException { super(mapping); this.clientBootstrapTemplate = clientBootstrapTemplate; - ignoredHosts = loadIgnoredHosts(); + ignoredHosts = HostMatcher.load(Path.of("ignoredHosts.txt")); } private static Set loadIgnoredHosts() throws IOException { @@ -50,8 +51,8 @@ private static Set loadIgnoredHosts() throws IOException { @Override protected void replaceHandler(ChannelHandlerContext channelHandlerContext, String hostname, SslContext sslContext) throws Exception { ChannelPipeline pipeline = channelHandlerContext.pipeline(); - if(ignoredHosts.contains(hostname)){ - LOG.debug("skipping hostname {}", hostname); + if(ignoredHosts.matches(hostname)){ + LOG.info("skipping hostname {}", hostname); boolean foundThis = false; diff --git a/src/test/java/io/github/danthe1st/httpsintercept/control/HostMatcherTests.java b/src/test/java/io/github/danthe1st/httpsintercept/control/HostMatcherTests.java new file mode 100644 index 0000000..d23e07c --- /dev/null +++ b/src/test/java/io/github/danthe1st/httpsintercept/control/HostMatcherTests.java @@ -0,0 +1,59 @@ +package io.github.danthe1st.httpsintercept.control; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.junit.jupiter.api.Test; + +class HostMatcherTests { + @Test + void testExactMatch() { + HostMatcher matcher = new HostMatcher(Set.of("example.com"), Collections.emptySet()); + assertTrue(matcher.matches("example.com")); + assertFalse(matcher.matches("github.com")); + } + + @Test + void testPartMatch() { + HostMatcher matcher = new HostMatcher(Collections.emptySet(), Set.of("example.com")); + assertTrue(matcher.matches("host.example.com")); + assertTrue(matcher.matches(".example.com")); + assertFalse(matcher.matches("host.github.com")); + assertFalse(matcher.matches("example.com")); + assertFalse(matcher.matches("example.com.")); + assertFalse(matcher.matches("")); + assertFalse(matcher.matches(".")); + } + + @Test + void testLoadExact() { + HostMatcher hostMatcher = HostMatcher.load(List.of("example.com")); + assertEquals(Set.of("example.com"), hostMatcher.getExactHosts()); + assertEquals(Collections.emptySet(), hostMatcher.getHostParts()); + } + + @Test + void testLoadParts() { + HostMatcher hostMatcher = HostMatcher.load(List.of("*.example.com")); + assertEquals(Collections.emptySet(), hostMatcher.getExactHosts()); + assertEquals(Set.of("example.com"), hostMatcher.getHostParts()); + + assertTrue(hostMatcher.matches("host.example.com")); + } + + @Test + void testLoadEmptyPart() { + HostMatcher hostMatcher = HostMatcher.load(List.of("*.")); + assertEquals(Collections.emptySet(), hostMatcher.getExactHosts()); + assertEquals(Set.of(""), hostMatcher.getHostParts()); + + assertFalse(hostMatcher.matches("something")); + assertFalse(hostMatcher.matches("example.com")); + } + +}