diff --git a/src/main/java/dan200/computercraft/ComputerCraft.java b/src/main/java/dan200/computercraft/ComputerCraft.java index 681cc8363..067fa3bc4 100644 --- a/src/main/java/dan200/computercraft/ComputerCraft.java +++ b/src/main/java/dan200/computercraft/ComputerCraft.java @@ -6,7 +6,8 @@ package dan200.computercraft; import dan200.computercraft.api.turtle.event.TurtleAction; -import dan200.computercraft.core.apis.http.AddressRule; +import dan200.computercraft.core.apis.http.options.Action; +import dan200.computercraft.core.apis.http.options.AddressRule; import dan200.computercraft.shared.Config; import dan200.computercraft.shared.computer.blocks.BlockComputer; import dan200.computercraft.shared.computer.core.ClientComputerRegistry; @@ -70,7 +71,7 @@ public final class ComputerCraft public static boolean disable_lua51_features = false; public static String default_computer_settings = ""; public static boolean debug_enable = true; - public static boolean logPeripheralErrors = true; + public static boolean logComputerErrors = true; public static boolean commandRequireCreative = true; public static int computer_threads = 1; @@ -80,16 +81,16 @@ public final class ComputerCraft public static boolean httpEnabled = true; public static boolean httpWebsocketEnabled = true; public static List httpRules = Collections.unmodifiableList( Stream.concat( - Stream.of( DEFAULT_HTTP_DENY ).map( x -> AddressRule.parse( x, AddressRule.Action.DENY ) ).filter( Objects::nonNull ), - Stream.of( DEFAULT_HTTP_ALLOW ).map( x -> AddressRule.parse( x, AddressRule.Action.ALLOW ) ).filter( Objects::nonNull ) + Stream.of( DEFAULT_HTTP_DENY ) + .map( x -> AddressRule.parse( x, Action.DENY.toPartial() ) ) + .filter( Objects::nonNull ), + Stream.of( DEFAULT_HTTP_ALLOW ) + .map( x -> AddressRule.parse( x, Action.ALLOW.toPartial() ) ) + .filter( Objects::nonNull ) ).collect( Collectors.toList() ) ); - public static int httpTimeout = 30000; public static int httpMaxRequests = 16; - public static long httpMaxDownload = 16 * 1024 * 1024; - public static long httpMaxUpload = 4 * 1024 * 1024; public static int httpMaxWebsockets = 4; - public static int httpMaxWebsocketMessage = 128 * 1024; public static boolean enableCommandBlock = false; public static int modem_range = 64; diff --git a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java index eac31b9f8..fa0af20ea 100644 --- a/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java +++ b/src/main/java/dan200/computercraft/core/apis/HTTPAPI.java @@ -119,12 +119,6 @@ public final Object[] request( IArguments args ) throws LuaException URI uri = HttpRequest.checkUri( address ); HttpRequest request = new HttpRequest( requests, m_apiEnvironment, address, postString, headers, binary, redirect ); - long requestBody = request.body().readableBytes() + HttpRequest.getHeaderSize( headers ); - if( ComputerCraft.httpMaxUpload != 0 && requestBody > ComputerCraft.httpMaxUpload ) - { - throw new HTTPRequestException( "Request body is too large" ); - } - // Make the request request.queue( r -> r.request( uri, httpMethod ) ); 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 f92d5926e..2d902581c 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java +++ b/src/main/java/dan200/computercraft/core/apis/http/CheckUrl.java @@ -7,6 +7,7 @@ import dan200.computercraft.core.apis.IAPIEnvironment; +import java.net.InetSocketAddress; import java.net.URI; import java.util.concurrent.Future; @@ -46,7 +47,9 @@ private void doRun() try { - NetworkUtils.getAddress( host, 80, false ); + InetSocketAddress address = NetworkUtils.getAddress( host, 80, false ); + NetworkUtils.getOptions( host, address ); + if( tryClose() ) environment.queueEvent( EVENT, address, true ); } catch( HTTPRequestException e ) 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 c2a3e6c59..aa2a9a6be 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java +++ b/src/main/java/dan200/computercraft/core/apis/http/NetworkUtils.java @@ -6,6 +6,9 @@ package dan200.computercraft.core.apis.http; import dan200.computercraft.ComputerCraft; +import dan200.computercraft.core.apis.http.options.Action; +import dan200.computercraft.core.apis.http.options.AddressRule; +import dan200.computercraft.core.apis.http.options.Options; import dan200.computercraft.shared.util.ThreadUtils; import io.netty.buffer.ByteBuf; import io.netty.channel.EventLoopGroup; @@ -15,7 +18,6 @@ import javax.net.ssl.SSLException; import javax.net.ssl.TrustManagerFactory; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.security.KeyStore; import java.util.concurrent.ExecutorService; @@ -106,24 +108,31 @@ public static SslContext getSslContext() throws HTTPRequestException * @param port The port, or -1 if not defined. * @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 permitted. + * @throws HTTPRequestException If the host is not malformed. */ public static InetSocketAddress getAddress( String host, int port, boolean ssl ) throws HTTPRequestException { if( port < 0 ) port = ssl ? 443 : 80; - InetSocketAddress socketAddress = new InetSocketAddress( host, port ); if( socketAddress.isUnresolved() ) throw new HTTPRequestException( "Unknown host" ); - - InetAddress address = socketAddress.getAddress(); - if( AddressRule.apply( ComputerCraft.httpRules, host, address ) == AddressRule.Action.DENY ) - { - throw new HTTPRequestException( "Domain not permitted" ); - } - return socketAddress; } + /** + * Get options for a specific domain. + * + * @param host The host to resolve. + * @param address The address, resolved by {@link #getAddress(String, int, boolean)}. + * @return The options for this host. + * @throws HTTPRequestException If the host is not permitted + */ + public static Options getOptions( String host, InetSocketAddress address ) throws HTTPRequestException + { + Options options = AddressRule.apply( ComputerCraft.httpRules, host, address.getAddress() ); + if( options.action == Action.DENY ) throw new HTTPRequestException( "Domain not permitted" ); + return options; + } + /** * Read a {@link ByteBuf} into a byte array. * diff --git a/src/main/java/dan200/computercraft/core/apis/http/options/Action.java b/src/main/java/dan200/computercraft/core/apis/http/options/Action.java new file mode 100644 index 000000000..ac23cc575 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/http/options/Action.java @@ -0,0 +1,23 @@ +/* + * 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 javax.annotation.Nonnull; + +public enum Action +{ + ALLOW, + DENY; + + private final PartialOptions partial = new PartialOptions( this, null, null, null, null ); + + @Nonnull + public PartialOptions toPartial() + { + return partial; + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/http/AddressRule.java b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRule.java similarity index 76% rename from src/main/java/dan200/computercraft/core/apis/http/AddressRule.java rename to src/main/java/dan200/computercraft/core/apis/http/options/AddressRule.java index 19ab61f36..03bf372ba 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/AddressRule.java +++ b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRule.java @@ -3,11 +3,12 @@ * Copyright Daniel Ratcliffe, 2011-2020. Do not distribute without permission. * Send enquiries to dratcliffe@gmail.com */ -package dan200.computercraft.core.apis.http; +package dan200.computercraft.core.apis.http.options; import com.google.common.net.InetAddresses; import dan200.computercraft.ComputerCraft; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.net.Inet6Address; import java.net.InetAddress; @@ -18,6 +19,11 @@ */ public final class AddressRule { + public static final long MAX_DOWNLOAD = 16 * 1024 * 1024; + public static final long MAX_UPLOAD = 4 * 1024 * 1024; + public static final int TIMEOUT = 30_000; + public static final int WEBSOCKET_MESSAGE = 128 * 1024; + private static final class HostRange { private final byte[] min; @@ -44,25 +50,19 @@ public boolean contains( InetAddress address ) } } - public enum Action - { - ALLOW, - DENY, - } - private final HostRange ip; private final Pattern domainPattern; - private final Action action; + private final PartialOptions partial; - private AddressRule( HostRange ip, Pattern domainPattern, Action action ) + private AddressRule( @Nullable HostRange ip, @Nullable Pattern domainPattern, @Nonnull PartialOptions partial ) { this.ip = ip; this.domainPattern = domainPattern; - this.action = action; + this.partial = partial; } @Nullable - public static AddressRule parse( String filter, Action action ) + public static AddressRule parse( String filter, @Nonnull PartialOptions partial ) { int cidr = filter.indexOf( '/' ); if( cidr >= 0 ) @@ -117,12 +117,12 @@ else if( size < 8 ) size -= 8; } - return new AddressRule( new HostRange( minBytes, maxBytes ), null, action ); + return new AddressRule( new HostRange( minBytes, maxBytes ), null, partial ); } else { Pattern pattern = Pattern.compile( "^\\Q" + filter.replaceAll( "\\*", "\\\\E.*\\\\Q" ) + "\\E$" ); - return new AddressRule( null, pattern, action ); + return new AddressRule( null, pattern, partial ); } } @@ -133,7 +133,7 @@ else if( size < 8 ) * @param address The address to check. * @return Whether it matches any of these patterns. */ - public boolean matches( String domain, InetAddress address ) + private boolean matches( String domain, InetAddress address ) { if( domainPattern != null ) { @@ -155,13 +155,32 @@ private boolean matchesAddress( InetAddress address ) return ip != null && ip.contains( address ); } - public static Action apply( Iterable rules, String domain, InetAddress address ) + public static Options apply( Iterable rules, String domain, InetAddress address ) { + PartialOptions options = null; + boolean hasMany = false; + for( AddressRule rule : rules ) { - if( rule.matches( domain, address ) ) return rule.action; + if( !rule.matches( domain, address ) ) continue; + + if( options == null ) + { + options = rule.partial; + } + else + { + + if( !hasMany ) + { + options = options.copy(); + hasMany = true; + } + + options.merge( rule.partial ); + } } - return Action.DENY; + return (options == null ? PartialOptions.DEFAULT : options).toOptions(); } } 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 new file mode 100644 index 000000000..4f0bc5647 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/http/options/AddressRuleConfig.java @@ -0,0 +1,132 @@ +/* + * 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 com.electronwill.nightconfig.core.CommentedConfig; +import com.electronwill.nightconfig.core.Config; +import com.electronwill.nightconfig.core.InMemoryCommentedFormat; +import com.electronwill.nightconfig.core.UnmodifiableConfig; +import dan200.computercraft.ComputerCraft; + +import javax.annotation.Nullable; +import java.util.Locale; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Parses, checks and generates {@link Config}s for {@link AddressRule}. + */ +public class AddressRuleConfig +{ + public static UnmodifiableConfig makeRule( String host, Action action ) + { + CommentedConfig config = InMemoryCommentedFormat.defaultInstance().createConfig( ConcurrentHashMap::new ); + config.add( "host", host ); + config.add( "action", action.name().toLowerCase( Locale.ROOT ) ); + + if( host.equals( "*" ) && action == Action.ALLOW ) + { + config.setComment( "timeout", "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." ); + config.add( "timeout", AddressRule.TIMEOUT ); + + config.setComment( "max_download", "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." ); + config.set( "max_download", AddressRule.MAX_DOWNLOAD ); + + config.setComment( "max_upload", "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." ); + config.set( "max_upload", AddressRule.MAX_UPLOAD ); + + config.setComment( "max_websocket_message", "The maximum size (in bytes) that a computer can send or receive in one websocket packet." ); + config.set( "max_websocket_message", AddressRule.WEBSOCKET_MESSAGE ); + } + + return config; + } + + public static boolean checkRule( UnmodifiableConfig builder ) + { + String hostObj = get( builder, "host", String.class ).orElse( null ); + return hostObj != null && checkEnum( builder, "action", Action.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; + } + + @Nullable + public static AddressRule parseRule( UnmodifiableConfig builder ) + { + String hostObj = get( builder, "host", String.class ).orElse( null ); + if( hostObj == null ) return null; + + Action action = getEnum( builder, "action", Action.class ).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 ); + Integer websocketMessage = get( builder, "websocket_message", Number.class ).map( Number::intValue ).orElse( null ); + + PartialOptions options = new PartialOptions( + action, + maxUpload, + maxDownload, + timeout, + websocketMessage + ); + + return AddressRule.parse( hostObj, options ); + } + + private static boolean check( UnmodifiableConfig config, String field, Class klass ) + { + Object value = config.get( field ); + if( value == null || klass.isInstance( value ) ) return true; + + ComputerCraft.log.warn( "HTTP rule's {} is not a {}.", field, klass.getSimpleName() ); + return false; + } + + private static > boolean checkEnum( UnmodifiableConfig config, String field, Class klass ) + { + Object value = config.get( field ); + if( value == null ) return true; + + if( !(value instanceof String) ) + { + ComputerCraft.log.warn( "HTTP rule's {} is not a string", field ); + return false; + } + + if( parseEnum( klass, (String) value ) == null ) + { + ComputerCraft.log.warn( "HTTP rule's {} is not a known option", field ); + return false; + } + + return true; + } + + private static Optional get( UnmodifiableConfig config, String field, Class klass ) + { + Object value = config.get( field ); + return klass.isInstance( value ) ? Optional.of( klass.cast( value ) ) : Optional.empty(); + } + + private static > Optional getEnum( UnmodifiableConfig config, String field, Class klass ) + { + return get( config, field, String.class ).map( x -> parseEnum( klass, x ) ); + } + + @Nullable + private static > T parseEnum( Class klass, String x ) + { + for( T value : klass.getEnumConstants() ) + { + if( value.name().equalsIgnoreCase( x ) ) return value; + } + return null; + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/http/options/Options.java b/src/main/java/dan200/computercraft/core/apis/http/options/Options.java new file mode 100644 index 000000000..5dd0e78d1 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/http/options/Options.java @@ -0,0 +1,31 @@ +/* + * 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 javax.annotation.Nonnull; + +/** + * Options about a specific domain. + */ +public final class Options +{ + @Nonnull + public final Action action; + public final long maxUpload; + public final long maxDownload; + public final int timeout; + public final int websocketMessage; + + Options( @Nonnull Action action, long maxUpload, long maxDownload, int timeout, int websocketMessage ) + { + this.action = action; + this.maxUpload = maxUpload; + this.maxDownload = maxDownload; + this.timeout = timeout; + this.websocketMessage = websocketMessage; + } +} diff --git a/src/main/java/dan200/computercraft/core/apis/http/options/PartialOptions.java b/src/main/java/dan200/computercraft/core/apis/http/options/PartialOptions.java new file mode 100644 index 000000000..fb7150dd5 --- /dev/null +++ b/src/main/java/dan200/computercraft/core/apis/http/options/PartialOptions.java @@ -0,0 +1,59 @@ +/* + * 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 javax.annotation.Nonnull; + +public final class PartialOptions +{ + static final PartialOptions DEFAULT = new PartialOptions( null, null, null, null, null ); + + Action action; + Long maxUpload; + Long maxDownload; + Integer timeout; + Integer websocketMessage; + + Options options; + + PartialOptions( Action action, Long maxUpload, Long maxDownload, Integer timeout, Integer websocketMessage ) + { + this.action = action; + this.maxUpload = maxUpload; + this.maxDownload = maxDownload; + this.timeout = timeout; + this.websocketMessage = websocketMessage; + } + + @Nonnull + Options toOptions() + { + if( options != null ) return options; + + return options = new Options( + action == null ? Action.DENY : action, + maxUpload == null ? AddressRule.MAX_UPLOAD : maxUpload, + maxDownload == null ? AddressRule.MAX_DOWNLOAD : maxDownload, + timeout == null ? AddressRule.TIMEOUT : timeout, + websocketMessage == null ? AddressRule.WEBSOCKET_MESSAGE : websocketMessage + ); + } + + void merge( @Nonnull PartialOptions other ) + { + if( action == null && other.action != null ) action = other.action; + if( maxUpload == null && other.maxUpload != null ) maxUpload = other.maxUpload; + if( maxDownload == null && other.maxDownload != null ) maxDownload = other.maxDownload; + if( timeout == null && other.timeout != null ) timeout = other.timeout; + if( websocketMessage == null && other.websocketMessage != null ) websocketMessage = other.websocketMessage; + } + + PartialOptions copy() + { + return new PartialOptions( action, maxUpload, maxDownload, timeout, websocketMessage ); + } +} 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 3af3bacc3..ebd21a655 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 @@ -11,6 +11,7 @@ import dan200.computercraft.core.apis.http.NetworkUtils; import dan200.computercraft.core.apis.http.Resource; import dan200.computercraft.core.apis.http.ResourceGroup; +import dan200.computercraft.core.apis.http.options.Options; import dan200.computercraft.core.tracking.TrackingField; import io.netty.bootstrap.Bootstrap; import io.netty.buffer.ByteBuf; @@ -136,16 +137,24 @@ private void doRequest( URI uri, HttpMethod method ) { boolean ssl = uri.getScheme().equalsIgnoreCase( "https" ); InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl ); + Options options = NetworkUtils.getOptions( uri.getHost(), socketAddress ); SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null; // getAddress may have a slight delay, so let's perform another cancellation check. if( isClosed() ) return; + long requestBody = getHeaderSize( headers ) + postBuffer.capacity(); + if( options.maxUpload != 0 && requestBody > options.maxUpload ) + { + failure( "Request body is too large" ); + return; + } + // Add request size to the tracker before opening the connection environment.addTrackingChange( TrackingField.HTTP_REQUESTS, 1 ); - environment.addTrackingChange( TrackingField.HTTP_UPLOAD, getHeaderSize( headers ) + postBuffer.capacity() ); + environment.addTrackingChange( TrackingField.HTTP_UPLOAD, requestBody ); - HttpRequestHandler handler = currentRequest = new HttpRequestHandler( this, uri, method ); + HttpRequestHandler handler = currentRequest = new HttpRequestHandler( this, uri, method, options ); connectFuture = new Bootstrap() .group( NetworkUtils.LOOP_GROUP ) .channelFactory( NioSocketChannel::new ) @@ -155,9 +164,9 @@ private void doRequest( URI uri, HttpMethod method ) protected void initChannel( SocketChannel ch ) { - if( ComputerCraft.httpTimeout > 0 ) + if( options.timeout > 0 ) { - ch.config().setConnectTimeoutMillis( ComputerCraft.httpTimeout ); + ch.config().setConnectTimeoutMillis( options.timeout ); } ChannelPipeline p = ch.pipeline(); @@ -166,9 +175,9 @@ protected void initChannel( SocketChannel ch ) p.addLast( sslContext.newHandler( ch.alloc(), uri.getHost(), socketAddress.getPort() ) ); } - if( ComputerCraft.httpTimeout > 0 ) + if( options.timeout > 0 ) { - p.addLast( new ReadTimeoutHandler( ComputerCraft.httpTimeout, TimeUnit.MILLISECONDS ) ); + p.addLast( new ReadTimeoutHandler( options.timeout, TimeUnit.MILLISECONDS ) ); } p.addLast( @@ -194,7 +203,7 @@ protected void initChannel( SocketChannel ch ) catch( Exception e ) { failure( "Could not connect" ); - if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error in HTTP request", e ); + if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in HTTP request", e ); } } diff --git a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java index d742ec538..0f164d4f0 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java +++ b/src/main/java/dan200/computercraft/core/apis/http/request/HttpRequestHandler.java @@ -12,6 +12,7 @@ import dan200.computercraft.core.apis.handles.HandleGeneric; import dan200.computercraft.core.apis.http.HTTPRequestException; import dan200.computercraft.core.apis.http.NetworkUtils; +import dan200.computercraft.core.apis.http.options.Options; import dan200.computercraft.core.tracking.TrackingField; import io.netty.buffer.ByteBuf; import io.netty.buffer.CompositeByteBuf; @@ -45,18 +46,20 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler ComputerCraft.httpMaxDownload ) + if( options.maxDownload != 0 && responseBody.readableBytes() + partial.readableBytes() > options.maxDownload ) { closed = true; ctx.close(); @@ -185,7 +188,7 @@ public void channelRead0( ChannelHandlerContext ctx, HttpObject message ) @Override public void exceptionCaught( ChannelHandlerContext ctx, Throwable cause ) { - if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause ); + if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error handling HTTP response", cause ); request.failure( cause ); } 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 fe591f592..99efe6295 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 @@ -12,6 +12,7 @@ import dan200.computercraft.core.apis.http.NetworkUtils; import dan200.computercraft.core.apis.http.Resource; import dan200.computercraft.core.apis.http.ResourceGroup; +import dan200.computercraft.core.apis.http.options.Options; import dan200.computercraft.shared.util.IoUtil; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; @@ -130,6 +131,7 @@ private void doConnect() boolean ssl = uri.getScheme().equalsIgnoreCase( "wss" ); InetSocketAddress socketAddress = NetworkUtils.getAddress( uri.getHost(), uri.getPort(), ssl ); + Options options = NetworkUtils.getOptions( uri.getHost(), socketAddress ); SslContext sslContext = ssl ? NetworkUtils.getSslContext() : null; // getAddress may have a slight delay, so let's perform another cancellation check. @@ -151,14 +153,14 @@ protected void initChannel( SocketChannel ch ) WebSocketClientHandshaker handshaker = WebSocketClientHandshakerFactory.newHandshaker( uri, WebSocketVersion.V13, null, true, headers, - ComputerCraft.httpMaxWebsocketMessage == 0 ? MAX_MESSAGE_SIZE : ComputerCraft.httpMaxWebsocketMessage + options.websocketMessage <= 0 ? MAX_MESSAGE_SIZE : options.websocketMessage ); p.addLast( new HttpClientCodec(), new HttpObjectAggregator( 8192 ), WebSocketClientCompressionHandler.INSTANCE, - new WebsocketHandler( Websocket.this, handshaker ) + new WebsocketHandler( Websocket.this, handshaker, options ) ); } } ) @@ -178,15 +180,15 @@ protected void initChannel( SocketChannel ch ) catch( Exception e ) { failure( "Could not connect" ); - if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error in websocket", e ); + if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error in websocket", e ); } } - void success( Channel channel ) + void success( Channel channel, Options options ) { if( isClosed() ) return; - WebsocketHandle handle = new WebsocketHandle( this, channel ); + WebsocketHandle handle = new WebsocketHandle( this, options, channel ); environment().queueEvent( SUCCESS_EVENT, address, handle ); websocketHandle = createOwnerReference( handle ); diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java index 78bb17bdf..a6436a785 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java +++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandle.java @@ -6,8 +6,8 @@ package dan200.computercraft.core.apis.http.websocket; import com.google.common.base.Objects; -import dan200.computercraft.ComputerCraft; import dan200.computercraft.api.lua.*; +import dan200.computercraft.core.apis.http.options.Options; import dan200.computercraft.core.tracking.TrackingField; import dan200.computercraft.shared.util.StringUtil; import io.netty.buffer.Unpooled; @@ -28,13 +28,15 @@ public class WebsocketHandle implements Closeable { private final Websocket websocket; + private final Options options; private boolean closed = false; private Channel channel; - public WebsocketHandle( Websocket websocket, Channel channel ) + public WebsocketHandle( Websocket websocket, Options options, Channel channel ) { this.websocket = websocket; + this.options = options; this.channel = channel; } @@ -55,7 +57,7 @@ public final void send( IArguments args ) throws LuaException checkOpen(); String text = StringUtil.toString( args.get( 0 ) ); - if( ComputerCraft.httpMaxWebsocketMessage != 0 && text.length() > ComputerCraft.httpMaxWebsocketMessage ) + if( options.websocketMessage != 0 && text.length() > options.websocketMessage ) { throw new LuaException( "Message is too large" ); } diff --git a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java index c3e69d7b9..52c27b97c 100644 --- a/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java +++ b/src/main/java/dan200/computercraft/core/apis/http/websocket/WebsocketHandler.java @@ -7,6 +7,7 @@ import dan200.computercraft.core.apis.http.HTTPRequestException; import dan200.computercraft.core.apis.http.NetworkUtils; +import dan200.computercraft.core.apis.http.options.Options; import dan200.computercraft.core.tracking.TrackingField; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ConnectTimeoutException; @@ -23,11 +24,13 @@ public class WebsocketHandler extends SimpleChannelInboundHandler { private final Websocket websocket; private final WebSocketClientHandshaker handshaker; + private final Options options; - public WebsocketHandler( Websocket websocket, WebSocketClientHandshaker handshaker ) + public WebsocketHandler( Websocket websocket, WebSocketClientHandshaker handshaker, Options options ) { this.handshaker = handshaker; this.websocket = websocket; + this.options = options; } @Override @@ -52,7 +55,7 @@ public void channelRead0( ChannelHandlerContext ctx, Object msg ) if( !handshaker.isHandshakeComplete() ) { handshaker.finishHandshake( ctx.channel(), (FullHttpResponse) msg ); - websocket.success( ctx.channel() ); + websocket.success( ctx.channel(), options ); return; } diff --git a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java index 91ac8161e..6c7d6ca9b 100644 --- a/src/main/java/dan200/computercraft/core/computer/ComputerThread.java +++ b/src/main/java/dan200/computercraft/core/computer/ComputerThread.java @@ -518,7 +518,7 @@ public void run() private static void timeoutTask( ComputerExecutor executor, Thread thread, long time ) { - if( !ComputerCraft.logPeripheralErrors ) return; + if( !ComputerCraft.logComputerErrors ) return; StringBuilder builder = new StringBuilder() .append( "Terminating computer #" ).append( executor.getComputer().getID() ) diff --git a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java index 3c988f025..6f4b25652 100644 --- a/src/main/java/dan200/computercraft/core/lua/BasicFunction.java +++ b/src/main/java/dan200/computercraft/core/lua/BasicFunction.java @@ -54,7 +54,7 @@ public Varargs invoke( LuaState luaState, Varargs args ) throws LuaError } catch( Throwable t ) { - if( ComputerCraft.logPeripheralErrors ) + if( ComputerCraft.logComputerErrors ) { ComputerCraft.log.error( "Error calling " + name + " on " + instance, t ); } diff --git a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java index dc8291b21..58b284a1e 100644 --- a/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java +++ b/src/main/java/dan200/computercraft/core/lua/CobaltLuaMachine.java @@ -323,7 +323,7 @@ private LuaValue toValue( @Nullable Object object, @Nullable Map httpWebsocketEnabled; private static final ConfigValue> httpRules; - private static final ConfigValue httpTimeout; private static final ConfigValue httpMaxRequests; - private static final ConfigValue httpMaxDownload; - private static final ConfigValue httpMaxUpload; private static final ConfigValue httpMaxWebsockets; - private static final ConfigValue httpMaxWebsocketMessage; private static final ConfigValue commandBlockEnabled; private static final ConfigValue modemRange; @@ -121,7 +116,7 @@ private Config() {} logComputerErrors = builder .comment( "Log exceptions thrown by peripherals and other Lua objects.\n" + "This makes it easier for mod authors to debug problems, but may result in log spam should people use buggy methods." ) - .define( "log_computer_errors", ComputerCraft.logPeripheralErrors ); + .define( "log_computer_errors", ComputerCraft.logComputerErrors ); } { @@ -164,42 +159,25 @@ private Config() {} .define( "websocket_enabled", ComputerCraft.httpWebsocketEnabled ); httpRules = builder - .comment( "A list of rules which control which domains or IPs are allowed through the \"http\" API on computers.\n" + - "Each rule is an item with a 'host' to match against, and an action. " + + .comment( "A list of rules which control behaviour of the \"http\" API for specific domains or IPs.\n" + + "Each rule is an item with a 'host' to match against, and a series of properties. " + "The host may be a domain name (\"pastebin.com\"),\n" + - "wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\"). 'action' maybe 'allow' or 'block'. If no rules" + - "match, the domain will be blocked." ) + "wildcard (\"*.pastebin.com\") or CIDR notation (\"127.0.0.0/8\"). If no rules, the domain is blocked." ) .defineList( "rules", Stream.concat( - Stream.of( ComputerCraft.DEFAULT_HTTP_DENY ).map( x -> makeRule( x, "deny" ) ), - Stream.of( ComputerCraft.DEFAULT_HTTP_ALLOW ).map( x -> makeRule( x, "allow" ) ) + Stream.of( ComputerCraft.DEFAULT_HTTP_DENY ).map( x -> AddressRuleConfig.makeRule( x, Action.DENY ) ), + Stream.of( ComputerCraft.DEFAULT_HTTP_ALLOW ).map( x -> AddressRuleConfig.makeRule( x, Action.ALLOW ) ) ).collect( Collectors.toList() ), - x -> x instanceof UnmodifiableConfig && parseRule( (UnmodifiableConfig) x ) != null ); - - httpTimeout = builder - .comment( "The period of time (in milliseconds) to wait before a HTTP request times out. Set to 0 for unlimited." ) - .defineInRange( "timeout", ComputerCraft.httpTimeout, 0, Integer.MAX_VALUE ); + x -> x instanceof UnmodifiableConfig && AddressRuleConfig.checkRule( (UnmodifiableConfig) x ) ); httpMaxRequests = builder .comment( "The number of http requests a computer can make at one time. Additional requests will be queued, and sent when the running requests have finished. Set to 0 for unlimited." ) .defineInRange( "max_requests", ComputerCraft.httpMaxRequests, 0, Integer.MAX_VALUE ); - httpMaxDownload = builder - .comment( "The maximum size (in bytes) that a computer can download in a single request. Note that responses may receive more data than allowed, but this data will not be returned to the client." ) - .defineInRange( "max_download", (int) ComputerCraft.httpMaxDownload, 0, Integer.MAX_VALUE ); - - httpMaxUpload = builder - .comment( "The maximum size (in bytes) that a computer can upload in a single request. This includes headers and POST text." ) - .defineInRange( "max_upload", (int) ComputerCraft.httpMaxUpload, 0, Integer.MAX_VALUE ); - httpMaxWebsockets = builder .comment( "The number of websockets a computer can have open at one time. Set to 0 for unlimited." ) .defineInRange( "max_websockets", ComputerCraft.httpMaxWebsockets, 1, Integer.MAX_VALUE ); - httpMaxWebsocketMessage = builder - .comment( "The maximum size (in bytes) that a computer can send or receive in one websocket packet." ) - .defineInRange( "max_websocket_message", ComputerCraft.httpMaxWebsocketMessage, 0, Websocket.MAX_MESSAGE_SIZE ); - builder.pop(); } @@ -291,7 +269,7 @@ public static void sync() ComputerCraft.default_computer_settings = defaultComputerSettings.get(); ComputerCraft.debug_enable = debugEnabled.get(); ComputerCraft.computer_threads = computerThreads.get(); - ComputerCraft.logPeripheralErrors = logComputerErrors.get(); + ComputerCraft.logComputerErrors = logComputerErrors.get(); // Execution ComputerCraft.computer_threads = computerThreads.get(); @@ -302,14 +280,10 @@ public static void sync() ComputerCraft.httpEnabled = httpEnabled.get(); ComputerCraft.httpWebsocketEnabled = httpWebsocketEnabled.get(); ComputerCraft.httpRules = Collections.unmodifiableList( httpRules.get().stream() - .map( Config::parseRule ).filter( Objects::nonNull ).collect( Collectors.toList() ) ); + .map( AddressRuleConfig::parseRule ).filter( Objects::nonNull ).collect( Collectors.toList() ) ); - ComputerCraft.httpTimeout = httpTimeout.get(); ComputerCraft.httpMaxRequests = httpMaxRequests.get(); - ComputerCraft.httpMaxDownload = httpMaxDownload.get(); - ComputerCraft.httpMaxUpload = httpMaxUpload.get(); ComputerCraft.httpMaxWebsockets = httpMaxWebsockets.get(); - ComputerCraft.httpMaxWebsocketMessage = httpMaxWebsocketMessage.get(); // Peripheral ComputerCraft.enableCommandBlock = commandBlockEnabled.get(); @@ -362,28 +336,4 @@ private static TurtleAction getAction( String value ) return null; } } - - private static UnmodifiableConfig makeRule( String host, String action ) - { - com.electronwill.nightconfig.core.Config config = com.electronwill.nightconfig.core.Config.inMemory(); - config.add( "host", host ); - config.add( "action", action ); - return config; - } - - @Nullable - private static AddressRule parseRule( UnmodifiableConfig builder ) - { - Object hostObj = builder.get( "host" ); - Object actionObj = builder.get( "action" ); - if( !(hostObj instanceof String) || !(actionObj instanceof String) ) return null; - - String host = (String) hostObj, action = (String) actionObj; - for( AddressRule.Action candiate : AddressRule.Action.values() ) - { - if( candiate.name().equalsIgnoreCase( action ) ) return AddressRule.parse( host, candiate ); - } - - return null; - } } diff --git a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java index c7ef8986a..7354853ef 100644 --- a/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java +++ b/src/main/java/dan200/computercraft/shared/computer/apis/CommandAPI.java @@ -64,7 +64,7 @@ private Object[] doCommand( String command ) } catch( Throwable t ) { - if( ComputerCraft.logPeripheralErrors ) ComputerCraft.log.error( "Error running command.", t ); + if( ComputerCraft.logComputerErrors ) ComputerCraft.log.error( "Error running command.", t ); return new Object[] { false, createOutput( "Java Exception Thrown: " + t ) }; } } diff --git a/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java index 91fdc4cad..2807f189b 100644 --- a/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java +++ b/src/test/java/dan200/computercraft/core/ComputerTestDelegate.java @@ -85,7 +85,7 @@ public class ComputerTestDelegate @BeforeEach public void before() throws IOException { - ComputerCraft.logPeripheralErrors = true; + ComputerCraft.logComputerErrors = true; if( REPORT_PATH.delete() ) ComputerCraft.log.info( "Deleted previous coverage report." ); diff --git a/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java b/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java index e2deba3cb..21b55613b 100644 --- a/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java +++ b/src/test/java/dan200/computercraft/core/computer/ComputerBootstrap.java @@ -42,7 +42,7 @@ public static void run( String program, int maxTimes ) public static void run( IWritableMount mount, Consumer setup, int maxTicks ) { - ComputerCraft.logPeripheralErrors = true; + ComputerCraft.logComputerErrors = true; ComputerCraft.maxMainComputerTime = ComputerCraft.maxMainGlobalTime = Integer.MAX_VALUE; Terminal term = new Terminal( ComputerCraft.terminalWidth_computer, ComputerCraft.terminalHeight_computer );