Started abstracting the file processing code away from the API handlers, and making it more modular.

This commit is contained in:
CalDescent 2021-08-12 20:53:42 +01:00
parent e259a09b89
commit c790ea07dd
5 changed files with 466 additions and 201 deletions

View File

@ -9,9 +9,10 @@ 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 java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
@ -29,6 +30,7 @@ import org.qortal.block.BlockChain;
import org.qortal.crypto.Crypto;
import org.qortal.data.PaymentData;
import org.qortal.data.transaction.ArbitraryTransactionData;
import org.qortal.data.transaction.ArbitraryTransactionData.*;
import org.qortal.data.transaction.BaseTransactionData;
import org.qortal.data.transaction.TransactionData;
import org.qortal.data.transaction.ArbitraryTransactionData.DataType;
@ -45,6 +47,7 @@ import org.qortal.repository.RepositoryManager;
import org.qortal.settings.Settings;
import org.qortal.storage.DataFile;
import org.qortal.storage.DataFileChunk;
import org.qortal.storage.DataFileWriter;
import org.qortal.transaction.ArbitraryTransaction;
import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.TransactionType;
@ -265,18 +268,20 @@ public class ArbitraryResource {
String name = null;
byte[] secret = null;
ArbitraryTransactionData.Method method = ArbitraryTransactionData.Method.PUT;
ArbitraryTransactionData.Service service = ArbitraryTransactionData.Service.ARBITRARY_DATA;
ArbitraryTransactionData.Compression compression = ArbitraryTransactionData.Compression.NONE;
Method method = Method.PUT;
Service service = Service.ARBITRARY_DATA;
Compression compression = Compression.NONE;
// Check if a file or directory has been supplied
File file = new File(path);
if (!file.isFile()) {
LOGGER.info("Not a file: {}", path);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_CRITERIA);
DataFileWriter dataFileWriter = new DataFileWriter(Paths.get(path), method, compression);
try {
dataFileWriter.save();
} catch (IOException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
} catch (IllegalStateException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
DataFile dataFile = DataFile.fromPath(path);
DataFile dataFile = dataFileWriter.getDataFile();
if (dataFile == null) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}

View File

@ -1,10 +1,5 @@
package org.qortal.api.resource;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -13,12 +8,9 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@ -36,10 +28,10 @@ import org.qortal.api.ApiExceptionFactory;
import org.qortal.api.HTMLParser;
import org.qortal.api.Security;
import org.qortal.block.BlockChain;
import org.qortal.crypto.AES;
import org.qortal.crypto.Crypto;
import org.qortal.data.PaymentData;
import org.qortal.data.transaction.ArbitraryTransactionData;
import org.qortal.data.transaction.ArbitraryTransactionData.*;
import org.qortal.data.transaction.BaseTransactionData;
import org.qortal.group.Group;
import org.qortal.repository.DataException;
@ -47,14 +39,15 @@ import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.settings.Settings;
import org.qortal.storage.DataFile;
import org.qortal.storage.DataFile.*;
import org.qortal.storage.DataFileReader;
import org.qortal.storage.DataFileWriter;
import org.qortal.transaction.ArbitraryTransaction;
import org.qortal.transaction.Transaction;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.ArbitraryTransactionTransformer;
import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
import org.qortal.utils.ZipUtils;
@Path("/site")
@ -63,11 +56,6 @@ public class WebsiteResource {
private static final Logger LOGGER = LogManager.getLogger(WebsiteResource.class);
public enum ResourceIdType {
SIGNATURE,
FILE_HASH
};
@Context HttpServletRequest request;
@Context HttpServletResponse response;
@Context ServletContext context;
@ -115,7 +103,16 @@ public class WebsiteResource {
ArbitraryTransactionData.Service service = ArbitraryTransactionData.Service.WEBSITE;
ArbitraryTransactionData.Compression compression = ArbitraryTransactionData.Compression.ZIP;
DataFile dataFile = this.hostWebsite(path);
DataFileWriter dataFileWriter = new DataFileWriter(Paths.get(path), method, compression);
try {
dataFileWriter.save();
} catch (IOException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
} catch (IllegalStateException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
DataFile dataFile = dataFileWriter.getDataFile();
if (dataFile == null) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
@ -200,7 +197,19 @@ public class WebsiteResource {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.NON_PRODUCTION);
}
DataFile dataFile = this.hostWebsite(directoryPath);
Method method = Method.PUT;
Compression compression = Compression.ZIP;
DataFileWriter dataFileWriter = new DataFileWriter(Paths.get(directoryPath), method, compression);
try {
dataFileWriter.save();
} catch (IOException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
} catch (IllegalStateException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
DataFile dataFile = dataFileWriter.getDataFile();
if (dataFile != null) {
String digest58 = dataFile.digest58();
if (digest58 != null) {
@ -210,77 +219,6 @@ public class WebsiteResource {
return "Unable to generate preview URL";
}
private DataFile hostWebsite(String directoryPath) {
// 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
java.nio.file.Path tempDir = null;
try {
tempDir = Files.createTempDirectory("qortal-zip");
} catch (IOException e) {
LOGGER.error("Unable to create temp directory");
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.REPOSITORY_ISSUE);
}
// Firstly zip up the directory
String zipOutputFilePath = tempDir.toString() + File.separator + "zipped.zip";
try {
ZipUtils.zip(directoryPath, zipOutputFilePath, "data");
} catch (IOException e) {
LOGGER.info("Unable to zip directory", e);
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.INVALID_DATA);
}
// Next, encrypt the file with AES
String encryptedFilePath = tempDir.toString() + File.separator + "zipped_encrypted.zip";
SecretKey aesKey;
try {
aesKey = AES.generateKey(256);
AES.encryptFile("AES", aesKey, zipOutputFilePath, encryptedFilePath);
Files.delete(Paths.get(zipOutputFilePath));
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException
| BadPaddingException | IllegalBlockSizeException | IOException | InvalidKeyException e) {
throw ApiExceptionFactory.INSTANCE.createException(request, ApiError.TRANSFORMATION_ERROR);
}
try {
DataFile dataFile = DataFile.fromPath(encryptedFilePath);
dataFile.setSecret(aesKey.getEncoded());
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.digest58());
int chunkCount = dataFile.split(DataFile.CHUNK_SIZE);
if (chunkCount > 0) {
LOGGER.info(String.format("Successfully split into %d chunk%s:", chunkCount, (chunkCount == 1 ? "" : "s")));
LOGGER.info("{}", dataFile.printChunks());
return dataFile;
}
return null;
}
finally {
// Clean up
File zippedFile = new File(zipOutputFilePath);
if (zippedFile.exists()) {
zippedFile.delete();
}
File encryptedFile = new File(encryptedFilePath);
if (encryptedFile.exists()) {
encryptedFile.delete();
}
}
}
@GET
@Path("{signature}")
public HttpServletResponse getIndexBySignature(@PathParam("signature") String signature) {
@ -331,94 +269,21 @@ public class WebsiteResource {
inPath = File.separator + inPath;
}
String tempDirectory = System.getProperty("java.io.tmpdir");
String destPath = tempDirectory + File.separator + "qortal-sites" + File.separator + resourceId;
String unencryptedPath = destPath + File.separator + "zipped.zip";
String unzippedPath = destPath + File.separator + "data";
if (!Files.exists(Paths.get(unzippedPath))) {
// Load the full transaction data so we can access the file hashes
try (final Repository repository = RepositoryManager.getRepository()) {
DataFile dataFile = null;
byte[] digest = null;
byte[] secret = null;
if (resourceIdType == ResourceIdType.SIGNATURE) {
ArbitraryTransactionData transactionData = (ArbitraryTransactionData) repository.getTransactionRepository().fromSignature(Base58.decode(resourceId));
if (!(transactionData instanceof ArbitraryTransactionData)) {
return this.get404Response();
}
// Load hashes
digest = transactionData.getData();
byte[] chunkHashes = transactionData.getChunkHashes();
// Load secret
secret = transactionData.getSecret();
// Load data file(s)
dataFile = DataFile.fromHash(digest);
if (!dataFile.exists()) {
if (!dataFile.allChunksExist(chunkHashes)) {
// TODO: fetch them?
return this.get404Response();
}
// We have all the chunks but not the complete file, so join them
dataFile.addChunkHashes(chunkHashes);
dataFile.join();
}
}
else if (resourceIdType == ResourceIdType.FILE_HASH) {
dataFile = DataFile.fromHash58(resourceId);
digest = Base58.decode(resourceId);
secret = secret58 != null ? Base58.decode(secret58) : null;
}
// If the complete file still doesn't exist then something went wrong
if (!dataFile.exists()) {
return this.get404Response();
}
if (!Arrays.equals(dataFile.digest(), digest)) {
LOGGER.info("Unable to validate complete file hash");
return this.get404Response();
}
// Decrypt if we have the secret key.
if (secret != null && secret.length == Transformer.AES256_LENGTH) {
try {
SecretKey aesKey = new SecretKeySpec(secret, 0, secret.length, "AES");
AES.decryptFile("AES", aesKey, dataFile.getFilePath(), unencryptedPath);
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException
| BadPaddingException | IllegalBlockSizeException | IOException | InvalidKeyException e) {
return this.get404Response();
}
}
else {
// Assume it is unencrypted. We may block this.
unencryptedPath = dataFile.getFilePath();
}
// Unzip
try {
// TODO: compression types
//if (transactionData.getCompression() == ArbitraryTransactionData.Compression.ZIP) {
ZipUtils.unzip(unencryptedPath, destPath);
//}
} catch (IOException e) {
LOGGER.info("Unable to unzip file");
}
} catch (DataException e) {
return this.get500Response();
}
DataFileReader dataFileReader = new DataFileReader(resourceId, resourceIdType);
dataFileReader.setSecret58(secret58); // Optional, used for loading encrypted file hashes only
try {
dataFileReader.load(false);
} catch (Exception e) {
return this.get404Response();
}
java.nio.file.Path path = dataFileReader.getFilePath();
if (path == null) {
return this.get404Response();
}
String unzippedPath = path.toString();
try {
String filename = this.getFilename(unzippedPath, inPath);
String filename = this.getFilename(unzippedPath.toString(), inPath);
String filePath = unzippedPath + File.separator + filename;
if (HTMLParser.isHtmlFile(filename)) {
@ -445,7 +310,7 @@ public class WebsiteResource {
inputStream.close();
}
return response;
} catch (FileNotFoundException e) {
} catch (FileNotFoundException | NoSuchFileException e) {
LOGGER.info("File not found at path: {}", unzippedPath);
if (inPath.equals("/")) {
// Delete the unzipped folder if no index file was found
@ -455,7 +320,6 @@ public class WebsiteResource {
LOGGER.info("Unable to delete directory: {}", unzippedPath, e);
}
}
} catch (IOException e) {
LOGGER.info("Unable to serve file at path: {}", inPath, e);
}
@ -490,19 +354,6 @@ public class WebsiteResource {
return response;
}
private HttpServletResponse get500Response() {
try {
String responseString = "500: Internal Server Error";
byte[] responseData = responseString.getBytes();
response.setStatus(500);
response.setContentLength(responseData.length);
response.getOutputStream().write(responseData);
} catch (IOException e) {
LOGGER.info("Error writing 500 response");
}
return response;
}
private List<String> indexFiles() {
List<String> indexFiles = new ArrayList<>();
indexFiles.add("index.html");

View File

@ -41,6 +41,12 @@ public class DataFile {
}
}
// Resource ID types
public enum ResourceIdType {
SIGNATURE,
FILE_HASH
};
private static final Logger LOGGER = LogManager.getLogger(DataFile.class);
public static final long MAX_FILE_SIZE = 500 * 1024 * 1024; // 500MiB
@ -69,7 +75,7 @@ public class DataFile {
}
this.hash58 = Base58.encode(Crypto.digest(fileContent));
LOGGER.debug(String.format("File digest: %s, size: %d bytes", this.hash58, fileContent.length));
LOGGER.trace(String.format("File digest: %s, size: %d bytes", this.hash58, fileContent.length));
String outputFilePath = getOutputFilePath(this.hash58, true);
File outputFile = new File(outputFilePath);

View File

@ -0,0 +1,213 @@
package org.qortal.storage;
import org.qortal.crypto.AES;
import org.qortal.data.transaction.ArbitraryTransactionData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.repository.RepositoryManager;
import org.qortal.storage.DataFile.*;
import org.qortal.transform.Transformer;
import org.qortal.utils.Base58;
import org.qortal.utils.ZipUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class DataFileReader {
private String resourceId;
private ResourceIdType resourceIdType;
private String secret58;
private Path filePath;
private DataFile dataFile;
// Intermediate paths
private Path workingPath;
private Path uncompressedPath;
private Path unencryptedPath;
public DataFileReader(String resourceId, ResourceIdType resourceIdType) {
this.resourceId = resourceId;
this.resourceIdType = resourceIdType;
}
public void load(boolean overwrite) throws IllegalStateException, IOException, DataException {
try {
this.preExecute();
// Do nothing if files already exist and overwrite is set to false
if (Files.exists(this.uncompressedPath) && !overwrite) {
this.filePath = this.uncompressedPath;
return;
}
this.fetch();
this.decrypt();
this.uncompress();
} finally {
this.postExecute();
}
}
private void preExecute() {
this.createWorkingDirectory();
// Initialize unzipped path as it's used in a few places
this.uncompressedPath = Paths.get(this.workingPath.toString() + File.separator + "data");
}
private void postExecute() throws IOException {
this.cleanupFilesystem();
}
private void createWorkingDirectory() {
// Use the system tmpdir as our base, as it is deterministic
String baseDir = System.getProperty("java.io.tmpdir");
Path tempDir = Paths.get(baseDir + File.separator + "qortal" + File.separator + this.resourceId);
try {
Files.createDirectories(tempDir);
} catch (IOException e) {
throw new IllegalStateException("Unable to create temp directory");
}
this.workingPath = tempDir;
}
private void fetch() throws IllegalStateException, IOException, DataException {
switch (resourceIdType) {
case SIGNATURE:
this.fetchFromSignature();
break;
case FILE_HASH:
this.fetchFromFileHash();
break;
default:
throw new IllegalStateException(String.format("Unknown resource ID type specified: %s", resourceIdType.toString()));
}
}
private void fetchFromSignature() throws IllegalStateException, IOException, DataException {
// Load the full transaction data so we can access the file hashes
ArbitraryTransactionData transactionData;
try (final Repository repository = RepositoryManager.getRepository()) {
transactionData = (ArbitraryTransactionData) repository.getTransactionRepository().fromSignature(Base58.decode(resourceId));
}
if (!(transactionData instanceof ArbitraryTransactionData)) {
throw new IllegalStateException(String.format("Transaction data not found for signature %s", this.resourceId));
}
// Load hashes
byte[] digest = transactionData.getData();
byte[] chunkHashes = transactionData.getChunkHashes();
// Load secret
byte[] secret = transactionData.getSecret();
if (secret != null) {
this.secret58 = Base58.encode(secret);
}
// Load data file(s)
this.dataFile = DataFile.fromHash(digest);
if (!this.dataFile.exists()) {
if (!this.dataFile.allChunksExist(chunkHashes)) {
// TODO: fetch them?
throw new IllegalStateException(String.format("Missing chunks for file {}", dataFile));
}
// We have all the chunks but not the complete file, so join them
this.dataFile.addChunkHashes(chunkHashes);
this.dataFile.join();
}
// If the complete file still doesn't exist then something went wrong
if (!this.dataFile.exists()) {
throw new IOException(String.format("File doesn't exist: %s", dataFile));
}
// Ensure the complete hash matches the joined chunks
if (!Arrays.equals(dataFile.digest(), digest)) {
throw new IllegalStateException("Unable to validate complete file hash");
}
// Set filePath to the location of the DataFile
this.filePath = Paths.get(dataFile.getFilePath());
}
private void fetchFromFileHash() {
// Load data file directly from the hash
this.dataFile = DataFile.fromHash58(resourceId);
// Set filePath to the location of the DataFile
this.filePath = Paths.get(dataFile.getFilePath());
}
private void decrypt() {
// Decrypt if we have the secret key.
byte[] secret = this.secret58 != null ? Base58.decode(this.secret58) : null;
if (secret != null && secret.length == Transformer.AES256_LENGTH) {
try {
this.unencryptedPath = Paths.get(this.workingPath.toString() + File.separator + "zipped.zip");
SecretKey aesKey = new SecretKeySpec(secret, 0, secret.length, "AES");
AES.decryptFile("AES", aesKey, this.filePath.toString(), this.unencryptedPath.toString());
// Replace filePath pointer with the encrypted file path
// Don't delete the original DataFile, as this is handled in the cleanup phase
this.filePath = this.unencryptedPath;
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException
| BadPaddingException | IllegalBlockSizeException | IOException | InvalidKeyException e) {
throw new IllegalStateException(String.format("Unable to decrypt file %s: %s", dataFile, e.getMessage()));
}
} else {
// Assume it is unencrypted. We may block this in the future.
this.filePath = Paths.get(this.dataFile.getFilePath());
}
}
private void uncompress() throws IOException {
try {
// TODO: compression types
//if (transactionData.getCompression() == ArbitraryTransactionData.Compression.ZIP) {
ZipUtils.unzip(this.filePath.toString(), this.uncompressedPath.getParent().toString());
//}
} catch (IOException e) {
throw new IllegalStateException(String.format("Unable to unzip file: %s", e.getMessage()));
}
// Replace filePath pointer with the uncompressed file path
Files.delete(this.filePath);
this.filePath = this.uncompressedPath;
}
private void cleanupFilesystem() throws IOException {
// Clean up
if (this.uncompressedPath != null) {
File unzippedFile = new File(this.uncompressedPath.toString());
if (unzippedFile.exists()) {
unzippedFile.delete();
}
}
}
public void setSecret58(String secret58) {
this.secret58 = secret58;
}
public Path getFilePath() {
return this.filePath;
}
}

View File

@ -0,0 +1,190 @@
package org.qortal.storage;
import org.apache.commons.io.FileUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.qortal.data.transaction.ArbitraryTransactionData.*;
import org.qortal.crypto.AES;
import org.qortal.storage.DataFile.*;
import org.qortal.utils.ZipUtils;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
public class DataFileWriter {
private static final Logger LOGGER = LogManager.getLogger(DataFileWriter.class);
private Path filePath;
private Method method;
private Compression compression;
private SecretKey aesKey;
private DataFile dataFile;
// Intermediate paths to cleanup
private Path workingPath;
private Path compressedPath;
private Path encryptedPath;
public DataFileWriter(Path filePath, Method method, Compression compression) {
this.filePath = filePath;
this.method = method;
this.compression = compression;
}
public void save() throws IllegalStateException, IOException {
try {
this.preExecute();
this.compress();
this.encrypt();
this.split();
this.validate();
} finally {
this.postExecute();
}
}
private void preExecute() {
// Enforce compression when uploading a directory
File file = new File(this.filePath.toString());
if (file.isDirectory() && compression == Compression.NONE) {
throw new IllegalStateException("Unable to upload a directory without compression");
}
// Create temporary working directory
this.createWorkingDirectory();
}
private void postExecute() throws IOException {
this.cleanupFilesystem();
}
private void createWorkingDirectory() {
// Ensure temp folder exists
Path tempDir;
try {
tempDir = Files.createTempDirectory("qortal");
} catch (IOException e) {
throw new IllegalStateException("Unable to create temp directory");
}
this.workingPath = tempDir;
}
private void compress() {
// Compress the data if requested
if (this.compression != Compression.NONE) {
this.compressedPath = Paths.get(this.workingPath.toString() + File.separator + "zipped.zip");
try {
if (this.compression == Compression.ZIP) {
ZipUtils.zip(this.filePath.toString(), this.compressedPath.toString(), "data");
}
else {
throw new IllegalStateException(String.format("Unknown compression type specified: %s", compression.toString()));
}
// FUTURE: other compression types
// Replace filePath pointer with the zipped file path
// Don't delete the original file/directory, since this may be outside of our directory scope
this.filePath = this.compressedPath;
} catch (IOException e) {
throw new IllegalStateException("Unable to zip directory", e);
}
}
}
private void encrypt() {
this.encryptedPath = Paths.get(this.workingPath.toString() + File.separator + "zipped_encrypted.zip");
try {
// Encrypt the file with AES
this.aesKey = AES.generateKey(256);
AES.encryptFile("AES", this.aesKey, this.filePath.toString(), this.encryptedPath.toString());
// Replace filePath pointer with the encrypted file path
Files.delete(this.filePath);
this.filePath = this.encryptedPath;
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchPaddingException
| BadPaddingException | IllegalBlockSizeException | IOException | InvalidKeyException e) {
throw new IllegalStateException(String.format("Unable to encrypt file %s: %s", this.filePath, e.getMessage()));
}
}
private void validate() throws IOException {
this.dataFile = DataFile.fromPath(this.filePath.toString());
if (this.dataFile == null) {
throw new IOException("No file available when validating");
}
this.dataFile.setSecret(this.aesKey.getEncoded());
// Validate the file
ValidationResult validationResult = this.dataFile.isValid();
if (validationResult != ValidationResult.OK) {
throw new IllegalStateException(String.format("File %s failed validation: %s", this.dataFile, validationResult));
}
LOGGER.info("Whole file hash is valid: {}", this.dataFile.digest58());
// Validate each chunk
for (DataFileChunk chunk : this.dataFile.getChunks()) {
validationResult = chunk.isValid();
if (validationResult != ValidationResult.OK) {
throw new IllegalStateException(String.format("Chunk %s failed validation: %s", chunk, validationResult));
}
}
LOGGER.info("Chunk hashes are valid");
}
private void split() throws IOException {
this.dataFile = DataFile.fromPath(this.filePath.toString());
if (this.dataFile == null) {
throw new IOException("No file available when trying to split");
}
int chunkCount = this.dataFile.split(DataFile.CHUNK_SIZE);
if (chunkCount > 0) {
LOGGER.info(String.format("Successfully split into %d chunk%s", chunkCount, (chunkCount == 1 ? "" : "s")));
}
else {
throw new IllegalStateException("Unable to split file into chunks");
}
}
private void cleanupFilesystem() throws IOException {
// Clean up
if (this.compressedPath != null) {
File zippedFile = new File(this.compressedPath.toString());
if (zippedFile.exists()) {
zippedFile.delete();
}
}
if (this.encryptedPath != null) {
File encryptedFile = new File(this.encryptedPath.toString());
if (encryptedFile.exists()) {
encryptedFile.delete();
}
}
if (this.workingPath != null) {
FileUtils.deleteDirectory(new File(this.workingPath.toString()));
}
}
public DataFile getDataFile() {
return this.dataFile;
}
}