diff --git a/src/main/java/org/qortal/api/domainmap/resource/DomainMapResource.java b/src/main/java/org/qortal/api/domainmap/resource/DomainMapResource.java index 31d216dc..4cb9f8e5 100644 --- a/src/main/java/org/qortal/api/domainmap/resource/DomainMapResource.java +++ b/src/main/java/org/qortal/api/domainmap/resource/DomainMapResource.java @@ -42,15 +42,15 @@ public class DomainMapResource { // Build synchronously, so that we don't need to make the summary API endpoints available over // the domain map server. This means that there will be no loading screen, but this is potentially // preferred in this situation anyway (e.g. to avoid confusing search engine robots). - return this.get(domainMap.get(request.getServerName()), ResourceIdType.NAME, Service.WEBSITE, inPath, null, "", false, false); + return this.get(domainMap.get(request.getServerName()), ResourceIdType.NAME, Service.WEBSITE, null, inPath, null, "", false, false); } return ArbitraryDataRenderer.getResponse(response, 404, "Error 404: File Not Found"); } - private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String inPath, - String secret58, String prefix, boolean usePrefix, boolean async) { + private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier, + String inPath, String secret58, String prefix, boolean usePrefix, boolean async) { - ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, inPath, + ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath, secret58, prefix, usePrefix, async, "domainMap", request, response, context); return renderer.render(); } diff --git a/src/main/java/org/qortal/api/gateway/resource/GatewayResource.java b/src/main/java/org/qortal/api/gateway/resource/GatewayResource.java index 07e1cfb4..354631c0 100644 --- a/src/main/java/org/qortal/api/gateway/resource/GatewayResource.java +++ b/src/main/java/org/qortal/api/gateway/resource/GatewayResource.java @@ -82,7 +82,7 @@ public class GatewayResource { @PathParam("path") String inPath) { // Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data Security.disallowLoopbackRequests(request); - return this.get(name, ResourceIdType.NAME, Service.WEBSITE, inPath, null, "", true, true); + return this.get(name, ResourceIdType.NAME, Service.WEBSITE, null, inPath, null, "", true, true); } @GET @@ -91,7 +91,7 @@ public class GatewayResource { public HttpServletResponse getIndexByName(@PathParam("name") String name) { // Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data Security.disallowLoopbackRequests(request); - return this.get(name, ResourceIdType.NAME, Service.WEBSITE, "/", null, "", true, true); + return this.get(name, ResourceIdType.NAME, Service.WEBSITE, null, "/", null, "", true, true); } @@ -103,7 +103,7 @@ public class GatewayResource { @PathParam("path") String inPath) { // Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data Security.disallowLoopbackRequests(request); - return this.get(name, ResourceIdType.NAME, Service.WEBSITE, inPath, null, "/site", true, true); + return this.get(name, ResourceIdType.NAME, Service.WEBSITE, null, inPath, null, "/site", true, true); } @GET @@ -111,14 +111,14 @@ public class GatewayResource { public HttpServletResponse getSiteIndexByName(@PathParam("name") String name) { // Block requests from localhost, to prevent websites/apps from running javascript that fetches unvetted data Security.disallowLoopbackRequests(request); - return this.get(name, ResourceIdType.NAME, Service.WEBSITE, "/", null, "/site", true, true); + return this.get(name, ResourceIdType.NAME, Service.WEBSITE, null, "/", null, "/site", true, true); } - private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String inPath, - String secret58, String prefix, boolean usePrefix, boolean async) { + private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier, + String inPath, String secret58, String prefix, boolean usePrefix, boolean async) { - ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, inPath, + ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath, secret58, prefix, usePrefix, async, "gateway", request, response, context); return renderer.render(); } diff --git a/src/main/java/org/qortal/api/restricted/resource/RenderResource.java b/src/main/java/org/qortal/api/restricted/resource/RenderResource.java index 519e02ab..60ec23d5 100644 --- a/src/main/java/org/qortal/api/restricted/resource/RenderResource.java +++ b/src/main/java/org/qortal/api/restricted/resource/RenderResource.java @@ -146,7 +146,7 @@ public class RenderResource { if (!Settings.getInstance().isQDNAuthBypassEnabled()) Security.requirePriorAuthorization(request, signature, Service.WEBSITE, null); - return this.get(signature, ResourceIdType.SIGNATURE, null, "/", null, "/render/signature", true, true, theme); + return this.get(signature, ResourceIdType.SIGNATURE, null, null, "/", null, "/render/signature", true, true, theme); } @GET @@ -157,7 +157,7 @@ public class RenderResource { if (!Settings.getInstance().isQDNAuthBypassEnabled()) Security.requirePriorAuthorization(request, signature, Service.WEBSITE, null); - return this.get(signature, ResourceIdType.SIGNATURE, null, inPath,null, "/render/signature", true, true, theme); + return this.get(signature, ResourceIdType.SIGNATURE, null, null, inPath,null, "/render/signature", true, true, theme); } @GET @@ -168,7 +168,7 @@ public class RenderResource { if (!Settings.getInstance().isQDNAuthBypassEnabled()) Security.requirePriorAuthorization(request, hash58, Service.WEBSITE, null); - return this.get(hash58, ResourceIdType.FILE_HASH, Service.WEBSITE, "/", secret58, "/render/hash", true, false, theme); + return this.get(hash58, ResourceIdType.FILE_HASH, Service.WEBSITE, null, "/", secret58, "/render/hash", true, false, theme); } @GET @@ -180,7 +180,7 @@ public class RenderResource { if (!Settings.getInstance().isQDNAuthBypassEnabled()) Security.requirePriorAuthorization(request, hash58, Service.WEBSITE, null); - return this.get(hash58, ResourceIdType.FILE_HASH, Service.WEBSITE, inPath, secret58, "/render/hash", true, false, theme); + return this.get(hash58, ResourceIdType.FILE_HASH, Service.WEBSITE, null, inPath, secret58, "/render/hash", true, false, theme); } @GET @@ -189,12 +189,13 @@ public class RenderResource { public HttpServletResponse getPathByName(@PathParam("service") Service service, @PathParam("name") String name, @PathParam("path") String inPath, + @QueryParam("identifier") String identifier, @QueryParam("theme") String theme) { if (!Settings.getInstance().isQDNAuthBypassEnabled()) Security.requirePriorAuthorization(request, name, service, null); String prefix = String.format("/render/%s", service); - return this.get(name, ResourceIdType.NAME, service, inPath, null, prefix, true, true, theme); + return this.get(name, ResourceIdType.NAME, service, identifier, inPath, null, prefix, true, true, theme); } @GET @@ -207,15 +208,15 @@ public class RenderResource { Security.requirePriorAuthorization(request, name, service, null); String prefix = String.format("/render/%s", service); - return this.get(name, ResourceIdType.NAME, service, "/", null, prefix, true, true, theme); + return this.get(name, ResourceIdType.NAME, service, null, "/", null, prefix, true, true, theme); } - private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String inPath, - String secret58, String prefix, boolean usePrefix, boolean async, String theme) { + private HttpServletResponse get(String resourceId, ResourceIdType resourceIdType, Service service, String identifier, + String inPath, String secret58, String prefix, boolean usePrefix, boolean async, String theme) { - ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, inPath, + ArbitraryDataRenderer renderer = new ArbitraryDataRenderer(resourceId, resourceIdType, service, identifier, inPath, secret58, prefix, usePrefix, async, "render", request, response, context); if (theme != null) { diff --git a/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java b/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java index 4b804f51..2df13b8c 100644 --- a/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java +++ b/src/main/java/org/qortal/arbitrary/ArbitraryDataRenderer.java @@ -2,6 +2,7 @@ package org.qortal.arbitrary; import com.google.common.io.Resources; import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.ArrayUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.api.HTMLParser; @@ -34,6 +35,7 @@ public class ArbitraryDataRenderer { private final String resourceId; private final ResourceIdType resourceIdType; private final Service service; + private final String identifier; private String theme = "light"; private String inPath; private final String secret58; @@ -45,13 +47,14 @@ public class ArbitraryDataRenderer { private final HttpServletResponse response; private final ServletContext context; - public ArbitraryDataRenderer(String resourceId, ResourceIdType resourceIdType, Service service, String inPath, - String secret58, String prefix, boolean usePrefix, boolean async, String qdnContext, + public ArbitraryDataRenderer(String resourceId, ResourceIdType resourceIdType, Service service, String identifier, + String inPath, String secret58, String prefix, boolean usePrefix, boolean async, String qdnContext, HttpServletRequest request, HttpServletResponse response, ServletContext context) { this.resourceId = resourceId; this.resourceIdType = resourceIdType; this.service = service; + this.identifier = identifier != null ? identifier : "default"; this.inPath = inPath; this.secret58 = secret58; this.prefix = prefix; @@ -73,14 +76,14 @@ public class ArbitraryDataRenderer { return ArbitraryDataRenderer.getResponse(response, 500, "QDN is disabled in settings"); } - ArbitraryDataReader arbitraryDataReader = new ArbitraryDataReader(resourceId, resourceIdType, service, null); + ArbitraryDataReader arbitraryDataReader = new ArbitraryDataReader(resourceId, resourceIdType, service, identifier); arbitraryDataReader.setSecret58(secret58); // Optional, used for loading encrypted file hashes only try { if (!arbitraryDataReader.isCachedDataAvailable()) { // If async is requested, show a loading screen whilst build is in progress if (async) { arbitraryDataReader.loadAsynchronously(false, 10); - return this.getLoadingResponse(service, resourceId, theme); + return this.getLoadingResponse(service, resourceId, identifier, theme); } // Otherwise, loop until we have data @@ -113,6 +116,12 @@ public class ArbitraryDataRenderer { } String unzippedPath = path.toString(); + String[] files = ArrayUtils.removeElement(new File(unzippedPath).list(), ".qortal"); + if (files.length == 1) { + // This is a single file resource + inPath = files[0]; + } + try { String filename = this.getFilename(unzippedPath, inPath); String filePath = Paths.get(unzippedPath, filename).toString(); @@ -174,7 +183,7 @@ public class ArbitraryDataRenderer { return userPath; } - private HttpServletResponse getLoadingResponse(Service service, String name, String theme) { + private HttpServletResponse getLoadingResponse(Service service, String name, String identifier, String theme) { String responseString = ""; URL url = Resources.getResource("loading/index.html"); try { @@ -183,6 +192,7 @@ public class ArbitraryDataRenderer { // Replace vars responseString = responseString.replace("%%SERVICE%%", service.toString()); responseString = responseString.replace("%%NAME%%", name); + responseString = responseString.replace("%%IDENTIFIER%%", identifier); responseString = responseString.replace("%%THEME%%", theme); } catch (IOException e) { diff --git a/src/main/resources/loading/index.html b/src/main/resources/loading/index.html index a828e04e..8e992049 100644 --- a/src/main/resources/loading/index.html +++ b/src/main/resources/loading/index.html @@ -43,8 +43,9 @@ var host = location.protocol + '//' + location.host; var service = "%%SERVICE%%" var name = "%%NAME%%" + var identifier = "%%IDENTIFIER%%" - var url = host + '/arbitrary/resource/status/' + service + '/' + name + '?build=true'; + var url = host + '/arbitrary/resource/status/' + service + '/' + name + '/' + identifier + '?build=true'; var textStatus = "Loading..."; var textProgress = ""; var retryInterval = 2500; diff --git a/src/main/resources/q-apps/q-apps.js b/src/main/resources/q-apps/q-apps.js index e5fb3fea..5b6e9c15 100644 --- a/src/main/resources/q-apps/q-apps.js +++ b/src/main/resources/q-apps/q-apps.js @@ -81,6 +81,8 @@ window.addEventListener("message", (event) => { url = "/" + data.name; } if (data.path != null) url = url.concat((data.path.startsWith("/") ? "" : "/") + data.path); + if (data.identifier != null) url = url.concat("?identifier=" + data.identifier); + window.location = url; response = true; break; @@ -228,11 +230,26 @@ function interceptClickEvent(e) { let parts = href.split("/"); const service = parts[0].toUpperCase(); parts.shift(); const name = parts[0]; parts.shift(); + let identifier; + + if (parts.length > 0) { + identifier = parts[0]; // Do not shift yet + // Check if a resource exists with this service, name and identifier combination + const url = "/arbitrary/resource/status/" + service + "/" + name + "/" + identifier; + const response = httpGet(url); + const responseObj = JSON.parse(response); + if (responseObj.totalChunkCount > 0) { + // Identifier exists, so don't include it in the path + parts.shift(); + } + } + const path = parts.join("/"); qortalRequest({ action: "LINK_TO_QDN_RESOURCE", service: service, name: name, + identifier: identifier, path: path }); }