From 275ca58a82c627128a145a8754cbe32568536bd9 Mon Sep 17 00:00:00 2001 From: SquidDev Date: Tue, 15 Sep 2020 22:05:27 +0100 Subject: [PATCH] HTTP rules now allow filtering by port The HTTP filtering system becomes even more complex! Though in this case, it's pretty minimal, and definitely worth doing. For instance, the following rule will allow connecting to localhost on port :8080. [[http.rules]] host = "127.0.0.1" port = 8080 action = "allow" # Other rules as before. Closes #540 --- .../dan200/computercraft/ComputerCraft.java | 4 +-- .../core/apis/http/CheckUrl.java | 9 ++--- .../core/apis/http/NetworkUtils.java | 18 +++++++++- .../core/apis/http/options/AddressRule.java | 26 +++++++++----- .../apis/http/options/AddressRuleConfig.java | 7 ++-- .../core/apis/http/request/HttpRequest.java | 2 +- .../core/apis/http/websocket/Websocket.java | 3 +- .../apis/http/options/AddressRuleTest.java | 34 +++++++++++++++++++ 8 files changed, 83 insertions(+), 20 deletions(-) create mode 100644 src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index ab89ba49d..d31e1e41f 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -63,10 +63,10 @@ public final class ComputerCraft public static boolean httpWebsocketEnabled = true; public static List httpRules = Collections.unmodifiableList( Stream.concat( Stream.of( DEFAULT_HTTP_DENY ) - .map( x -> AddressRule.parse( x, Action.DENY.toPartial() ) ) + .map( x -> AddressRule.parse( x, null, Action.DENY.toPartial() ) ) .filter( Objects::nonNull ), Stream.of( DEFAULT_HTTP_ALLOW ) - .map( x -> AddressRule.parse( x, Action.ALLOW.toPartial() ) ) + .map( x -> AddressRule.parse( x, null, Action.ALLOW.toPartial() ) ) .filter( Objects::nonNull ) ).collect( Collectors.toList() ) ); diff --git a/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java b/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java index a2b9f896d..41ddc9390 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java +++ b/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java @@ -24,14 +24,14 @@ public class CheckUrl extends Resource private final IAPIEnvironment environment; private final String address; - private final String host; + private final URI uri; public CheckUrl( ResourceGroup limiter, IAPIEnvironment environment, String address, URI uri ) { super( limiter ); this.environment = environment; this.address = address; - host = uri.getHost(); + this.uri = uri; } public void run() @@ -47,8 +47,9 @@ public class CheckUrl extends Resource try { - InetSocketAddress netAddress = NetworkUtils.getAddress( host, 80, false ); - NetworkUtils.getOptions( host, netAddress ); + boolean ssl = uri.getScheme().equalsIgnoreCase( "https" ); + InetSocketAddress netAddress = NetworkUtils.getAddress( uri, ssl ); + NetworkUtils.getOptions( uri.getHost(), netAddress ); if( tryClose() ) environment.queueEvent( EVENT, address, true ); } diff --git a/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java b/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java index aa2a9a6be..9dc2309a9 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java +++ b/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java @@ -19,6 +19,7 @@ import io.netty.handler.ssl.SslContextBuilder; import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; import java.net.InetSocketAddress; +import java.net.URI; import java.security.KeyStore; import java.util.concurrent.ExecutorService; import java.util.concurrent.SynchronousQueue; @@ -99,6 +100,21 @@ public final class NetworkUtils } } + /** + * Create a {@link InetSocketAddress} from a {@link java.net.URI}. + * + * Note, this may require a DNS lookup, and so should not be executed on the main CC thread. + * + * @param uri The URI to fetch. + * @param ssl Whether to connect with SSL. This is used to find the default port if not otherwise specified. + * @return The resolved address. + * @throws HTTPRequestException If the host is not malformed. + */ + public static InetSocketAddress getAddress( URI uri, boolean ssl ) throws HTTPRequestException + { + return getAddress( uri.getHost(), uri.getPort(), ssl ); + } + /** * Create a {@link InetSocketAddress} from the resolved {@code host} and port. * @@ -128,7 +144,7 @@ public final class NetworkUtils */ public static Options getOptions( String host, InetSocketAddress address ) throws HTTPRequestException { - Options options = AddressRule.apply( ComputerCraft.httpRules, host, address.getAddress() ); + Options options = AddressRule.apply( ComputerCraft.httpRules, host, address ); if( options.action == Action.DENY ) throw new HTTPRequestException( "Domain not permitted" ); return options; } diff --git a/src/main/java/dan200/computercraft/core/apis/http/options/AddressRule.java b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRule.java index 03bf372ba..c7498f269 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/options/AddressRule.java +++ b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRule.java @@ -12,6 +12,7 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.Inet6Address; import java.net.InetAddress; +import java.net.InetSocketAddress; import java.util.regex.Pattern; /** @@ -52,17 +53,23 @@ public final class AddressRule private final HostRange ip; private final Pattern domainPattern; + private final Integer port; private final PartialOptions partial; - private AddressRule( @Nullable HostRange ip, @Nullable Pattern domainPattern, @Nonnull PartialOptions partial ) + private AddressRule( + @Nullable HostRange ip, + @Nullable Pattern domainPattern, + @Nullable Integer port, + @Nonnull PartialOptions partial ) { this.ip = ip; this.domainPattern = domainPattern; this.partial = partial; + this.port = port; } @Nullable - public static AddressRule parse( String filter, @Nonnull PartialOptions partial ) + public static AddressRule parse( String filter, @Nullable Integer port, @Nonnull PartialOptions partial ) { int cidr = filter.indexOf( '/' ); if( cidr >= 0 ) @@ -117,24 +124,27 @@ public final class AddressRule size -= 8; } - return new AddressRule( new HostRange( minBytes, maxBytes ), null, partial ); + return new AddressRule( new HostRange( minBytes, maxBytes ), null, port, partial ); } else { Pattern pattern = Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" ); - return new AddressRule( null, pattern, partial ); + return new AddressRule( null, pattern, port, partial ); } } /** * Determine whether the given address matches a series of patterns. * - * @param domain The domain to match - * @param address The address to check. + * @param domain The domain to match + * @param socketAddress The address to check. * @return Whether it matches any of these patterns. */ - private boolean matches( String domain, InetAddress address ) + private boolean matches( String domain, InetSocketAddress socketAddress ) { + InetAddress address = socketAddress.getAddress(); + if( port != null && port != socketAddress.getPort() ) return false; + if( domainPattern != null ) { if( domainPattern.matcher( domain ).matches() ) return true; @@ -155,7 +165,7 @@ public final class AddressRule return ip != null && ip.contains( address ); } - public static Options apply( Iterable rules, String domain, InetAddress address ) + public static Options apply( Iterable rules, String domain, InetSocketAddress address ) { PartialOptions options = null; boolean hasMany = false; diff --git a/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java index 4f0bc5647..db82c06ef 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java +++ b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java @@ -49,12 +49,14 @@ public class AddressRuleConfig public static boolean checkRule( UnmodifiableConfig builder ) { String hostObj = get( builder, "host", String.class ).orElse( null ); + Integer port = get( builder, "port", Number.class ).map( Number::intValue ).orElse( null ); return hostObj != null && checkEnum( builder, "action", Action.class ) + && check( builder, "port", Number.class ) && check( builder, "timeout", Number.class ) && check( builder, "max_upload", Number.class ) && check( builder, "max_download", Number.class ) && check( builder, "websocket_message", Number.class ) - && AddressRule.parse( hostObj, PartialOptions.DEFAULT ) != null; + && AddressRule.parse( hostObj, port, PartialOptions.DEFAULT ) != null; } @Nullable @@ -64,6 +66,7 @@ public class AddressRuleConfig if( hostObj == null ) return null; Action action = getEnum( builder, "action", Action.class ).orElse( null ); + Integer port = get( builder, "port", Number.class ).map( Number::intValue ).orElse( null ); Integer timeout = get( builder, "timeout", Number.class ).map( Number::intValue ).orElse( null ); Long maxUpload = get( builder, "max_upload", Number.class ).map( Number::longValue ).orElse( null ); Long maxDownload = get( builder, "max_download", Number.class ).map( Number::longValue ).orElse( null ); @@ -77,7 +80,7 @@ public class AddressRuleConfig websocketMessage ); - return AddressRule.parse( hostObj, options ); + return AddressRule.parse( hostObj, port, options ); } private static boolean check( UnmodifiableConfig config, String field, Class klass ) diff --git a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java index ebd21a655..115d8ab04 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java +++ b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequest.java @@ -136,7 +136,7 @@ public class HttpRequest extends Resource try { boolean ssl = uri.getScheme().equalsIgnoreCase( "https" ); - InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl ); + InetSocketAddress socketAddress = NetworkUtils.getAddress( uri, ssl ); Options options = NetworkUtils.getOptions( uri.getHost(), socketAddress ); SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null; diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java index 1841a3df9..0407a198a 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java +++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/Websocket.java @@ -129,8 +129,7 @@ public class Websocket extends Resource try { boolean ssl = uri.getScheme().equalsIgnoreCase( "wss" ); - - InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl ); + InetSocketAddress socketAddress = NetworkUtils.getAddress( uri, ssl ); Options options = NetworkUtils.getOptions( uri.getHost(), socketAddress ); SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null; diff --git a/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java b/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java new file mode 100644 index 000000000..690cb37f5 --- /dev/null +++ b/src/test/java/dan200/computercraft/core/apis/http/options/AddressRuleTest.java @@ -0,0 +1,34 @@ +/* + * This file is part of ComputerCraft - http://www.computercraft.info + * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission. + * Send enquiries to dratcliffe@gmail.com + */ + +package dan200.computercraft.core.apis.http.options; + +import org.junit.jupiter.api.Test; + +import java.net.InetSocketAddress; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AddressRuleTest +{ + @Test + public void matchesPort() + { + Iterable rules = Collections.singletonList( AddressRule.parse( + "127.0.0.1", 8080, + new PartialOptions( Action.ALLOW, null, null, null, null ) + ) ); + + assertEquals( apply( rules, "localhost", 8080 ).action, Action.ALLOW ); + assertEquals( apply( rules, "localhost", 8081 ).action, Action.DENY ); + } + + private Options apply( Iterable rules, String host, int port ) + { + return AddressRule.apply( rules, host, new InetSocketAddress( host, port ) ); + } +}