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 ) ); + } +}