diff --git a/Plan/api/build.gradle b/Plan/api/build.gradle index 786fe4d12..6d30c6656 100644 --- a/Plan/api/build.gradle +++ b/Plan/api/build.gradle @@ -4,6 +4,7 @@ plugins { dependencies { compileOnly group: 'org.apache.commons', name: 'commons-lang3', version: '3.9' + compileOnly "com.google.code.gson:gson:$gsonVersion" } ext.apiVersion = '5.0-R0.3' diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/ResolverService.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/ResolverService.java new file mode 100644 index 000000000..71de1ea1f --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/ResolverService.java @@ -0,0 +1,71 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web; + +import com.djrapitops.plan.delivery.web.resolver.Resolver; + +import java.util.Optional; +import java.util.regex.Pattern; + +/** + * Service for modifying webserver request resolution. + *

+ * It is recommended to use plugin based namespace in your custom targets, + * eg. "/flyplugin/flying" to avoid collisions with other plugins. + * You can also use {@link #getResolver(String)} to check if target is already resolved. + * + * @author Rsl1122 + */ +public interface ResolverService { + + /** + * Register a new resolver. + * + * @param pluginName Name of the plugin that is registering (For error messages) + * @param start Start of the target to match against, eg "/example" will send "/example/target" etc to the Resolver. + * @param resolver {@link Resolver} to use for this + * @throws IllegalArgumentException If pluginName is null or empty. + */ + void registerResolver(String pluginName, String start, Resolver resolver); + + /** + * Register a new resolver with regex that maches start of target. + *

+ * NOTICE: It is recommended to avoid too generic regex like "/.*" to not override existing resolvers. + *

+ * Parameters (?param=value) are not included in the regex matching. + * + * @param pluginName Name of the plugin that is registering (For error messages) + * @param pattern Regex Pattern, "/example.*" will send "/exampletarget" etc to the Resolver. + * @param resolver {@link Resolver} to use for this. + * @throws IllegalArgumentException If pluginName is null or empty. + */ + void registerResolverForMatches(String pluginName, Pattern pattern, Resolver resolver); + + /** + * Obtain a {@link Resolver} for a target. + *

+ * First matching resolver will be returned. + * {@link #registerResolver} resolvers have higher priority than {@link #registerResolverForMatches}. + *

+ * Can be used when making Resolver middleware. + * + * @param target "/example/target" + * @return Resolver if registered or empty. + */ + Optional getResolver(String target); +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/MimeType.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/MimeType.java new file mode 100644 index 000000000..d31d1725d --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/MimeType.java @@ -0,0 +1,35 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +public final class MimeType { + public static final String HTML = "text/html"; + public static final String CSS = "text/css"; + public static final String JSON = "application/json"; + public static final String JS = "application/javascript"; + public static final String IMAGE = "image/gif"; + public static final String FAVICON = "image/x-icon"; + public static final String FONT_TFF = "application/x-font-ttf"; + public static final String FONT_WOFF = "application/font-woff"; + public static final String FONT_WOFF2 = "application/font-woff2"; + public static final String FONT_EOT = "application/vnd.ms-fontobject"; + public static final String FONT_BYTESTREAM = "application/octet-stream"; + + private MimeType() { + // Static variable class + } +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Parameters.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Parameters.java new file mode 100644 index 000000000..759ada8ee --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Parameters.java @@ -0,0 +1,44 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +import java.util.Map; +import java.util.Optional; + +/** + * Represents URI parameters described with {@code ?param=value¶m2=value2} in the URL. + * + * @author Rsl1122 + */ +public final class Parameters { + + private final Map byKey; + + public Parameters(Map byKey) { + this.byKey = byKey; + } + + /** + * Obtain an URI parameter by key. + * + * @param key Case-sensitive key, eg. 'param' in {@code ?param=value¶m2=value2} + * @return The value in the URL or empty if key is not specified in the URL. + */ + public Optional get(String key) { + return Optional.ofNullable(byKey.get(key)); + } +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Resolver.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Resolver.java new file mode 100644 index 000000000..fec439e73 --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Resolver.java @@ -0,0 +1,49 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +import java.util.Optional; + +public interface Resolver { + + /** + * Implement access control if authorization is enabled. + *

+ * Is not called when access control is not active. + * + * @param permissions WebUser that is accessing this page. + * @param target Target that is being accessed, /example/target + * @param parameters Parameters in the URL, ?param=value etc. + * @return true if allowed, false if response should be 403 (forbidden) + */ + boolean canAccess(WebUser permissions, URLTarget target, Parameters parameters); + + /** + * Implement request resolution. + * + * @param target Target that is being accessed, /example/target + * @param parameters Parameters in the URL, ?param=value etc. + * @return Response or empty if the response should be 404 (not found). + * @see Response for return value + */ + Optional resolve(URLTarget target, Parameters parameters); + + default ResponseBuilder newResponseBuilder() { + return new ResponseBuilder(); + } + +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Response.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Response.java new file mode 100644 index 000000000..d81e3f9c3 --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/Response.java @@ -0,0 +1,57 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * Represents a response that will be sent over HTTP. + * + * @author Rsl1122 + * @see MimeType for MIME types that are commonly used. + */ +public final class Response { + + final Map headers; + int code = 200; + byte[] bytes; + Charset charset; // can be null (raw bytes) + + Response() { + headers = new HashMap<>(); + } + + public byte[] getBytes() { + return bytes; + } + + public int getCode() { + return code; + } + + public Map getHeaders() { + return headers; + } + + public Optional getCharset() { + return Optional.ofNullable(charset); + } + +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java new file mode 100644 index 000000000..d6066b0e9 --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/ResponseBuilder.java @@ -0,0 +1,142 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +import com.google.gson.Gson; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class ResponseBuilder { + + private final Response response; + + ResponseBuilder() { + this.response = new Response(); + } + + /** + * Set MIME Type of the Response. + * + * @param mimeType MIME type of the Response https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types + * @return this builder. + * @see MimeType for common MIME types. + */ + public ResponseBuilder setMimeType(String mimeType) { + return setHeader("Content-Type", mimeType); + } + + /** + * Set HTTP Status code. + *

+ * Default status code is 200 (OK) if not set. + * + * @param code 1xx, 2xx, 3xx, 4xx, 5xx, https://developer.mozilla.org/en-US/docs/Web/HTTP/Status + * @return this builder. + */ + public ResponseBuilder setStatus(int code) { + return this; + } + + /** + * Set HTTP Response header. + * + * @param header Key of the header. https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers + * @param value Value for the header. + * @return this builder. + */ + public ResponseBuilder setHeader(String header, Object value) { + response.headers.put(header, value.toString()); + return this; + } + + /** + * Utility method for building redirects. + * + * @param url URL to redirect the client to with 302 Found. + * @return https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Location + */ + public ResponseBuilder redirectTo(String url) { + return setStatus(302).setHeader("Location", url).setContent(new byte[]{}); + } + + public ResponseBuilder setContent(byte[] bytes) { + response.bytes = bytes; + return setHeader("Content-Length", bytes.length); + } + + public ResponseBuilder setContent(String utf8String) { + return setContent(utf8String, StandardCharsets.UTF_8); + } + + public ResponseBuilder setContent(String content, Charset charset) { + String mimeType = response.headers.get("Content-Type"); + response.charset = charset; + + if (mimeType != null) { + String[] parts = mimeType.split(";"); + if (parts.length == 1) { + setMimeType(parts[0] + "; charset=" + charset.name().toLowerCase()); + } + } + + return setContent(content.getBytes(charset)); + } + + public ResponseBuilder setJSONContent(Object objectToSerialize) { + return setJSONContent(new Gson().toJson(objectToSerialize)); + } + + public ResponseBuilder setJSONContent(String json) { + setContent(json); + return setMimeType(MimeType.JSON); + } + + /** + * Finish building. + * + * @return Response. + * @throws InvalidResponseException if content was not defined (not even empty byte array). + * @throws InvalidResponseException if content has bytes, but MIME-type is not defined. + * @throws InvalidResponseException if status code is outside range 100-599. + * @see #setMimeType(String) to set MIME-type. + */ + public Response build() { + byte[] content = response.bytes; + exceptionIf(content == null, "Content not defined for Response"); + String mimeType = response.getHeaders().get("Content-Type"); + exceptionIf(content.length > 0 && mimeType == null, "MIME Type not defined for Response"); + exceptionIf(content.length > 0 && mimeType.isEmpty(), "MIME Type empty for Response"); + exceptionIf(response.code < 100 || response.code >= 600, "HTTP Status code out of bounds (" + response.code + ")"); + return response; + } + + private void exceptionIf(boolean value, String errorMsg) { + if (value) throw new InvalidResponseException(errorMsg); + } + + /** + * Thrown when {@link ResponseBuilder} is missing some parameters. + * + * @author Rsl1122 + */ + public static class InvalidResponseException extends IllegalStateException { + public InvalidResponseException(String s) { + super(s); + } + } +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/URLTarget.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/URLTarget.java new file mode 100644 index 000000000..ccaf54bb2 --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/URLTarget.java @@ -0,0 +1,66 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public final class URLTarget { + + // Internal representation + private final List parts; + private final String full; + + public URLTarget(String target) { + this.parts = parse(target); + full = target; + } + + private List parse(String target) { + String[] partArray = target.split("/"); + // Ignores index 0, assuming target starts with / + return Arrays.asList(partArray) + .subList(1, partArray.length); + } + + /** + * Obtain the full target. + * + * @return Example: "/target/path/in/url" + */ + public String asString() { + return full; + } + + /** + * Obtain part of the target by index of slashes in the URL. + * + * @param index Index from root, eg. /0/1/2/3 etc + * @return part after a '/' in the target, Example "target" for 0 in "/target/example", "" for 0 in "/", "" for 1 in "/example/" + */ + public Optional getPart(int index) { + if (index >= parts.size()) { + return Optional.empty(); + } + return Optional.of(parts.get(index)); + } + + public boolean endsWith(String suffix) { + return full.endsWith(suffix); + } +} diff --git a/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/WebUser.java b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/WebUser.java new file mode 100644 index 000000000..90ef2efff --- /dev/null +++ b/Plan/api/src/main/java/com/djrapitops/plan/delivery/web/resolver/WebUser.java @@ -0,0 +1,45 @@ +/* + * This file is part of Player Analytics (Plan). + * + * Plan is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License v3 as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Plan is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Plan. If not, see . + */ +package com.djrapitops.plan.delivery.web.resolver; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public final class WebUser { + + private final String name; + private final Set permissions; + + public WebUser(String name) { + this.name = name; + this.permissions = new HashSet<>(); + } + + public WebUser(String name, String... permissions) { + this(name); + this.permissions.addAll(Arrays.asList(permissions)); + } + + public boolean hasPermission(String permission) { + return permissions.contains(permission); + } + + public String getName() { + return name; + } +}