Moved "directory" data uploads to new POST /site/upload API.

Directory uploads don't make much sense outside of website hosting, so it's best to make this API specific to that purpose.
This commit is contained in:
CalDescent 2021-06-23 09:10:06 +01:00
parent f77ec1faf6
commit 39f5dce51c
2 changed files with 114 additions and 64 deletions

View File

@ -20,7 +20,6 @@ import org.qortal.settings.Settings;
import org.qortal.storage.DataFile; import org.qortal.storage.DataFile;
import org.qortal.storage.DataFile.ValidationResult; import org.qortal.storage.DataFile.ValidationResult;
import org.qortal.utils.Base58; import org.qortal.utils.Base58;
import org.qortal.utils.ZipUtils;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.*; import javax.ws.rs.*;
@ -71,7 +70,7 @@ public class DataResource {
} }
) )
@ApiErrors({ApiError.REPOSITORY_ISSUE}) @ApiErrors({ApiError.REPOSITORY_ISSUE})
public String uploadDataAtPath(String path) { public String uploadFileAtPath(String path) {
Security.checkApiCallAllowed(request); Security.checkApiCallAllowed(request);
// It's too dangerous to allow user-supplied filenames in weaker security contexts // It's too dangerous to allow user-supplied filenames in weaker security contexts
@ -82,15 +81,26 @@ public class DataResource {
// Check if a file or directory has been supplied // Check if a file or directory has been supplied
File file = new File(path); File file = new File(path);
if (file.isFile()) { if (!file.isFile()) {
return this.uploadFile(path); LOGGER.info("Not a file: {}", path);
} throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
else if (file.isDirectory()) {
return this.uploadDirectory(path);
} }
LOGGER.info("No file or folder found at path: {}", path); DataFile dataFile = new DataFile(path);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA); ValidationResult validationResult = dataFile.isValid();
if (validationResult != DataFile.ValidationResult.OK) {
LOGGER.error("Invalid file: {}", validationResult);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
LOGGER.info("Whole file digest: {}", dataFile.base58Digest());
int chunkCount = dataFile.split(DataFile.CHUNK_SIZE);
if (chunkCount > 0) {
LOGGER.info(String.format("Successfully split into %d chunk%s", chunkCount, (chunkCount == 1 ? "" : "s")));
return "true";
}
return "false";
} catch (DataException e) { } catch (DataException e) {
LOGGER.error("Repository issue when uploading data", e); LOGGER.error("Repository issue when uploading data", e);
@ -101,67 +111,6 @@ public class DataResource {
} }
} }
private String uploadFile(String filePath) {
DataFile dataFile = new DataFile(filePath);
ValidationResult validationResult = dataFile.isValid();
if (validationResult != DataFile.ValidationResult.OK) {
LOGGER.error("Invalid file: {}", validationResult);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
LOGGER.info("Whole file digest: {}", dataFile.base58Digest());
int chunkCount = dataFile.split(DataFile.CHUNK_SIZE);
if (chunkCount > 0) {
LOGGER.info(String.format("Successfully split into %d chunk%s", chunkCount, (chunkCount == 1 ? "" : "s")));
return "true";
}
return "false";
}
private String uploadDirectory(String directoryPath) {
// Ensure temp folder exists
try {
Files.createDirectories(Paths.get("temp"));
} catch (IOException e) {
LOGGER.error("Unable to create temp directory");
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
}
// Firstly zip up the directory
String outputFilePath = "temp/zipped.zip";
try {
ZipUtils.zip(directoryPath, outputFilePath, "data");
} catch (IOException e) {
LOGGER.info("Unable to zip directory", e);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
try {
DataFile dataFile = new DataFile(outputFilePath);
ValidationResult validationResult = dataFile.isValid();
if (validationResult != DataFile.ValidationResult.OK) {
LOGGER.error("Invalid file: {}", validationResult);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
LOGGER.info("Whole file digest: {}", dataFile.base58Digest());
int chunkCount = dataFile.split(DataFile.CHUNK_SIZE);
if (chunkCount > 0) {
LOGGER.info(String.format("Successfully split into %d chunk%s", chunkCount, (chunkCount == 1 ? "" : "s")));
return "true";
}
return "false";
}
finally {
// Clean up by deleting the zipped file
File zippedFile = new File(outputFilePath);
if (zippedFile.exists()) {
zippedFile.delete();
}
}
}
@DELETE @DELETE
@Path("/file") @Path("/file")

View File

@ -1,8 +1,12 @@
package org.qortal.api.resource; package org.qortal.api.resource;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -11,21 +15,118 @@ import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.parameters.RequestBody;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.jsoup.Jsoup; import org.jsoup.Jsoup;
import org.jsoup.nodes.Document; import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.jsoup.select.Elements; import org.jsoup.select.Elements;
import org.qortal.api.ApiError;
import org.qortal.api.ApiExceptionFactory;
import org.qortal.api.Security;
import org.qortal.settings.Settings;
import org.qortal.storage.DataFile; import org.qortal.storage.DataFile;
import org.qortal.utils.ZipUtils; import org.qortal.utils.ZipUtils;
@Path("/site") @Path("/site")
@Tag(name = "Website")
public class WebsiteResource { public class WebsiteResource {
private static final Logger LOGGER = LogManager.getLogger(WebsiteResource.class); private static final Logger LOGGER = LogManager.getLogger(WebsiteResource.class);
@Context
HttpServletRequest request;
@POST
@Path("/upload")
@Operation(
summary = "Build raw, unsigned, UPLOAD_DATA transaction, based on a user-supplied path to a static website",
requestBody = @RequestBody(
required = true,
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "string", example = "/Users/user/Documents/MyStaticWebsite"
)
)
),
responses = {
@ApiResponse(
description = "raw, unsigned, UPLOAD_DATA transaction encoded in Base58",
content = @Content(
mediaType = MediaType.TEXT_PLAIN,
schema = @Schema(
type = "string"
)
)
)
}
)
public String hostWebsite(String directoryPath) {
Security.checkApiCallAllowed(request);
// It's too dangerous to allow user-supplied filenames in weaker security contexts
if (Settings.getInstance().isApiRestricted()) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
}
// Check if a file or directory has been supplied
File file = new File(directoryPath);
if (!file.isDirectory()) {
LOGGER.info("Not a directory: {}", directoryPath);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
}
// Ensure temp folder exists
try {
Files.createDirectories(Paths.get("temp"));
} catch (IOException e) {
LOGGER.error("Unable to create temp directory");
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
}
// Firstly zip up the directory
String outputFilePath = "temp/zipped.zip";
try {
ZipUtils.zip(directoryPath, outputFilePath, "data");
} catch (IOException e) {
LOGGER.info("Unable to zip directory", e);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
try {
DataFile dataFile = new DataFile(outputFilePath);
DataFile.ValidationResult validationResult = dataFile.isValid();
if (validationResult != DataFile.ValidationResult.OK) {
LOGGER.error("Invalid file: {}", validationResult);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
LOGGER.info("Whole file digest: {}", dataFile.base58Digest());
int chunkCount = dataFile.split(DataFile.CHUNK_SIZE);
if (chunkCount > 0) {
LOGGER.info(String.format("Successfully split into %d chunk%s", chunkCount, (chunkCount == 1 ? "" : "s")));
return "true";
}
return "false";
}
finally {
// Clean up by deleting the zipped file
File zippedFile = new File(outputFilePath);
if (zippedFile.exists()) {
zippedFile.delete();
}
}
}
@GET @GET
@Path("{resource}") @Path("{resource}")
public Response getResourceIndex(@PathParam("resource") String resourceId) { public Response getResourceIndex(@PathParam("resource") String resourceId) {