mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-03 15:08:12 +01:00
React html customization / public_html folder (#2862)
* Add public_html folder, configuration and access methods to it * Make Frontend BETA static resource resolution prefer public_html * Add resolver for getting any file in public_html from webserver * Test customized bundle loading from public_html * Update gradle wrapper to 7.6 * Wrote scripts to React build or run dev server through gradle * Disable cyclomatic-complexity check on PublicHtmlResolver * Throw bad request exception on IllegalPathException * Throw bad request exception on bad chars in URI query
This commit is contained in:
parent
413e087c4d
commit
09279cbb66
@ -20,7 +20,7 @@ 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 JS = "text/javascript";
|
||||
public static final String IMAGE = "image/gif";
|
||||
public static final String FAVICON = "image/x-icon";
|
||||
public static final String FONT_TTF = "application/x-font-ttf";
|
||||
|
@ -16,6 +16,7 @@
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.web.resolver.request;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
@ -82,6 +83,8 @@ public final class URIQuery {
|
||||
);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// If UTF-8 is unsupported, we have bigger problems
|
||||
} catch (IllegalArgumentException badCharacter) {
|
||||
throw new BadRequestException("URI Query contained bad character");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,14 @@ task yarnBundle(type: YarnTask) {
|
||||
args = ['run', 'build']
|
||||
}
|
||||
|
||||
task yarnStart(type: YarnTask) {
|
||||
logging.captureStandardOutput LogLevel.INFO
|
||||
inputs.file("$rootDir/react/dashboard/package.json")
|
||||
|
||||
dependsOn yarn_install
|
||||
args = ['run', 'start']
|
||||
}
|
||||
|
||||
task copyYarnBuildResults {
|
||||
inputs.files(fileTree("$rootDir/react/dashboard/build"))
|
||||
outputs.dir("$rootDir/common/build/resources/main/assets/plan/web")
|
||||
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.delivery;
|
||||
|
||||
import com.djrapitops.plan.delivery.formatting.Formatters;
|
||||
import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs;
|
||||
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||
import dagger.Lazy;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@ -28,14 +29,16 @@ public class DeliveryUtilities {
|
||||
|
||||
private final Lazy<Formatters> formatters;
|
||||
private final Lazy<Graphs> graphs;
|
||||
private final Lazy<PublicHtmlFiles> publicHtmlFiles;
|
||||
|
||||
@Inject
|
||||
public DeliveryUtilities(
|
||||
Lazy<Formatters> formatters,
|
||||
Lazy<Graphs> graphs
|
||||
) {
|
||||
Lazy<Graphs> graphs,
|
||||
Lazy<PublicHtmlFiles> publicHtmlFiles) {
|
||||
this.formatters = formatters;
|
||||
this.graphs = graphs;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
}
|
||||
|
||||
public Formatters getFormatters() {
|
||||
@ -46,4 +49,7 @@ public class DeliveryUtilities {
|
||||
return graphs.get();
|
||||
}
|
||||
|
||||
public PublicHtmlFiles getPublicHtmlFiles() {
|
||||
return publicHtmlFiles.get();
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,7 @@ import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
|
||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.version.VersionChecker;
|
||||
import dagger.Lazy;
|
||||
@ -59,6 +60,7 @@ public class PageFactory {
|
||||
|
||||
private final Lazy<VersionChecker> versionChecker;
|
||||
private final Lazy<PlanFiles> files;
|
||||
private final Lazy<PublicHtmlFiles> publicHtmlFiles;
|
||||
private final Lazy<PlanConfig> config;
|
||||
private final Lazy<Theme> theme;
|
||||
private final Lazy<DBSystem> dbSystem;
|
||||
@ -73,7 +75,7 @@ public class PageFactory {
|
||||
public PageFactory(
|
||||
Lazy<VersionChecker> versionChecker,
|
||||
Lazy<PlanFiles> files,
|
||||
Lazy<PlanConfig> config,
|
||||
Lazy<PublicHtmlFiles> publicHtmlFiles, Lazy<PlanConfig> config,
|
||||
Lazy<Theme> theme,
|
||||
Lazy<DBSystem> dbSystem,
|
||||
Lazy<ServerInfo> serverInfo,
|
||||
@ -85,6 +87,7 @@ public class PageFactory {
|
||||
) {
|
||||
this.versionChecker = versionChecker;
|
||||
this.files = files;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
this.config = config;
|
||||
this.theme = theme;
|
||||
this.dbSystem = dbSystem;
|
||||
@ -106,7 +109,8 @@ public class PageFactory {
|
||||
}
|
||||
|
||||
public Page reactPage() throws IOException {
|
||||
return new ReactPage(getBasePath(), getResource("index.html"));
|
||||
// TODO use ResourceService to apply snippets to the React index.html
|
||||
return new ReactPage(getBasePath(), getPublicHtmlOrJarResource("index.html"));
|
||||
}
|
||||
|
||||
private String getBasePath() {
|
||||
@ -244,16 +248,26 @@ public class PageFactory {
|
||||
return getResource(name).asString();
|
||||
}
|
||||
|
||||
public WebResource getResource(String name) throws IOException {
|
||||
public WebResource getResource(String resourceName) throws IOException {
|
||||
try {
|
||||
return ResourceService.getInstance().getResource("Plan", name,
|
||||
() -> files.get().getResourceFromJar("web/" + name).asWebResource()
|
||||
return ResourceService.getInstance().getResource("Plan", resourceName,
|
||||
() -> files.get().getResourceFromJar("web/" + resourceName).asWebResource()
|
||||
);
|
||||
} catch (UncheckedIOException readFail) {
|
||||
throw readFail.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
public WebResource getPublicHtmlOrJarResource(String resourceName) throws IOException {
|
||||
try {
|
||||
return publicHtmlFiles.get().findPublicHtmlResource(resourceName)
|
||||
.orElseGet(() -> files.get().getResourceFromJar("web/" + resourceName))
|
||||
.asWebResource();
|
||||
} catch (UncheckedIOException readFail) {
|
||||
throw readFail.getCause();
|
||||
}
|
||||
}
|
||||
|
||||
public Page loginPage() throws IOException {
|
||||
if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) {
|
||||
return reactPage();
|
||||
|
@ -17,13 +17,17 @@
|
||||
package com.djrapitops.plan.delivery.web;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import net.playeranalytics.plugin.server.PluginLogger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* ResolverService Implementation.
|
||||
@ -33,11 +37,16 @@ import java.util.regex.Pattern;
|
||||
@Singleton
|
||||
public class ResolverSvc implements ResolverService {
|
||||
|
||||
private final PlanConfig config;
|
||||
private final PluginLogger logger;
|
||||
|
||||
private final List<Container> basicResolvers;
|
||||
private final List<Container> regexResolvers;
|
||||
|
||||
@Inject
|
||||
public ResolverSvc() {
|
||||
public ResolverSvc(PlanConfig config, PluginLogger logger) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
basicResolvers = new ArrayList<>();
|
||||
regexResolvers = new ArrayList<>();
|
||||
}
|
||||
@ -78,6 +87,12 @@ public class ResolverSvc implements ResolverService {
|
||||
for (Container container : regexResolvers) {
|
||||
if (container.matcher.test(target)) resolvers.add(container.resolver);
|
||||
}
|
||||
if (config.isTrue(PluginSettings.DEV_MODE)) {
|
||||
logger.info("Match Resolvers " + target + " - " + resolvers.stream()
|
||||
.map(Object::getClass)
|
||||
.map(Class::getSimpleName)
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
return resolvers;
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.ResourceSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||
import com.djrapitops.plan.storage.file.Resource;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||
@ -50,7 +50,7 @@ import java.util.function.Supplier;
|
||||
public class ResourceSvc implements ResourceService {
|
||||
|
||||
public final Set<Snippet> snippets;
|
||||
private final PlanFiles files;
|
||||
private final PublicHtmlFiles publicHtmlFiles;
|
||||
private final ResourceSettings resourceSettings;
|
||||
private final Locale locale;
|
||||
private final PluginLogger logger;
|
||||
@ -58,13 +58,13 @@ public class ResourceSvc implements ResourceService {
|
||||
|
||||
@Inject
|
||||
public ResourceSvc(
|
||||
PlanFiles files,
|
||||
PublicHtmlFiles publicHtmlFiles,
|
||||
PlanConfig config,
|
||||
Locale locale,
|
||||
PluginLogger logger,
|
||||
ErrorLogger errorLogger
|
||||
) {
|
||||
this.files = files;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
this.resourceSettings = config.getResourceSettings();
|
||||
this.locale = locale;
|
||||
this.logger = logger;
|
||||
@ -155,7 +155,7 @@ public class ResourceSvc implements ResourceService {
|
||||
}
|
||||
|
||||
public WebResource getOrWriteCustomized(@Untrusted String fileName, Supplier<WebResource> source) throws IOException {
|
||||
Optional<Resource> customizedResource = files.getCustomizableResource(fileName);
|
||||
Optional<Resource> customizedResource = publicHtmlFiles.findCustomizedResource(fileName);
|
||||
if (customizedResource.isPresent()) {
|
||||
return readCustomized(customizedResource.get());
|
||||
} else {
|
||||
|
@ -28,6 +28,7 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@ -35,8 +36,11 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Task in charge of checking html customized files on enable to see if they are outdated.
|
||||
*
|
||||
* @deprecated Html customization system will be overhauled for React version of frontend.
|
||||
*/
|
||||
@Singleton
|
||||
@Deprecated(forRemoval = true, since = "#2260") // TODO Remove after Frontend BETA
|
||||
public class WebAssetVersionCheckTask extends TaskSystem.Task {
|
||||
|
||||
private final PlanConfig config;
|
||||
@ -110,7 +114,8 @@ public class WebAssetVersionCheckTask extends TaskSystem.Task {
|
||||
}
|
||||
|
||||
private Optional<AssetInfo> findOutdatedResource(String resource) {
|
||||
Optional<File> resourceFile = files.attemptToFind(resource);
|
||||
Path dir = config.getResourceSettings().getCustomizationDirectory();
|
||||
Optional<File> resourceFile = files.attemptToFind(dir, resource);
|
||||
Optional<Long> webAssetVersion = assetVersions.getAssetVersion(resource);
|
||||
if (resourceFile.isPresent() && webAssetVersion.isPresent() && webAssetVersion.get() > resourceFile.get().lastModified()) {
|
||||
return Optional.of(new AssetInfo(
|
||||
|
@ -30,10 +30,10 @@ import com.djrapitops.plan.delivery.web.resolver.ResponseBuilder;
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.NotFoundException;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
||||
import com.djrapitops.plan.delivery.webserver.auth.FailReason;
|
||||
import com.djrapitops.plan.exceptions.WebUserAuthException;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.locale.Locale;
|
||||
import com.djrapitops.plan.settings.locale.lang.ErrorPageLang;
|
||||
import com.djrapitops.plan.settings.theme.Theme;
|
||||
@ -41,6 +41,8 @@ import com.djrapitops.plan.storage.database.DBSystem;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||
import com.djrapitops.plan.storage.file.Resource;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import com.djrapitops.plan.utilities.java.Maps;
|
||||
import com.djrapitops.plan.utilities.java.UnaryChain;
|
||||
@ -53,8 +55,6 @@ import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
@ -70,6 +70,8 @@ public class ResponseFactory {
|
||||
private static final String STATIC_BUNDLE_FOLDER = "static";
|
||||
|
||||
private final PlanFiles files;
|
||||
private final PlanConfig config;
|
||||
private final PublicHtmlFiles publicHtmlFiles;
|
||||
private final PageFactory pageFactory;
|
||||
private final Locale locale;
|
||||
private final DBSystem dbSystem;
|
||||
@ -80,6 +82,7 @@ public class ResponseFactory {
|
||||
@Inject
|
||||
public ResponseFactory(
|
||||
PlanFiles files,
|
||||
PlanConfig config, PublicHtmlFiles publicHtmlFiles,
|
||||
PageFactory pageFactory,
|
||||
Locale locale,
|
||||
DBSystem dbSystem,
|
||||
@ -88,6 +91,8 @@ public class ResponseFactory {
|
||||
Lazy<Addresses> addresses
|
||||
) {
|
||||
this.files = files;
|
||||
this.config = config;
|
||||
this.publicHtmlFiles = publicHtmlFiles;
|
||||
this.pageFactory = pageFactory;
|
||||
this.locale = locale;
|
||||
this.dbSystem = dbSystem;
|
||||
@ -97,11 +102,23 @@ public class ResponseFactory {
|
||||
httpLastModifiedFormatter = formatters.httpLastModifiedLong();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UncheckedIOException If reading the resource fails
|
||||
*/
|
||||
public WebResource getResource(@Untrusted String resourceName) {
|
||||
return ResourceService.getInstance().getResource("Plan", resourceName,
|
||||
() -> files.getResourceFromJar("web/" + resourceName).asWebResource());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws UncheckedIOException If reading the resource fails
|
||||
*/
|
||||
private WebResource getPublicOrJarResource(@Untrusted String resourceName) {
|
||||
return publicHtmlFiles.findPublicHtmlResource(resourceName)
|
||||
.orElseGet(() -> files.getResourceFromJar("web/" + resourceName))
|
||||
.asWebResource();
|
||||
}
|
||||
|
||||
private static Response browserCachedNotChangedResponse() {
|
||||
return Response.builder()
|
||||
.setStatus(304)
|
||||
@ -168,7 +185,7 @@ public class ResponseFactory {
|
||||
}
|
||||
|
||||
private Response getCachedOrNew(long modified, String fileName, Function<String, Response> newResponseFunction) {
|
||||
WebResource resource = getResource(fileName);
|
||||
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
Optional<Long> lastModified = resource.getLastModified();
|
||||
if (lastModified.isPresent() && modified == lastModified.get()) {
|
||||
return browserCachedNotChangedResponse();
|
||||
@ -217,7 +234,7 @@ public class ResponseFactory {
|
||||
|
||||
public Response javaScriptResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
WebResource resource = getResource(fileName);
|
||||
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
String content = UnaryChain.of(resource.asString())
|
||||
.chain(this::replaceMainAddressPlaceholder)
|
||||
.chain(theme::replaceThemeColors)
|
||||
@ -267,7 +284,7 @@ public class ResponseFactory {
|
||||
|
||||
public Response cssResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
WebResource resource = getResource(fileName);
|
||||
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
String content = UnaryChain.of(resource.asString())
|
||||
.chain(theme::replaceThemeColors)
|
||||
.chain(contents -> StringUtils.replace(contents, "/static", getBasePath() + "/static"))
|
||||
@ -297,7 +314,7 @@ public class ResponseFactory {
|
||||
|
||||
public Response imageResponse(@Untrusted String fileName) {
|
||||
try {
|
||||
WebResource resource = getResource(fileName);
|
||||
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(MimeType.IMAGE)
|
||||
.setContent(resource)
|
||||
@ -333,7 +350,7 @@ public class ResponseFactory {
|
||||
type = MimeType.FONT_BYTESTREAM;
|
||||
}
|
||||
try {
|
||||
WebResource resource = getResource(fileName);
|
||||
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(type)
|
||||
.setContent(resource);
|
||||
@ -350,6 +367,47 @@ public class ResponseFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public Response publicHtmlResourceResponse(long modified, @Untrusted String fileName, String mimeType) {
|
||||
// Slightly different from getCachedOrNew
|
||||
WebResource resource = publicHtmlFiles.findPublicHtmlResource(fileName)
|
||||
.map(Resource::asWebResource)
|
||||
.orElse(null);
|
||||
if (resource == null) return null;
|
||||
|
||||
Optional<Long> lastModified = resource.getLastModified();
|
||||
if (lastModified.isPresent() && modified == lastModified.get()) {
|
||||
return browserCachedNotChangedResponse();
|
||||
} else {
|
||||
return publicHtmlResourceResponse(fileName, mimeType);
|
||||
}
|
||||
}
|
||||
|
||||
public Response publicHtmlResourceResponse(@Untrusted String fileName, String mimeType) {
|
||||
try {
|
||||
WebResource resource = publicHtmlFiles.findPublicHtmlResource(fileName)
|
||||
.map(Resource::asWebResource)
|
||||
.orElse(null);
|
||||
if (resource == null) return null;
|
||||
|
||||
byte[] content = resource.asBytes();
|
||||
ResponseBuilder responseBuilder = Response.builder()
|
||||
.setMimeType(mimeType)
|
||||
.setContent(content)
|
||||
.setStatus(200);
|
||||
|
||||
if (fileName.contains(STATIC_BUNDLE_FOLDER)) {
|
||||
resource.getLastModified().ifPresent(lastModified -> responseBuilder
|
||||
// Can't cache css bundles in browser since base path might change
|
||||
.setHeader(HttpHeader.CACHE_CONTROL.asString(), CacheStrategy.CHECK_ETAG)
|
||||
.setHeader(HttpHeader.LAST_MODIFIED.asString(), httpLastModifiedFormatter.apply(lastModified))
|
||||
.setHeader(HttpHeader.ETAG.asString(), lastModified));
|
||||
}
|
||||
return responseBuilder.build();
|
||||
} catch (UncheckedIOException e) {
|
||||
return notFound404("CSS File not found");
|
||||
}
|
||||
}
|
||||
|
||||
public Response redirectResponse(String location) {
|
||||
return Response.builder().redirectTo(location).build();
|
||||
}
|
||||
@ -405,49 +463,6 @@ public class ResponseFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public Response basicAuthFail(WebUserAuthException e) {
|
||||
try {
|
||||
FailReason failReason = e.getFailReason();
|
||||
String reason = failReason.getReason();
|
||||
if (failReason == FailReason.ERROR) {
|
||||
StringBuilder errorBuilder = new StringBuilder("</p><pre>");
|
||||
for (String line : getStackTrace(e.getCause())) {
|
||||
errorBuilder.append(line);
|
||||
}
|
||||
errorBuilder.append("</pre>");
|
||||
|
||||
reason += errorBuilder.toString();
|
||||
}
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
.setContent(pageFactory.errorPage(Icon.called("lock").build(), "401 Unauthorized", "Authentication Failed.</p><p><b>Reason: " + reason + "</b></p><p>").toHtml())
|
||||
.setStatus(401)
|
||||
.setHeader("WWW-Authenticate", "Basic realm=\"" + failReason.getReason() + "\"")
|
||||
.build();
|
||||
} catch (IOException jarReadFailed) {
|
||||
return forInternalError(e, "Failed to generate PromptAuthorizationResponse");
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> getStackTrace(Throwable throwable) {
|
||||
List<String> stackTrace = new ArrayList<>();
|
||||
stackTrace.add(throwable.toString());
|
||||
for (StackTraceElement element : throwable.getStackTrace()) {
|
||||
stackTrace.add(" " + element.toString());
|
||||
}
|
||||
|
||||
Throwable cause = throwable.getCause();
|
||||
if (cause != null) {
|
||||
List<String> causeTrace = getStackTrace(cause);
|
||||
if (!causeTrace.isEmpty()) {
|
||||
causeTrace.set(0, "Caused by: " + causeTrace.get(0));
|
||||
stackTrace.addAll(causeTrace);
|
||||
}
|
||||
}
|
||||
|
||||
return stackTrace;
|
||||
}
|
||||
|
||||
public Response forbidden403() {
|
||||
return forbidden403("Your user is not authorized to view this page.<br>"
|
||||
+ "If you believe this is an error contact staff to change your access level.");
|
||||
@ -485,23 +500,6 @@ public class ResponseFactory {
|
||||
.build();
|
||||
}
|
||||
|
||||
public Response basicAuth() {
|
||||
try {
|
||||
String tips = "<br>- Ensure you have registered a user with <b>/plan register</b><br>"
|
||||
+ "- Check that the username and password are correct<br>"
|
||||
+ "- Username and password are case-sensitive<br>"
|
||||
+ "<br>If you have forgotten your password, ask a staff member to delete your old user and re-register.";
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.HTML)
|
||||
.setContent(pageFactory.errorPage(Icon.called("lock").build(), "401 Unauthorized", "Authentication Failed." + tips).toHtml())
|
||||
.setStatus(401)
|
||||
.setHeader("WWW-Authenticate", "Basic realm=\"Plan WebUser (/plan register)\"")
|
||||
.build();
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate PromptAuthorizationResponse");
|
||||
}
|
||||
}
|
||||
|
||||
public Response badRequest(String errorMessage, String target) {
|
||||
return Response.builder()
|
||||
.setMimeType(MimeType.JSON)
|
||||
@ -528,7 +526,7 @@ public class ResponseFactory {
|
||||
try {
|
||||
return forPage(request, pageFactory.loginPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate player page");
|
||||
return forInternalError(e, "Failed to generate login page");
|
||||
}
|
||||
}
|
||||
|
||||
@ -536,7 +534,7 @@ public class ResponseFactory {
|
||||
try {
|
||||
return forPage(request, pageFactory.registerPage());
|
||||
} catch (IOException e) {
|
||||
return forInternalError(e, "Failed to generate player page");
|
||||
return forInternalError(e, "Failed to generate register page");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -86,6 +86,7 @@ public class ResponseResolver {
|
||||
private final ResolverService resolverService;
|
||||
private final ResponseFactory responseFactory;
|
||||
private final Lazy<WebServer> webServer;
|
||||
private final PublicHtmlResolver publicHtmlResolver;
|
||||
|
||||
@Inject
|
||||
public ResponseResolver(
|
||||
@ -100,6 +101,7 @@ public class ResponseResolver {
|
||||
RootPageResolver rootPageResolver,
|
||||
RootJSONResolver rootJSONResolver,
|
||||
StaticResourceResolver staticResourceResolver,
|
||||
PublicHtmlResolver publicHtmlResolver,
|
||||
|
||||
LoginPageResolver loginPageResolver,
|
||||
RegisterPageResolver registerPageResolver,
|
||||
@ -123,6 +125,7 @@ public class ResponseResolver {
|
||||
this.rootPageResolver = rootPageResolver;
|
||||
this.rootJSONResolver = rootJSONResolver;
|
||||
this.staticResourceResolver = staticResourceResolver;
|
||||
this.publicHtmlResolver = publicHtmlResolver;
|
||||
this.loginPageResolver = loginPageResolver;
|
||||
this.registerPageResolver = registerPageResolver;
|
||||
this.loginResolver = loginResolver;
|
||||
@ -157,6 +160,7 @@ public class ResponseResolver {
|
||||
|
||||
resolverService.registerResolverForMatches(plugin, Pattern.compile("^/$"), rootPageResolver);
|
||||
resolverService.registerResolverForMatches(plugin, Pattern.compile(StaticResourceResolver.PATH_REGEX), staticResourceResolver);
|
||||
resolverService.registerResolverForMatches(plugin, Pattern.compile(".*"), publicHtmlResolver);
|
||||
|
||||
resolverService.registerResolver(plugin, "/v1", rootJSONResolver.getResolver());
|
||||
resolverService.registerResolver(plugin, "/docs/swagger.json", swaggerJsonResolver);
|
||||
|
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.delivery.webserver.resolver;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.MimeType;
|
||||
import com.djrapitops.plan.delivery.web.resolver.NoAuthResolver;
|
||||
import com.djrapitops.plan.delivery.web.resolver.Response;
|
||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||
import com.djrapitops.plan.delivery.webserver.ResponseFactory;
|
||||
import com.djrapitops.plan.identification.Identifiers;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Resolves any files in public_html folder.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class PublicHtmlResolver implements NoAuthResolver {
|
||||
|
||||
private final ResponseFactory responseFactory;
|
||||
|
||||
@Inject
|
||||
public PublicHtmlResolver(ResponseFactory responseFactory) {
|
||||
this.responseFactory = responseFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Response> resolve(Request request) {
|
||||
return Optional.ofNullable(getResponse(request));
|
||||
}
|
||||
|
||||
@SuppressWarnings("OptionalIsPresent") // More readable
|
||||
private Response getResponse(Request request) {
|
||||
@Untrusted String resource = request.getPath().asString().substring(1);
|
||||
@Untrusted Optional<Long> etag = Identifiers.getEtag(request);
|
||||
|
||||
Optional<String> mimeType = getMimeType(resource);
|
||||
if (mimeType.isEmpty()) return null;
|
||||
|
||||
return etag.map(tag -> responseFactory.publicHtmlResourceResponse(tag, resource, mimeType.get()))
|
||||
.orElseGet(() -> responseFactory.publicHtmlResourceResponse(resource, mimeType.get()));
|
||||
}
|
||||
|
||||
private Optional<String> getMimeType(@Untrusted String resource) {
|
||||
return Optional.ofNullable(getNullableMimeType(resource));
|
||||
}
|
||||
|
||||
// Checkstyle.OFF: CyclomaticComplexity
|
||||
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types
|
||||
private String getNullableMimeType(@Untrusted String resource) {
|
||||
if (resource.endsWith(".avif")) return "image/avif";
|
||||
if (resource.endsWith(".bin")) return "application/octet-stream";
|
||||
if (resource.endsWith(".bmp")) return "image/bmp";
|
||||
if (resource.endsWith(".css")) return MimeType.CSS;
|
||||
if (resource.endsWith(".csv")) return "text/csv";
|
||||
if (resource.endsWith(".eot")) return MimeType.FONT_BYTESTREAM;
|
||||
if (resource.endsWith(".gif")) return MimeType.IMAGE;
|
||||
if (resource.endsWith(".html")) return MimeType.HTML;
|
||||
if (resource.endsWith(".htm")) return MimeType.HTML;
|
||||
if (resource.endsWith(".ico")) return "image/vnd.microsoft.icon";
|
||||
if (resource.endsWith(".ics")) return "text/calendar";
|
||||
if (resource.endsWith(".js")) return MimeType.JS;
|
||||
if (resource.endsWith(".jpeg")) return MimeType.IMAGE;
|
||||
if (resource.endsWith(".jpg")) return MimeType.IMAGE;
|
||||
if (resource.endsWith(".json")) return MimeType.JSON;
|
||||
if (resource.endsWith(".jsonld")) return "application/ld+json";
|
||||
if (resource.endsWith(".mjs")) return MimeType.JS;
|
||||
if (resource.endsWith(".otf")) return MimeType.FONT_BYTESTREAM;
|
||||
if (resource.endsWith(".pdf")) return "application/pdf";
|
||||
if (resource.endsWith(".php")) return "application/x-httpd-php";
|
||||
if (resource.endsWith(".png")) return MimeType.IMAGE;
|
||||
if (resource.endsWith(".rtf")) return "application/rtf";
|
||||
if (resource.endsWith(".svg")) return "image/svg+xml";
|
||||
if (resource.endsWith(".tif")) return "image/tiff";
|
||||
if (resource.endsWith(".tiff")) return "image/tiff";
|
||||
if (resource.endsWith(".ttf")) return "text/plain";
|
||||
if (resource.endsWith(".txt")) return "text/plain";
|
||||
if (resource.endsWith(".woff")) return MimeType.FONT_BYTESTREAM;
|
||||
if (resource.endsWith(".woff2")) return MimeType.FONT_BYTESTREAM;
|
||||
if (resource.endsWith(".xml")) return "application/xml";
|
||||
|
||||
return null;
|
||||
}
|
||||
// Checkstyle.ON: CyclomaticComplexity
|
||||
|
||||
}
|
@ -18,6 +18,7 @@ package com.djrapitops.plan.settings.config;
|
||||
|
||||
import com.djrapitops.plan.settings.config.paths.CustomizedFileSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -72,9 +73,16 @@ public class ResourceSettings {
|
||||
}
|
||||
|
||||
public Path getCustomizationDirectory() {
|
||||
Path exportDirectory = Paths.get(config.get(CustomizedFileSettings.PATH));
|
||||
return exportDirectory.isAbsolute()
|
||||
? exportDirectory
|
||||
: files.getDataDirectory().resolve(exportDirectory);
|
||||
Path customizationDirectory = Paths.get(config.get(CustomizedFileSettings.PATH));
|
||||
return customizationDirectory.isAbsolute()
|
||||
? customizationDirectory
|
||||
: files.getDataDirectory().resolve(customizationDirectory);
|
||||
}
|
||||
|
||||
public Path getPublicHtmlDirectory() {
|
||||
Path customizationDirectory = Paths.get(config.get(WebserverSettings.PUBLIC_HTML_PATH));
|
||||
return customizationDirectory.isAbsolute()
|
||||
? customizationDirectory
|
||||
: files.getDataDirectory().resolve(customizationDirectory);
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ public class WebserverSettings {
|
||||
public static final Setting<Boolean> DISABLED_AUTHENTICATION = new BooleanSetting("Webserver.Security.Disable_authentication");
|
||||
public static final Setting<Boolean> LOG_ACCESS_TO_CONSOLE = new BooleanSetting("Webserver.Security.Access_log.Print_to_console");
|
||||
public static final Setting<String> EXTERNAL_LINK = new StringSetting("Webserver.External_Webserver_address");
|
||||
public static final Setting<String> PUBLIC_HTML_PATH = new StringSetting("Webserver.Public_html_directory");
|
||||
|
||||
public static final Setting<Long> REDUCED_REFRESH_BARRIER = new TimeSetting("Webserver.Cache.Reduced_refresh_barrier");
|
||||
public static final Setting<Long> INVALIDATE_QUERY_RESULTS = new TimeSetting("Webserver.Cache.Invalidate_query_results_on_disk_after");
|
||||
|
@ -19,8 +19,6 @@ package com.djrapitops.plan.storage.file;
|
||||
import com.djrapitops.plan.SubSystem;
|
||||
import com.djrapitops.plan.delivery.web.AssetVersions;
|
||||
import com.djrapitops.plan.exceptions.EnableException;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.CustomizedFileSettings;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
import dagger.Lazy;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
@ -50,19 +48,16 @@ public class PlanFiles implements SubSystem {
|
||||
private final File configFile;
|
||||
|
||||
private final Lazy<AssetVersions> assetVersions;
|
||||
private final Lazy<PlanConfig> config;
|
||||
|
||||
@Inject
|
||||
public PlanFiles(
|
||||
@Named("dataFolder") File dataFolder,
|
||||
JarResource.StreamFunction getResourceStream,
|
||||
Lazy<AssetVersions> assetVersions,
|
||||
Lazy<PlanConfig> config
|
||||
Lazy<AssetVersions> assetVersions
|
||||
) {
|
||||
this.dataFolder = dataFolder;
|
||||
this.getResourceStream = getResourceStream;
|
||||
this.assetVersions = assetVersions;
|
||||
this.config = config;
|
||||
this.configFile = getFileFromPluginFolder("config.yml");
|
||||
}
|
||||
|
||||
@ -150,28 +145,7 @@ public class PlanFiles implements SubSystem {
|
||||
return new FileResource(resourceName, getFileFromPluginFolder(resourceName));
|
||||
}
|
||||
|
||||
// TODO Customized file logic should be moved to another class so the circular dependency on config can be removed.
|
||||
public Optional<Resource> getCustomizableResource(@Untrusted String resourceName) {
|
||||
return Optional.ofNullable(findCustomized(resourceName));
|
||||
}
|
||||
|
||||
private Resource findCustomized(@Untrusted String resourceName) {
|
||||
if (config.get().isTrue(CustomizedFileSettings.WEB_DEV_MODE)) {
|
||||
// Bypass cache in web developer mode.
|
||||
return getFileResource(resourceName);
|
||||
} else {
|
||||
return ResourceCache.getOrCache(resourceName, () -> getFileResource(resourceName));
|
||||
}
|
||||
}
|
||||
|
||||
private FileResource getFileResource(@Untrusted String resourceName) {
|
||||
return attemptToFind(resourceName)
|
||||
.map(found -> new FileResource(resourceName, found))
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
public Optional<File> attemptToFind(@Untrusted String resourceName) {
|
||||
Path dir = config.get().getResourceSettings().getCustomizationDirectory();
|
||||
public Optional<File> attemptToFind(Path dir, @Untrusted String resourceName) {
|
||||
if (dir.toFile().exists() && dir.toFile().isDirectory()) {
|
||||
// Path may be absolute due to resolving untrusted path
|
||||
@Untrusted Path asPath = dir.resolve(resourceName);
|
||||
|
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.storage.file;
|
||||
|
||||
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.InvalidPathException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Access to public_html folder and its contents.
|
||||
*
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@Singleton
|
||||
public class PublicHtmlFiles {
|
||||
|
||||
private final PlanConfig config;
|
||||
|
||||
@Inject
|
||||
public PublicHtmlFiles(PlanConfig config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
public Optional<Resource> findCustomizedResource(@Untrusted String resourceName) {
|
||||
Path customizationDirectory = config.getResourceSettings().getCustomizationDirectory();
|
||||
return attemptToFind(customizationDirectory, resourceName)
|
||||
.map(found -> new FileResource(resourceName, found));
|
||||
}
|
||||
|
||||
public Optional<Resource> findPublicHtmlResource(@Untrusted String resourceName) {
|
||||
Path publicHtmlDirectory = config.getResourceSettings().getPublicHtmlDirectory();
|
||||
return attemptToFind(publicHtmlDirectory, resourceName)
|
||||
.map(found -> new FileResource(resourceName, found));
|
||||
}
|
||||
|
||||
private Optional<File> attemptToFind(Path from, @Untrusted String resourceName) {
|
||||
if (!Files.exists(from)) {
|
||||
try {
|
||||
Files.createDirectories(from);
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException("Could not create folder configured in '" + WebserverSettings.PUBLIC_HTML_PATH.getPath() + "'-setting, please create it manually.", e);
|
||||
}
|
||||
}
|
||||
if (from.toFile().exists() && from.toFile().isDirectory()) {
|
||||
@Untrusted Path asPath;
|
||||
try {
|
||||
asPath = from.resolve(resourceName).normalize();
|
||||
} catch (InvalidPathException badCharacter) {
|
||||
throw new BadRequestException("Requested resource name contained a bad character.");
|
||||
}
|
||||
// Path may be absolute due to resolving untrusted path
|
||||
if (!asPath.startsWith(from)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
// Now it should be trustworthy
|
||||
File found = asPath.toFile();
|
||||
if (found.exists()) {
|
||||
return Optional.of(found);
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
@ -46,19 +46,10 @@ Webserver:
|
||||
# Internal IP usually does not need to be changed, only change it if you know what you're doing!
|
||||
# 0.0.0.0 allocates Internal (local) IP automatically for the WebServer.
|
||||
Internal_IP: 0.0.0.0
|
||||
Cache:
|
||||
Reduced_refresh_barrier:
|
||||
Time: 15
|
||||
Unit: SECONDS
|
||||
Invalidate_query_results_on_disk_after:
|
||||
Time: 7
|
||||
Unit: DAYS
|
||||
Invalidate_disk_cache_after:
|
||||
Time: 2
|
||||
Unit: DAYS
|
||||
Invalidate_memory_cache_after:
|
||||
Time: 5
|
||||
Unit: MINUTES
|
||||
# Use absolute path ("C:\Example\Path", "/var/example/path") or relative ("public_html" -> {server}/plugins/Plan/public_html)
|
||||
# NOTE: All files in this directory can be read by anyone who can access the webserver.
|
||||
# This can be used to host certbot http challenge file, or for customizing Plan React-bundle
|
||||
Public_html_directory: "public_html"
|
||||
Security:
|
||||
SSL_certificate:
|
||||
KeyStore_path: Cert.jks
|
||||
@ -88,6 +79,19 @@ Webserver:
|
||||
Unit: HOURS
|
||||
Disable_Webserver: false
|
||||
External_Webserver_address: "https://www.example.address"
|
||||
Cache:
|
||||
Reduced_refresh_barrier:
|
||||
Time: 15
|
||||
Unit: SECONDS
|
||||
Invalidate_query_results_on_disk_after:
|
||||
Time: 7
|
||||
Unit: DAYS
|
||||
Invalidate_disk_cache_after:
|
||||
Time: 2
|
||||
Unit: DAYS
|
||||
Invalidate_memory_cache_after:
|
||||
Time: 5
|
||||
Unit: MINUTES
|
||||
# -----------------------------------------------------
|
||||
Data_gathering:
|
||||
Geolocations: true
|
||||
|
@ -48,19 +48,10 @@ Webserver:
|
||||
# Internal IP usually does not need to be changed, only change it if you know what you're doing!
|
||||
# 0.0.0.0 allocates Internal (local) IP automatically for the WebServer.
|
||||
Internal_IP: 0.0.0.0
|
||||
Cache:
|
||||
Reduced_refresh_barrier:
|
||||
Time: 15
|
||||
Unit: SECONDS
|
||||
Invalidate_query_results_on_disk_after:
|
||||
Time: 7
|
||||
Unit: DAYS
|
||||
Invalidate_disk_cache_after:
|
||||
Time: 2
|
||||
Unit: DAYS
|
||||
Invalidate_memory_cache_after:
|
||||
Time: 5
|
||||
Unit: MINUTES
|
||||
# Use absolute path ("C:\Example\Path", "/var/example/path") or relative ("public_html" -> {server}/plugins/Plan/public_html)
|
||||
# NOTE: All files in this directory can be read by anyone who can access the webserver.
|
||||
# This can be used to host certbot http challenge file, or for customizing Plan React-bundle
|
||||
Public_html_directory: "public_html"
|
||||
Security:
|
||||
SSL_certificate:
|
||||
KeyStore_path: Cert.jks
|
||||
@ -90,6 +81,19 @@ Webserver:
|
||||
Unit: HOURS
|
||||
Disable_Webserver: false
|
||||
External_Webserver_address: https://www.example.address
|
||||
Cache:
|
||||
Reduced_refresh_barrier:
|
||||
Time: 15
|
||||
Unit: SECONDS
|
||||
Invalidate_query_results_on_disk_after:
|
||||
Time: 7
|
||||
Unit: DAYS
|
||||
Invalidate_disk_cache_after:
|
||||
Time: 2
|
||||
Unit: DAYS
|
||||
Invalidate_memory_cache_after:
|
||||
Time: 5
|
||||
Unit: MINUTES
|
||||
# -----------------------------------------------------
|
||||
Data_gathering:
|
||||
Geolocations: true
|
||||
|
@ -16,8 +16,6 @@
|
||||
*/
|
||||
package com.djrapitops.plan.storage.file;
|
||||
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.CustomizedFileSettings;
|
||||
import extension.FullSystemExtension;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@ -28,10 +26,8 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
@ -49,17 +45,4 @@ class PlanFilesTest {
|
||||
File file = files.getFileFromPluginFolder(testFile.toFile().getAbsolutePath());
|
||||
assertNotEquals(testFile.toFile().getAbsolutePath(), file.getAbsolutePath());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("getCustomizableResource has no Path Traversal vulnerability")
|
||||
void getCustomizableResourceDoesNotAllowAbsolutePathTraversal(@TempDir Path tempDir, PlanConfig config, PlanFiles files) throws IOException {
|
||||
config.set(CustomizedFileSettings.PATH, tempDir.resolve("customized").toFile().getAbsolutePath());
|
||||
|
||||
Path testFile = tempDir.resolve("file.db");
|
||||
Files.createDirectories(tempDir.getParent());
|
||||
Files.createFile(testFile);
|
||||
|
||||
Optional<Resource> resource = files.getCustomizableResource(testFile.toFile().getAbsolutePath());
|
||||
assertTrue(resource.isEmpty());
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.djrapitops.plan.storage.file;
|
||||
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.CustomizedFileSettings;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import extension.FullSystemExtension;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* @author AuroraLS3
|
||||
*/
|
||||
@ExtendWith(FullSystemExtension.class)
|
||||
class PublicHtmlFilesTest {
|
||||
|
||||
@Test
|
||||
@DisplayName("findCustomizedResource has no Path Traversal vulnerability")
|
||||
void getCustomizableResourceDoesNotAllowAbsolutePathTraversal(@TempDir Path tempDir, PlanConfig config, PublicHtmlFiles files) throws IOException {
|
||||
Path directory = tempDir.resolve("customized");
|
||||
config.set(CustomizedFileSettings.PATH, directory.toFile().getAbsolutePath());
|
||||
|
||||
Path testFile = tempDir.resolve("file.db");
|
||||
Files.createDirectories(directory);
|
||||
Files.createDirectories(testFile.getParent());
|
||||
Files.createFile(testFile);
|
||||
|
||||
Optional<Resource> resource = files.findCustomizedResource(testFile.toFile().getAbsolutePath());
|
||||
assertTrue(resource.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findPublicHtmlResource has no Path Traversal vulnerability")
|
||||
void findPublicHtmlResourceDoesNotAllowAbsolutePathTraversal(@TempDir Path tempDir, PlanConfig config, PublicHtmlFiles files) throws IOException {
|
||||
Path directory = tempDir.resolve("public_html");
|
||||
config.set(WebserverSettings.PUBLIC_HTML_PATH, directory.toFile().getAbsolutePath());
|
||||
|
||||
Path testFile = tempDir.resolve("file.db");
|
||||
Files.createDirectories(directory);
|
||||
Files.createDirectories(testFile.getParent());
|
||||
Files.createFile(testFile);
|
||||
|
||||
Optional<Resource> resource = files.findPublicHtmlResource(testFile.toFile().getAbsolutePath());
|
||||
assertTrue(resource.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findCustomizedResource finds File")
|
||||
void findCustomizedResourceFindsFile(@TempDir Path tempDir, PlanConfig config, PublicHtmlFiles files) throws IOException {
|
||||
Path directory = tempDir.resolve("customized");
|
||||
config.set(CustomizedFileSettings.PATH, directory.toFile().getAbsolutePath());
|
||||
|
||||
Path testFile = directory.resolve("file.db");
|
||||
Files.createDirectories(directory);
|
||||
Files.createDirectories(testFile.getParent());
|
||||
Files.createFile(testFile);
|
||||
|
||||
Optional<Resource> resource = files.findCustomizedResource("file.db");
|
||||
assertTrue(resource.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("findPublicHtmlResource finds File")
|
||||
void findPublicHtmlResourceFindsFile(@TempDir Path tempDir, PlanConfig config, PublicHtmlFiles files) throws IOException {
|
||||
Path directory = tempDir.resolve("public_html");
|
||||
config.set(WebserverSettings.PUBLIC_HTML_PATH, directory.toFile().getAbsolutePath());
|
||||
|
||||
Path testFile = directory.resolve("file.db");
|
||||
Files.createDirectories(directory);
|
||||
Files.createDirectories(testFile.getParent());
|
||||
Files.createFile(testFile);
|
||||
|
||||
Optional<Resource> resource = files.findPublicHtmlResource("file.db");
|
||||
assertTrue(resource.isPresent());
|
||||
}
|
||||
}
|
@ -18,12 +18,14 @@ package extension;
|
||||
|
||||
import com.djrapitops.plan.PlanSystem;
|
||||
import com.djrapitops.plan.commands.PlanCommand;
|
||||
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||
import com.djrapitops.plan.delivery.export.Exporter;
|
||||
import com.djrapitops.plan.identification.ServerUUID;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||
import com.djrapitops.plan.storage.database.Database;
|
||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||
import org.junit.jupiter.api.extension.*;
|
||||
import utilities.RandomData;
|
||||
import utilities.dagger.PlanPluginComponent;
|
||||
@ -86,6 +88,8 @@ public class FullSystemExtension implements ParameterResolver, BeforeAllCallback
|
||||
PlanPluginComponent.class.equals(type) ||
|
||||
PlanCommand.class.equals(type) ||
|
||||
Database.class.equals(type) ||
|
||||
DeliveryUtilities.class.equals(type) ||
|
||||
PublicHtmlFiles.class.equals(type) ||
|
||||
Exporter.class.equals(type);
|
||||
}
|
||||
|
||||
@ -121,6 +125,12 @@ public class FullSystemExtension implements ParameterResolver, BeforeAllCallback
|
||||
if (Database.class.equals(type)) {
|
||||
return planSystem.getDatabaseSystem().getDatabase();
|
||||
}
|
||||
if (DeliveryUtilities.class.equals(type)) {
|
||||
return planSystem.getDeliveryUtilities();
|
||||
}
|
||||
if (PublicHtmlFiles.class.equals(type)) {
|
||||
return planSystem.getDeliveryUtilities().getPublicHtmlFiles();
|
||||
}
|
||||
if (Exporter.class.equals(type)) {
|
||||
return planSystem.getExportSystem().getExporter();
|
||||
}
|
||||
|
BIN
Plan/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
Plan/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@ -1,5 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
285
Plan/gradlew
vendored
285
Plan/gradlew
vendored
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env sh
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -17,67 +17,101 @@
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MSYS* | MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
JAVACMD=java
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
@ -106,80 +140,105 @@ location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
|
11
Plan/gradlew.bat
vendored
11
Plan/gradlew.bat
vendored
@ -26,6 +26,7 @@ if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
2
Plan/react/buildBundle.bat
Normal file
2
Plan/react/buildBundle.bat
Normal file
@ -0,0 +1,2 @@
|
||||
cd ..
|
||||
./gradlew :common:yarnBundle
|
4
Plan/react/buildBundle.sh
Normal file
4
Plan/react/buildBundle.sh
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd ..
|
||||
./gradlew :common:yarnBundle
|
2
Plan/react/devServer.bat
Normal file
2
Plan/react/devServer.bat
Normal file
@ -0,0 +1,2 @@
|
||||
cd ..
|
||||
./gradlew :common:yarnStart
|
4
Plan/react/devServer.sh
Normal file
4
Plan/react/devServer.sh
Normal file
@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cd ..
|
||||
./gradlew :common:yarnStart
|
@ -19,7 +19,6 @@ package com.djrapitops.plan.storage.file;
|
||||
import com.djrapitops.plan.PlanPlugin;
|
||||
import com.djrapitops.plan.PlanSponge;
|
||||
import com.djrapitops.plan.delivery.web.AssetVersions;
|
||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||
import dagger.Lazy;
|
||||
import org.spongepowered.api.Sponge;
|
||||
import org.spongepowered.api.resource.ResourcePath;
|
||||
@ -46,10 +45,9 @@ public class SpongePlanFiles extends PlanFiles {
|
||||
@Named("dataFolder") File dataFolder,
|
||||
JarResource.StreamFunction getResourceStream,
|
||||
PlanPlugin plugin,
|
||||
Lazy<AssetVersions> assetVersions,
|
||||
Lazy<PlanConfig> config
|
||||
Lazy<AssetVersions> assetVersions
|
||||
) {
|
||||
super(dataFolder, getResourceStream, assetVersions, config);
|
||||
super(dataFolder, getResourceStream, assetVersions);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user