CC-Tweaked/projects/common/src/main/java/dan200/computercraft/shared/config/AddressRuleConfig.java

142 lines
5.9 KiB
Java

/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.shared.config;
import com.electronwill.nightconfig.core.Config;
import com.electronwill.nightconfig.core.InMemoryCommentedFormat;
import com.electronwill.nightconfig.core.UnmodifiableConfig;
import dan200.computercraft.core.apis.http.options.Action;
import dan200.computercraft.core.apis.http.options.AddressRule;
import dan200.computercraft.core.apis.http.options.PartialOptions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.util.Locale;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.OptionalLong;
import java.util.concurrent.ConcurrentHashMap;
/**
* Parses, checks and generates {@link Config}s for {@link AddressRule}.
*/
class AddressRuleConfig {
private static final Logger LOG = LoggerFactory.getLogger(AddressRuleConfig.class);
public static UnmodifiableConfig makeRule(String host, Action action) {
var 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) {
var hostObj = get(builder, "host", String.class).orElse(null);
var port = unboxOptInt(get(builder, "port", Number.class));
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, port, PartialOptions.DEFAULT) != null;
}
@Nullable
public static AddressRule parseRule(UnmodifiableConfig builder) {
var hostObj = get(builder, "host", String.class).orElse(null);
if (hostObj == null) return null;
var action = getEnum(builder, "action", Action.class).orElse(null);
var port = unboxOptInt(get(builder, "port", Number.class));
var timeout = unboxOptInt(get(builder, "timeout", Number.class));
var maxUpload = unboxOptLong(get(builder, "max_upload", Number.class).map(Number::longValue));
var maxDownload = unboxOptLong(get(builder, "max_download", Number.class).map(Number::longValue));
var websocketMessage = unboxOptInt(get(builder, "websocket_message", Number.class).map(Number::intValue));
var options = new PartialOptions(
action,
maxUpload,
maxDownload,
timeout,
websocketMessage
);
return AddressRule.parse(hostObj, port, options);
}
private static <T> boolean check(UnmodifiableConfig config, String field, Class<T> klass) {
var value = config.get(field);
if (value == null || klass.isInstance(value)) return true;
LOG.warn("HTTP rule's {} is not a {}.", field, klass.getSimpleName());
return false;
}
private static <T extends Enum<T>> boolean checkEnum(UnmodifiableConfig config, String field, Class<T> klass) {
var value = config.get(field);
if (value == null) return true;
if (!(value instanceof String)) {
LOG.warn("HTTP rule's {} is not a string", field);
return false;
}
if (parseEnum(klass, (String) value) == null) {
LOG.warn("HTTP rule's {} is not a known option", field);
return false;
}
return true;
}
private static <T> Optional<T> get(UnmodifiableConfig config, String field, Class<T> klass) {
var value = config.get(field);
return klass.isInstance(value) ? Optional.of(klass.cast(value)) : Optional.empty();
}
private static <T extends Enum<T>> Optional<T> getEnum(UnmodifiableConfig config, String field, Class<T> klass) {
return get(config, field, String.class).map(x -> parseEnum(klass, x));
}
private static OptionalLong unboxOptLong(Optional<? extends Number> value) {
return value.map(Number::intValue).map(OptionalLong::of).orElse(OptionalLong.empty());
}
private static OptionalInt unboxOptInt(Optional<? extends Number> value) {
return value.map(Number::intValue).map(OptionalInt::of).orElse(OptionalInt.empty());
}
@Nullable
private static <T extends Enum<T>> T parseEnum(Class<T> klass, String x) {
for (var value : klass.getEnumConstants()) {
if (value.name().equalsIgnoreCase(x)) return value;
}
return null;
}
}