mirror of
https://github.com/plan-player-analytics/Plan.git
synced 2025-01-05 07:58:08 +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 HTML = "text/html";
|
||||||
public static final String CSS = "text/css";
|
public static final String CSS = "text/css";
|
||||||
public static final String JSON = "application/json";
|
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 IMAGE = "image/gif";
|
||||||
public static final String FAVICON = "image/x-icon";
|
public static final String FAVICON = "image/x-icon";
|
||||||
public static final String FONT_TTF = "application/x-font-ttf";
|
public static final String FONT_TTF = "application/x-font-ttf";
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.delivery.web.resolver.request;
|
package com.djrapitops.plan.delivery.web.resolver.request;
|
||||||
|
|
||||||
|
import com.djrapitops.plan.delivery.web.resolver.exception.BadRequestException;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
import java.io.UnsupportedEncodingException;
|
||||||
@ -82,6 +83,8 @@ public final class URIQuery {
|
|||||||
);
|
);
|
||||||
} catch (UnsupportedEncodingException e) {
|
} catch (UnsupportedEncodingException e) {
|
||||||
// If UTF-8 is unsupported, we have bigger problems
|
// 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']
|
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 {
|
task copyYarnBuildResults {
|
||||||
inputs.files(fileTree("$rootDir/react/dashboard/build"))
|
inputs.files(fileTree("$rootDir/react/dashboard/build"))
|
||||||
outputs.dir("$rootDir/common/build/resources/main/assets/plan/web")
|
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.formatting.Formatters;
|
||||||
import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs;
|
import com.djrapitops.plan.delivery.rendering.json.graphs.Graphs;
|
||||||
|
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@ -28,14 +29,16 @@ public class DeliveryUtilities {
|
|||||||
|
|
||||||
private final Lazy<Formatters> formatters;
|
private final Lazy<Formatters> formatters;
|
||||||
private final Lazy<Graphs> graphs;
|
private final Lazy<Graphs> graphs;
|
||||||
|
private final Lazy<PublicHtmlFiles> publicHtmlFiles;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public DeliveryUtilities(
|
public DeliveryUtilities(
|
||||||
Lazy<Formatters> formatters,
|
Lazy<Formatters> formatters,
|
||||||
Lazy<Graphs> graphs
|
Lazy<Graphs> graphs,
|
||||||
) {
|
Lazy<PublicHtmlFiles> publicHtmlFiles) {
|
||||||
this.formatters = formatters;
|
this.formatters = formatters;
|
||||||
this.graphs = graphs;
|
this.graphs = graphs;
|
||||||
|
this.publicHtmlFiles = publicHtmlFiles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Formatters getFormatters() {
|
public Formatters getFormatters() {
|
||||||
@ -46,4 +49,7 @@ public class DeliveryUtilities {
|
|||||||
return graphs.get();
|
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.containers.ContainerFetchQueries;
|
||||||
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
import com.djrapitops.plan.storage.database.queries.objects.ServerQueries;
|
||||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
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.utilities.dev.Untrusted;
|
||||||
import com.djrapitops.plan.version.VersionChecker;
|
import com.djrapitops.plan.version.VersionChecker;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
@ -59,6 +60,7 @@ public class PageFactory {
|
|||||||
|
|
||||||
private final Lazy<VersionChecker> versionChecker;
|
private final Lazy<VersionChecker> versionChecker;
|
||||||
private final Lazy<PlanFiles> files;
|
private final Lazy<PlanFiles> files;
|
||||||
|
private final Lazy<PublicHtmlFiles> publicHtmlFiles;
|
||||||
private final Lazy<PlanConfig> config;
|
private final Lazy<PlanConfig> config;
|
||||||
private final Lazy<Theme> theme;
|
private final Lazy<Theme> theme;
|
||||||
private final Lazy<DBSystem> dbSystem;
|
private final Lazy<DBSystem> dbSystem;
|
||||||
@ -73,7 +75,7 @@ public class PageFactory {
|
|||||||
public PageFactory(
|
public PageFactory(
|
||||||
Lazy<VersionChecker> versionChecker,
|
Lazy<VersionChecker> versionChecker,
|
||||||
Lazy<PlanFiles> files,
|
Lazy<PlanFiles> files,
|
||||||
Lazy<PlanConfig> config,
|
Lazy<PublicHtmlFiles> publicHtmlFiles, Lazy<PlanConfig> config,
|
||||||
Lazy<Theme> theme,
|
Lazy<Theme> theme,
|
||||||
Lazy<DBSystem> dbSystem,
|
Lazy<DBSystem> dbSystem,
|
||||||
Lazy<ServerInfo> serverInfo,
|
Lazy<ServerInfo> serverInfo,
|
||||||
@ -85,6 +87,7 @@ public class PageFactory {
|
|||||||
) {
|
) {
|
||||||
this.versionChecker = versionChecker;
|
this.versionChecker = versionChecker;
|
||||||
this.files = files;
|
this.files = files;
|
||||||
|
this.publicHtmlFiles = publicHtmlFiles;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
this.dbSystem = dbSystem;
|
this.dbSystem = dbSystem;
|
||||||
@ -106,7 +109,8 @@ public class PageFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Page reactPage() throws IOException {
|
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() {
|
private String getBasePath() {
|
||||||
@ -244,16 +248,26 @@ public class PageFactory {
|
|||||||
return getResource(name).asString();
|
return getResource(name).asString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public WebResource getResource(String name) throws IOException {
|
public WebResource getResource(String resourceName) throws IOException {
|
||||||
try {
|
try {
|
||||||
return ResourceService.getInstance().getResource("Plan", name,
|
return ResourceService.getInstance().getResource("Plan", resourceName,
|
||||||
() -> files.get().getResourceFromJar("web/" + name).asWebResource()
|
() -> files.get().getResourceFromJar("web/" + resourceName).asWebResource()
|
||||||
);
|
);
|
||||||
} catch (UncheckedIOException readFail) {
|
} catch (UncheckedIOException readFail) {
|
||||||
throw readFail.getCause();
|
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 {
|
public Page loginPage() throws IOException {
|
||||||
if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) {
|
if (config.get().isTrue(PluginSettings.FRONTEND_BETA)) {
|
||||||
return reactPage();
|
return reactPage();
|
||||||
|
@ -17,13 +17,17 @@
|
|||||||
package com.djrapitops.plan.delivery.web;
|
package com.djrapitops.plan.delivery.web;
|
||||||
|
|
||||||
import com.djrapitops.plan.delivery.web.resolver.Resolver;
|
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 com.djrapitops.plan.utilities.dev.Untrusted;
|
||||||
|
import net.playeranalytics.plugin.server.PluginLogger;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ResolverService Implementation.
|
* ResolverService Implementation.
|
||||||
@ -33,11 +37,16 @@ import java.util.regex.Pattern;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class ResolverSvc implements ResolverService {
|
public class ResolverSvc implements ResolverService {
|
||||||
|
|
||||||
|
private final PlanConfig config;
|
||||||
|
private final PluginLogger logger;
|
||||||
|
|
||||||
private final List<Container> basicResolvers;
|
private final List<Container> basicResolvers;
|
||||||
private final List<Container> regexResolvers;
|
private final List<Container> regexResolvers;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ResolverSvc() {
|
public ResolverSvc(PlanConfig config, PluginLogger logger) {
|
||||||
|
this.config = config;
|
||||||
|
this.logger = logger;
|
||||||
basicResolvers = new ArrayList<>();
|
basicResolvers = new ArrayList<>();
|
||||||
regexResolvers = new ArrayList<>();
|
regexResolvers = new ArrayList<>();
|
||||||
}
|
}
|
||||||
@ -78,6 +87,12 @@ public class ResolverSvc implements ResolverService {
|
|||||||
for (Container container : regexResolvers) {
|
for (Container container : regexResolvers) {
|
||||||
if (container.matcher.test(target)) resolvers.add(container.resolver);
|
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;
|
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.config.ResourceSettings;
|
||||||
import com.djrapitops.plan.settings.locale.Locale;
|
import com.djrapitops.plan.settings.locale.Locale;
|
||||||
import com.djrapitops.plan.settings.locale.lang.PluginLang;
|
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.storage.file.Resource;
|
||||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||||
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
import com.djrapitops.plan.utilities.logging.ErrorContext;
|
||||||
@ -50,7 +50,7 @@ import java.util.function.Supplier;
|
|||||||
public class ResourceSvc implements ResourceService {
|
public class ResourceSvc implements ResourceService {
|
||||||
|
|
||||||
public final Set<Snippet> snippets;
|
public final Set<Snippet> snippets;
|
||||||
private final PlanFiles files;
|
private final PublicHtmlFiles publicHtmlFiles;
|
||||||
private final ResourceSettings resourceSettings;
|
private final ResourceSettings resourceSettings;
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
private final PluginLogger logger;
|
private final PluginLogger logger;
|
||||||
@ -58,13 +58,13 @@ public class ResourceSvc implements ResourceService {
|
|||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ResourceSvc(
|
public ResourceSvc(
|
||||||
PlanFiles files,
|
PublicHtmlFiles publicHtmlFiles,
|
||||||
PlanConfig config,
|
PlanConfig config,
|
||||||
Locale locale,
|
Locale locale,
|
||||||
PluginLogger logger,
|
PluginLogger logger,
|
||||||
ErrorLogger errorLogger
|
ErrorLogger errorLogger
|
||||||
) {
|
) {
|
||||||
this.files = files;
|
this.publicHtmlFiles = publicHtmlFiles;
|
||||||
this.resourceSettings = config.getResourceSettings();
|
this.resourceSettings = config.getResourceSettings();
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@ -155,7 +155,7 @@ public class ResourceSvc implements ResourceService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public WebResource getOrWriteCustomized(@Untrusted String fileName, Supplier<WebResource> source) throws IOException {
|
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()) {
|
if (customizedResource.isPresent()) {
|
||||||
return readCustomized(customizedResource.get());
|
return readCustomized(customizedResource.get());
|
||||||
} else {
|
} else {
|
||||||
|
@ -28,6 +28,7 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
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.
|
* 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
|
@Singleton
|
||||||
|
@Deprecated(forRemoval = true, since = "#2260") // TODO Remove after Frontend BETA
|
||||||
public class WebAssetVersionCheckTask extends TaskSystem.Task {
|
public class WebAssetVersionCheckTask extends TaskSystem.Task {
|
||||||
|
|
||||||
private final PlanConfig config;
|
private final PlanConfig config;
|
||||||
@ -110,7 +114,8 @@ public class WebAssetVersionCheckTask extends TaskSystem.Task {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Optional<AssetInfo> findOutdatedResource(String resource) {
|
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);
|
Optional<Long> webAssetVersion = assetVersions.getAssetVersion(resource);
|
||||||
if (resourceFile.isPresent() && webAssetVersion.isPresent() && webAssetVersion.get() > resourceFile.get().lastModified()) {
|
if (resourceFile.isPresent() && webAssetVersion.isPresent() && webAssetVersion.get() > resourceFile.get().lastModified()) {
|
||||||
return Optional.of(new AssetInfo(
|
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.exception.NotFoundException;
|
||||||
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
import com.djrapitops.plan.delivery.web.resolver.request.Request;
|
||||||
import com.djrapitops.plan.delivery.web.resource.WebResource;
|
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.Identifiers;
|
||||||
import com.djrapitops.plan.identification.ServerUUID;
|
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.Locale;
|
||||||
import com.djrapitops.plan.settings.locale.lang.ErrorPageLang;
|
import com.djrapitops.plan.settings.locale.lang.ErrorPageLang;
|
||||||
import com.djrapitops.plan.settings.theme.Theme;
|
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.Database;
|
||||||
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
|
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
|
||||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
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.dev.Untrusted;
|
||||||
import com.djrapitops.plan.utilities.java.Maps;
|
import com.djrapitops.plan.utilities.java.Maps;
|
||||||
import com.djrapitops.plan.utilities.java.UnaryChain;
|
import com.djrapitops.plan.utilities.java.UnaryChain;
|
||||||
@ -53,8 +55,6 @@ import javax.inject.Inject;
|
|||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.UncheckedIOException;
|
import java.io.UncheckedIOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
@ -70,6 +70,8 @@ public class ResponseFactory {
|
|||||||
private static final String STATIC_BUNDLE_FOLDER = "static";
|
private static final String STATIC_BUNDLE_FOLDER = "static";
|
||||||
|
|
||||||
private final PlanFiles files;
|
private final PlanFiles files;
|
||||||
|
private final PlanConfig config;
|
||||||
|
private final PublicHtmlFiles publicHtmlFiles;
|
||||||
private final PageFactory pageFactory;
|
private final PageFactory pageFactory;
|
||||||
private final Locale locale;
|
private final Locale locale;
|
||||||
private final DBSystem dbSystem;
|
private final DBSystem dbSystem;
|
||||||
@ -80,6 +82,7 @@ public class ResponseFactory {
|
|||||||
@Inject
|
@Inject
|
||||||
public ResponseFactory(
|
public ResponseFactory(
|
||||||
PlanFiles files,
|
PlanFiles files,
|
||||||
|
PlanConfig config, PublicHtmlFiles publicHtmlFiles,
|
||||||
PageFactory pageFactory,
|
PageFactory pageFactory,
|
||||||
Locale locale,
|
Locale locale,
|
||||||
DBSystem dbSystem,
|
DBSystem dbSystem,
|
||||||
@ -88,6 +91,8 @@ public class ResponseFactory {
|
|||||||
Lazy<Addresses> addresses
|
Lazy<Addresses> addresses
|
||||||
) {
|
) {
|
||||||
this.files = files;
|
this.files = files;
|
||||||
|
this.config = config;
|
||||||
|
this.publicHtmlFiles = publicHtmlFiles;
|
||||||
this.pageFactory = pageFactory;
|
this.pageFactory = pageFactory;
|
||||||
this.locale = locale;
|
this.locale = locale;
|
||||||
this.dbSystem = dbSystem;
|
this.dbSystem = dbSystem;
|
||||||
@ -97,11 +102,23 @@ public class ResponseFactory {
|
|||||||
httpLastModifiedFormatter = formatters.httpLastModifiedLong();
|
httpLastModifiedFormatter = formatters.httpLastModifiedLong();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws UncheckedIOException If reading the resource fails
|
||||||
|
*/
|
||||||
public WebResource getResource(@Untrusted String resourceName) {
|
public WebResource getResource(@Untrusted String resourceName) {
|
||||||
return ResourceService.getInstance().getResource("Plan", resourceName,
|
return ResourceService.getInstance().getResource("Plan", resourceName,
|
||||||
() -> files.getResourceFromJar("web/" + resourceName).asWebResource());
|
() -> 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() {
|
private static Response browserCachedNotChangedResponse() {
|
||||||
return Response.builder()
|
return Response.builder()
|
||||||
.setStatus(304)
|
.setStatus(304)
|
||||||
@ -168,7 +185,7 @@ public class ResponseFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Response getCachedOrNew(long modified, String fileName, Function<String, Response> newResponseFunction) {
|
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();
|
Optional<Long> lastModified = resource.getLastModified();
|
||||||
if (lastModified.isPresent() && modified == lastModified.get()) {
|
if (lastModified.isPresent() && modified == lastModified.get()) {
|
||||||
return browserCachedNotChangedResponse();
|
return browserCachedNotChangedResponse();
|
||||||
@ -217,7 +234,7 @@ public class ResponseFactory {
|
|||||||
|
|
||||||
public Response javaScriptResponse(@Untrusted String fileName) {
|
public Response javaScriptResponse(@Untrusted String fileName) {
|
||||||
try {
|
try {
|
||||||
WebResource resource = getResource(fileName);
|
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||||
String content = UnaryChain.of(resource.asString())
|
String content = UnaryChain.of(resource.asString())
|
||||||
.chain(this::replaceMainAddressPlaceholder)
|
.chain(this::replaceMainAddressPlaceholder)
|
||||||
.chain(theme::replaceThemeColors)
|
.chain(theme::replaceThemeColors)
|
||||||
@ -267,7 +284,7 @@ public class ResponseFactory {
|
|||||||
|
|
||||||
public Response cssResponse(@Untrusted String fileName) {
|
public Response cssResponse(@Untrusted String fileName) {
|
||||||
try {
|
try {
|
||||||
WebResource resource = getResource(fileName);
|
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||||
String content = UnaryChain.of(resource.asString())
|
String content = UnaryChain.of(resource.asString())
|
||||||
.chain(theme::replaceThemeColors)
|
.chain(theme::replaceThemeColors)
|
||||||
.chain(contents -> StringUtils.replace(contents, "/static", getBasePath() + "/static"))
|
.chain(contents -> StringUtils.replace(contents, "/static", getBasePath() + "/static"))
|
||||||
@ -297,7 +314,7 @@ public class ResponseFactory {
|
|||||||
|
|
||||||
public Response imageResponse(@Untrusted String fileName) {
|
public Response imageResponse(@Untrusted String fileName) {
|
||||||
try {
|
try {
|
||||||
WebResource resource = getResource(fileName);
|
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||||
ResponseBuilder responseBuilder = Response.builder()
|
ResponseBuilder responseBuilder = Response.builder()
|
||||||
.setMimeType(MimeType.IMAGE)
|
.setMimeType(MimeType.IMAGE)
|
||||||
.setContent(resource)
|
.setContent(resource)
|
||||||
@ -333,7 +350,7 @@ public class ResponseFactory {
|
|||||||
type = MimeType.FONT_BYTESTREAM;
|
type = MimeType.FONT_BYTESTREAM;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
WebResource resource = getResource(fileName);
|
WebResource resource = config.isTrue(PluginSettings.FRONTEND_BETA) ? getPublicOrJarResource(fileName) : getResource(fileName);
|
||||||
ResponseBuilder responseBuilder = Response.builder()
|
ResponseBuilder responseBuilder = Response.builder()
|
||||||
.setMimeType(type)
|
.setMimeType(type)
|
||||||
.setContent(resource);
|
.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) {
|
public Response redirectResponse(String location) {
|
||||||
return Response.builder().redirectTo(location).build();
|
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() {
|
public Response forbidden403() {
|
||||||
return forbidden403("Your user is not authorized to view this page.<br>"
|
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.");
|
+ "If you believe this is an error contact staff to change your access level.");
|
||||||
@ -485,23 +500,6 @@ public class ResponseFactory {
|
|||||||
.build();
|
.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) {
|
public Response badRequest(String errorMessage, String target) {
|
||||||
return Response.builder()
|
return Response.builder()
|
||||||
.setMimeType(MimeType.JSON)
|
.setMimeType(MimeType.JSON)
|
||||||
@ -528,7 +526,7 @@ public class ResponseFactory {
|
|||||||
try {
|
try {
|
||||||
return forPage(request, pageFactory.loginPage());
|
return forPage(request, pageFactory.loginPage());
|
||||||
} catch (IOException e) {
|
} 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 {
|
try {
|
||||||
return forPage(request, pageFactory.registerPage());
|
return forPage(request, pageFactory.registerPage());
|
||||||
} catch (IOException e) {
|
} 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 ResolverService resolverService;
|
||||||
private final ResponseFactory responseFactory;
|
private final ResponseFactory responseFactory;
|
||||||
private final Lazy<WebServer> webServer;
|
private final Lazy<WebServer> webServer;
|
||||||
|
private final PublicHtmlResolver publicHtmlResolver;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public ResponseResolver(
|
public ResponseResolver(
|
||||||
@ -100,6 +101,7 @@ public class ResponseResolver {
|
|||||||
RootPageResolver rootPageResolver,
|
RootPageResolver rootPageResolver,
|
||||||
RootJSONResolver rootJSONResolver,
|
RootJSONResolver rootJSONResolver,
|
||||||
StaticResourceResolver staticResourceResolver,
|
StaticResourceResolver staticResourceResolver,
|
||||||
|
PublicHtmlResolver publicHtmlResolver,
|
||||||
|
|
||||||
LoginPageResolver loginPageResolver,
|
LoginPageResolver loginPageResolver,
|
||||||
RegisterPageResolver registerPageResolver,
|
RegisterPageResolver registerPageResolver,
|
||||||
@ -123,6 +125,7 @@ public class ResponseResolver {
|
|||||||
this.rootPageResolver = rootPageResolver;
|
this.rootPageResolver = rootPageResolver;
|
||||||
this.rootJSONResolver = rootJSONResolver;
|
this.rootJSONResolver = rootJSONResolver;
|
||||||
this.staticResourceResolver = staticResourceResolver;
|
this.staticResourceResolver = staticResourceResolver;
|
||||||
|
this.publicHtmlResolver = publicHtmlResolver;
|
||||||
this.loginPageResolver = loginPageResolver;
|
this.loginPageResolver = loginPageResolver;
|
||||||
this.registerPageResolver = registerPageResolver;
|
this.registerPageResolver = registerPageResolver;
|
||||||
this.loginResolver = loginResolver;
|
this.loginResolver = loginResolver;
|
||||||
@ -157,6 +160,7 @@ public class ResponseResolver {
|
|||||||
|
|
||||||
resolverService.registerResolverForMatches(plugin, Pattern.compile("^/$"), rootPageResolver);
|
resolverService.registerResolverForMatches(plugin, Pattern.compile("^/$"), rootPageResolver);
|
||||||
resolverService.registerResolverForMatches(plugin, Pattern.compile(StaticResourceResolver.PATH_REGEX), staticResourceResolver);
|
resolverService.registerResolverForMatches(plugin, Pattern.compile(StaticResourceResolver.PATH_REGEX), staticResourceResolver);
|
||||||
|
resolverService.registerResolverForMatches(plugin, Pattern.compile(".*"), publicHtmlResolver);
|
||||||
|
|
||||||
resolverService.registerResolver(plugin, "/v1", rootJSONResolver.getResolver());
|
resolverService.registerResolver(plugin, "/v1", rootJSONResolver.getResolver());
|
||||||
resolverService.registerResolver(plugin, "/docs/swagger.json", swaggerJsonResolver);
|
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.CustomizedFileSettings;
|
||||||
import com.djrapitops.plan.settings.config.paths.PluginSettings;
|
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.storage.file.PlanFiles;
|
||||||
import com.djrapitops.plan.utilities.dev.Untrusted;
|
import com.djrapitops.plan.utilities.dev.Untrusted;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -72,9 +73,16 @@ public class ResourceSettings {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Path getCustomizationDirectory() {
|
public Path getCustomizationDirectory() {
|
||||||
Path exportDirectory = Paths.get(config.get(CustomizedFileSettings.PATH));
|
Path customizationDirectory = Paths.get(config.get(CustomizedFileSettings.PATH));
|
||||||
return exportDirectory.isAbsolute()
|
return customizationDirectory.isAbsolute()
|
||||||
? exportDirectory
|
? customizationDirectory
|
||||||
: files.getDataDirectory().resolve(exportDirectory);
|
: 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> 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<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> 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> 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");
|
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.SubSystem;
|
||||||
import com.djrapitops.plan.delivery.web.AssetVersions;
|
import com.djrapitops.plan.delivery.web.AssetVersions;
|
||||||
import com.djrapitops.plan.exceptions.EnableException;
|
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 com.djrapitops.plan.utilities.dev.Untrusted;
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -50,19 +48,16 @@ public class PlanFiles implements SubSystem {
|
|||||||
private final File configFile;
|
private final File configFile;
|
||||||
|
|
||||||
private final Lazy<AssetVersions> assetVersions;
|
private final Lazy<AssetVersions> assetVersions;
|
||||||
private final Lazy<PlanConfig> config;
|
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public PlanFiles(
|
public PlanFiles(
|
||||||
@Named("dataFolder") File dataFolder,
|
@Named("dataFolder") File dataFolder,
|
||||||
JarResource.StreamFunction getResourceStream,
|
JarResource.StreamFunction getResourceStream,
|
||||||
Lazy<AssetVersions> assetVersions,
|
Lazy<AssetVersions> assetVersions
|
||||||
Lazy<PlanConfig> config
|
|
||||||
) {
|
) {
|
||||||
this.dataFolder = dataFolder;
|
this.dataFolder = dataFolder;
|
||||||
this.getResourceStream = getResourceStream;
|
this.getResourceStream = getResourceStream;
|
||||||
this.assetVersions = assetVersions;
|
this.assetVersions = assetVersions;
|
||||||
this.config = config;
|
|
||||||
this.configFile = getFileFromPluginFolder("config.yml");
|
this.configFile = getFileFromPluginFolder("config.yml");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,28 +145,7 @@ public class PlanFiles implements SubSystem {
|
|||||||
return new FileResource(resourceName, getFileFromPluginFolder(resourceName));
|
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<File> attemptToFind(Path dir, @Untrusted String resourceName) {
|
||||||
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();
|
|
||||||
if (dir.toFile().exists() && dir.toFile().isDirectory()) {
|
if (dir.toFile().exists() && dir.toFile().isDirectory()) {
|
||||||
// Path may be absolute due to resolving untrusted path
|
// Path may be absolute due to resolving untrusted path
|
||||||
@Untrusted Path asPath = dir.resolve(resourceName);
|
@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();
|
||||||
|
}
|
||||||
|
}
|
@ -43,22 +43,13 @@ Webserver:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
# %port% is replaced automatically with Webserver.Port
|
# %port% is replaced automatically with Webserver.Port
|
||||||
Address: your.domain.here:%port%
|
Address: your.domain.here:%port%
|
||||||
# InternalIP usually does not need to be changed, only change it if you know what you're doing!
|
# 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.
|
# 0.0.0.0 allocates Internal (local) IP automatically for the WebServer.
|
||||||
Internal_IP: 0.0.0.0
|
Internal_IP: 0.0.0.0
|
||||||
Cache:
|
# Use absolute path ("C:\Example\Path", "/var/example/path") or relative ("public_html" -> {server}/plugins/Plan/public_html)
|
||||||
Reduced_refresh_barrier:
|
# NOTE: All files in this directory can be read by anyone who can access the webserver.
|
||||||
Time: 15
|
# This can be used to host certbot http challenge file, or for customizing Plan React-bundle
|
||||||
Unit: SECONDS
|
Public_html_directory: "public_html"
|
||||||
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
|
|
||||||
Security:
|
Security:
|
||||||
SSL_certificate:
|
SSL_certificate:
|
||||||
KeyStore_path: Cert.jks
|
KeyStore_path: Cert.jks
|
||||||
@ -88,6 +79,19 @@ Webserver:
|
|||||||
Unit: HOURS
|
Unit: HOURS
|
||||||
Disable_Webserver: false
|
Disable_Webserver: false
|
||||||
External_Webserver_address: "https://www.example.address"
|
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:
|
Data_gathering:
|
||||||
Geolocations: true
|
Geolocations: true
|
||||||
|
@ -45,22 +45,13 @@ Webserver:
|
|||||||
Enabled: false
|
Enabled: false
|
||||||
# %port% is replaced automatically with Webserver.Port
|
# %port% is replaced automatically with Webserver.Port
|
||||||
Address: your.domain.here:%port%
|
Address: your.domain.here:%port%
|
||||||
# InternalIP usually does not need to be changed, only change it if you know what you're doing!
|
# 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.
|
# 0.0.0.0 allocates Internal (local) IP automatically for the WebServer.
|
||||||
Internal_IP: 0.0.0.0
|
Internal_IP: 0.0.0.0
|
||||||
Cache:
|
# Use absolute path ("C:\Example\Path", "/var/example/path") or relative ("public_html" -> {server}/plugins/Plan/public_html)
|
||||||
Reduced_refresh_barrier:
|
# NOTE: All files in this directory can be read by anyone who can access the webserver.
|
||||||
Time: 15
|
# This can be used to host certbot http challenge file, or for customizing Plan React-bundle
|
||||||
Unit: SECONDS
|
Public_html_directory: "public_html"
|
||||||
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
|
|
||||||
Security:
|
Security:
|
||||||
SSL_certificate:
|
SSL_certificate:
|
||||||
KeyStore_path: Cert.jks
|
KeyStore_path: Cert.jks
|
||||||
@ -90,6 +81,19 @@ Webserver:
|
|||||||
Unit: HOURS
|
Unit: HOURS
|
||||||
Disable_Webserver: false
|
Disable_Webserver: false
|
||||||
External_Webserver_address: https://www.example.address
|
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:
|
Data_gathering:
|
||||||
Geolocations: true
|
Geolocations: true
|
||||||
|
@ -16,8 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.djrapitops.plan.storage.file;
|
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 extension.FullSystemExtension;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
@ -28,10 +26,8 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author AuroraLS3
|
* @author AuroraLS3
|
||||||
@ -49,17 +45,4 @@ class PlanFilesTest {
|
|||||||
File file = files.getFileFromPluginFolder(testFile.toFile().getAbsolutePath());
|
File file = files.getFileFromPluginFolder(testFile.toFile().getAbsolutePath());
|
||||||
assertNotEquals(testFile.toFile().getAbsolutePath(), file.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.PlanSystem;
|
||||||
import com.djrapitops.plan.commands.PlanCommand;
|
import com.djrapitops.plan.commands.PlanCommand;
|
||||||
|
import com.djrapitops.plan.delivery.DeliveryUtilities;
|
||||||
import com.djrapitops.plan.delivery.export.Exporter;
|
import com.djrapitops.plan.delivery.export.Exporter;
|
||||||
import com.djrapitops.plan.identification.ServerUUID;
|
import com.djrapitops.plan.identification.ServerUUID;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
import com.djrapitops.plan.settings.config.PlanConfig;
|
||||||
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
import com.djrapitops.plan.settings.config.paths.WebserverSettings;
|
||||||
import com.djrapitops.plan.storage.database.Database;
|
import com.djrapitops.plan.storage.database.Database;
|
||||||
import com.djrapitops.plan.storage.file.PlanFiles;
|
import com.djrapitops.plan.storage.file.PlanFiles;
|
||||||
|
import com.djrapitops.plan.storage.file.PublicHtmlFiles;
|
||||||
import org.junit.jupiter.api.extension.*;
|
import org.junit.jupiter.api.extension.*;
|
||||||
import utilities.RandomData;
|
import utilities.RandomData;
|
||||||
import utilities.dagger.PlanPluginComponent;
|
import utilities.dagger.PlanPluginComponent;
|
||||||
@ -86,6 +88,8 @@ public class FullSystemExtension implements ParameterResolver, BeforeAllCallback
|
|||||||
PlanPluginComponent.class.equals(type) ||
|
PlanPluginComponent.class.equals(type) ||
|
||||||
PlanCommand.class.equals(type) ||
|
PlanCommand.class.equals(type) ||
|
||||||
Database.class.equals(type) ||
|
Database.class.equals(type) ||
|
||||||
|
DeliveryUtilities.class.equals(type) ||
|
||||||
|
PublicHtmlFiles.class.equals(type) ||
|
||||||
Exporter.class.equals(type);
|
Exporter.class.equals(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,6 +125,12 @@ public class FullSystemExtension implements ParameterResolver, BeforeAllCallback
|
|||||||
if (Database.class.equals(type)) {
|
if (Database.class.equals(type)) {
|
||||||
return planSystem.getDatabaseSystem().getDatabase();
|
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)) {
|
if (Exporter.class.equals(type)) {
|
||||||
return planSystem.getExportSystem().getExporter();
|
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
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
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");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# you may not use this file except in compliance with 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
|
# 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"
|
# Resolve links: $0 may be a link
|
||||||
APP_BASE_NAME=`basename "$0"`
|
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.
|
# 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"'
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD="maximum"
|
MAX_FD=maximum
|
||||||
|
|
||||||
warn () {
|
warn () {
|
||||||
echo "$*"
|
echo "$*"
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
die () {
|
die () {
|
||||||
echo
|
echo
|
||||||
echo "$*"
|
echo "$*"
|
||||||
echo
|
echo
|
||||||
exit 1
|
exit 1
|
||||||
}
|
} >&2
|
||||||
|
|
||||||
# OS specific support (must be 'true' or 'false').
|
# OS specific support (must be 'true' or 'false').
|
||||||
cygwin=false
|
cygwin=false
|
||||||
msys=false
|
msys=false
|
||||||
darwin=false
|
darwin=false
|
||||||
nonstop=false
|
nonstop=false
|
||||||
case "`uname`" in
|
case "$( uname )" in #(
|
||||||
CYGWIN* )
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
cygwin=true
|
Darwin* ) darwin=true ;; #(
|
||||||
;;
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
Darwin* )
|
NONSTOP* ) nonstop=true ;;
|
||||||
darwin=true
|
|
||||||
;;
|
|
||||||
MSYS* | MINGW* )
|
|
||||||
msys=true
|
|
||||||
;;
|
|
||||||
NONSTOP* )
|
|
||||||
nonstop=true
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
|
|
||||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
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 [ -n "$JAVA_HOME" ] ; then
|
||||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
# IBM's JDK on AIX uses strange locations for the executables
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
else
|
else
|
||||||
JAVACMD="$JAVA_HOME/bin/java"
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
fi
|
fi
|
||||||
if [ ! -x "$JAVACMD" ] ; then
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
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."
|
location of your Java installation."
|
||||||
fi
|
fi
|
||||||
else
|
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.
|
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
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
@ -106,80 +140,105 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
MAX_FD_LIMIT=`ulimit -H -n`
|
case $MAX_FD in #(
|
||||||
if [ $? -eq 0 ] ; then
|
max*)
|
||||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
MAX_FD="$MAX_FD_LIMIT"
|
# shellcheck disable=SC3045
|
||||||
fi
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
ulimit -n $MAX_FD
|
warn "Could not query maximum file descriptor limit"
|
||||||
if [ $? -ne 0 ] ; then
|
esac
|
||||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
case $MAX_FD in #(
|
||||||
fi
|
'' | soft) :;; #(
|
||||||
else
|
*)
|
||||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
fi
|
# shellcheck disable=SC3045
|
||||||
fi
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
# 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" ;;
|
|
||||||
esac
|
esac
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Escape application args
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
save () {
|
# * args from the command line
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
# * the main class name
|
||||||
echo " "
|
# * -classpath
|
||||||
}
|
# * -D...appname settings
|
||||||
APP_ARGS=`save "$@"`
|
# * --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
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
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" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
|
15
Plan/gradlew.bat
vendored
15
Plan/gradlew.bat
vendored
@ -14,7 +14,7 @@
|
|||||||
@rem limitations under the License.
|
@rem limitations under the License.
|
||||||
@rem
|
@rem
|
||||||
|
|
||||||
@if "%DEBUG%" == "" @echo off
|
@if "%DEBUG%"=="" @echo off
|
||||||
@rem ##########################################################################
|
@rem ##########################################################################
|
||||||
@rem
|
@rem
|
||||||
@rem Gradle startup script for Windows
|
@rem Gradle startup script for Windows
|
||||||
@ -25,7 +25,8 @@
|
|||||||
if "%OS%"=="Windows_NT" setlocal
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
set DIRNAME=%~dp0
|
set DIRNAME=%~dp0
|
||||||
if "%DIRNAME%" == "" set DIRNAME=.
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
set APP_BASE_NAME=%~n0
|
set APP_BASE_NAME=%~n0
|
||||||
set APP_HOME=%DIRNAME%
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
|
|||||||
|
|
||||||
set JAVA_EXE=java.exe
|
set JAVA_EXE=java.exe
|
||||||
%JAVA_EXE% -version >NUL 2>&1
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
if "%ERRORLEVEL%" == "0" goto execute
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
echo.
|
echo.
|
||||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
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
|
:end
|
||||||
@rem End local scope for the variables with windows NT shell
|
@rem End local scope for the variables with windows NT shell
|
||||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
:fail
|
:fail
|
||||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
rem the _cmd.exe /c_ return code!
|
rem the _cmd.exe /c_ return code!
|
||||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
exit /b 1
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
:mainEnd
|
:mainEnd
|
||||||
if "%OS%"=="Windows_NT" endlocal
|
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.PlanPlugin;
|
||||||
import com.djrapitops.plan.PlanSponge;
|
import com.djrapitops.plan.PlanSponge;
|
||||||
import com.djrapitops.plan.delivery.web.AssetVersions;
|
import com.djrapitops.plan.delivery.web.AssetVersions;
|
||||||
import com.djrapitops.plan.settings.config.PlanConfig;
|
|
||||||
import dagger.Lazy;
|
import dagger.Lazy;
|
||||||
import org.spongepowered.api.Sponge;
|
import org.spongepowered.api.Sponge;
|
||||||
import org.spongepowered.api.resource.ResourcePath;
|
import org.spongepowered.api.resource.ResourcePath;
|
||||||
@ -46,10 +45,9 @@ public class SpongePlanFiles extends PlanFiles {
|
|||||||
@Named("dataFolder") File dataFolder,
|
@Named("dataFolder") File dataFolder,
|
||||||
JarResource.StreamFunction getResourceStream,
|
JarResource.StreamFunction getResourceStream,
|
||||||
PlanPlugin plugin,
|
PlanPlugin plugin,
|
||||||
Lazy<AssetVersions> assetVersions,
|
Lazy<AssetVersions> assetVersions
|
||||||
Lazy<PlanConfig> config
|
|
||||||
) {
|
) {
|
||||||
super(dataFolder, getResourceStream, assetVersions, config);
|
super(dataFolder, getResourceStream, assetVersions);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user