diff --git a/sdk/pom.xml b/sdk/pom.xml
index ac179299..d69b0294 100644
--- a/sdk/pom.xml
+++ b/sdk/pom.xml
@@ -83,5 +83,11 @@
4.13.1
test
+
+ org.apache.commons
+ commons-compress
+ 1.26.1
+ test
+
diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java b/sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java
index ec5b9e68..cc9d8d1e 100644
--- a/sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java
+++ b/sdk/src/main/java/io/opentdf/platform/sdk/ZipReader.java
@@ -1,208 +1,290 @@
package io.opentdf.platform.sdk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
+import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
public class ZipReader {
+
+ public static final Logger logger = LoggerFactory.getLogger(ZipReader.class);
+ public static final int END_OF_CENTRAL_DIRECTORY_SIZE = 22;
+ public static final int ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE = 20;
+
+ final ByteBuffer longBuf = ByteBuffer.allocate(Long.BYTES).order(ByteOrder.LITTLE_ENDIAN);
+ private Long readLong() throws IOException {
+ longBuf.clear();
+ if (this.zipChannel.read(longBuf) != 8) {
+ return null;
+ }
+ longBuf.flip();
+ return longBuf.getLong();
+ }
+
+ final ByteBuffer intBuf = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN);
+ private Integer readInt() throws IOException {
+ intBuf.clear();
+ if (this.zipChannel.read(intBuf) != 4) {
+ return null;
+ }
+ intBuf.flip();
+ return intBuf.getInt();
+ }
+
+ final ByteBuffer shortBuf = ByteBuffer.allocate(Short.BYTES).order(ByteOrder.LITTLE_ENDIAN);
+
+ private Short readShort() throws IOException {
+ shortBuf.clear();
+ if (this.zipChannel.read(shortBuf) != 2) {
+ return null;
+ }
+ shortBuf.flip();
+ return shortBuf.getShort();
+ }
+
+ private static class CentralDirectoryRecord {
+ final long numEntries;
+ final long offsetToStart;
+
+ public CentralDirectoryRecord(long numEntries, long offsetToStart) {
+ this.numEntries = numEntries;
+ this.offsetToStart = offsetToStart;
+ }
+ }
+
+ private static final int ZIP_64_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06064b50;
private static final int END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50;
- private static final int ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06064b50;
private static final int ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE = 0x07064b50;
- private static final int CENTRAL_DIRECTORY_LOCATOR_SIGNATURE = 0x02014b50;
- private static final int LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
+ private static final int CENTRAL_FILE_HEADER_SIGNATURE = 0x02014b50;
+
+ private static final int LOCAL_FILE_HEADER_SIGNATURE = 0x04034b50;
private static final int ZIP64_MAGICVAL = 0xFFFFFFFF;
private static final int ZIP64_EXTID= 0x0001;
- private int numEntries;
- private short fileNameLength;
- private short extraFieldLength;
- private long offsetToStartOfCentralDirectory;
- private long relativeOffsetEndOfZip64EndOfCentralDirectory;
-
- public void readEndOfCentralDirectory(ByteBuffer buffer) throws Exception {
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- long fileSize = buffer.capacity();
- long pointer = fileSize - 22; // 22 is the minimum size of the EOCDR
-
- // Search for the EOCDR from the end of the file
- while (pointer >= 0) {
- buffer.position((int)pointer);
- int signature = buffer.getInt();
+ CentralDirectoryRecord readEndOfCentralDirectory() throws IOException {
+ long eoCDRStart = zipChannel.size() - END_OF_CENTRAL_DIRECTORY_SIZE; // 22 is the minimum size of the EOCDR
+
+ while (eoCDRStart >= 0) {
+ zipChannel.position(eoCDRStart);
+ int signature = readInt();
if (signature == END_OF_CENTRAL_DIRECTORY_SIGNATURE) {
- System.out.println("Found End of Central Directory Record");
+ if (logger.isDebugEnabled()) {
+ logger.debug("Found end of central directory signature at {}", zipChannel.position() - Integer.BYTES);
+ }
break;
}
- pointer--;
+ eoCDRStart--;
}
- if (pointer < 0) {
- throw new Exception("Invalid tdf file");
+ if (eoCDRStart < 0) {
+ throw new RuntimeException("Didn't find the end of central directory");
}
- // Read the EOCDR
- short diskNumber = buffer.getShort();
- short centralDirectoryDiskNumber = buffer.getShort();
- short numEntriesThisDisk = buffer.getShort();
- numEntries = buffer.getShort();
- int centralDirectorySize = buffer.getInt();
- offsetToStartOfCentralDirectory = buffer.getInt();
- short commentLength = buffer.getShort();
+ short diskNumber = readShort();
+ short centralDirectoryDiskNumber = readShort();
+ short numCDEntriesOnThisDisk = readShort();
+
+ int totalNumEntries = readShort();
+ int sizeOfCentralDirectory = readInt();
+ long offsetToStartOfCentralDirectory = readInt();
+ short commentLength = readShort();
- // buffer's position at the start of the Central Directory
- boolean isZip64 = false;
if (offsetToStartOfCentralDirectory != ZIP64_MAGICVAL) {
- //buffer.position((int)offsetToStartOfCentralDirectory);
- } else {
- isZip64 = true;
- long index = fileSize - (22+ 20); // 22 is the size of the EOCDR and 20 is the size of the Zip64 EOCDR
- buffer.position((int)index);
- readZip64EndOfCentralDirectoryLocator(buffer);
- index = fileSize - (22 + 20 + 56); // 56 is the size of the Zip64 EOCDR
- buffer.position((int)index);
- readZip64EndOfCentralDirectoryRecord(buffer);
- //buffer.position((int)offsetToStartOfCentralDirectory);
- }
- // buffer.position(centralDirectoryOffset);
+ return new CentralDirectoryRecord(totalNumEntries, offsetToStartOfCentralDirectory);
+ }
+
+ long zip64CentralDirectoryLocatorStart = zipChannel.size() - (ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIZE + END_OF_CENTRAL_DIRECTORY_SIZE + commentLength);
+ zipChannel.position(zip64CentralDirectoryLocatorStart);
+ return extractZIP64CentralDirectoryInfo();
}
- private void readZip64EndOfCentralDirectoryLocator(ByteBuffer buffer) {
- int signature = buffer.getInt() ;
+ private CentralDirectoryRecord extractZIP64CentralDirectoryInfo() throws IOException {
+ // buffer's position at the start of the Central Directory
+ int signature = readInt();
if (signature != ZIP64_END_OF_CENTRAL_DIRECTORY_LOCATOR_SIGNATURE) {
throw new RuntimeException("Invalid Zip64 End of Central Directory Record Signature");
}
- int numberOfDiskWithZip64End = buffer.getInt();
- relativeOffsetEndOfZip64EndOfCentralDirectory = buffer.getLong();
- int totalNumberOfDisks = buffer.getInt();
- }
- private void readZip64EndOfCentralDirectoryRecord(ByteBuffer buffer) {
- int signature = buffer.getInt() ;
- if (signature != ZIP64_END_OF_CENTRAL_DIRECTORY_SIGNATURE) {
- throw new RuntimeException("Invalid Zip64 End of Central Directory Record ");
- }
- long sizeOfZip64EndOfCentralDirectoryRecord = buffer.getLong();
- short versionMadeBy = buffer.getShort();
- short versionNeededToExtract = buffer.getShort();
- int diskNumber = buffer.getInt();
- int diskWithCentralDirectory = buffer.getInt();
- long numEntriesOnThisDisk = buffer.getLong();
- numEntries = (int)buffer.getLong();
- long centralDirectorySize = buffer.getLong();
- offsetToStartOfCentralDirectory = buffer.getLong();
- }
+ int centralDirectoryDiskNumber = readInt();
+ long offsetToEndOfCentralDirectory = readLong();
+ int totalNumberOfDisks = readInt();
- public int getNumEntries() {
- return numEntries;
- }
+ zipChannel.position(offsetToEndOfCentralDirectory);
+ int sig = readInt();
+ if (sig != ZIP_64_END_OF_CENTRAL_DIRECTORY_SIGNATURE) {
+ throw new RuntimeException("Invalid");
+ }
+ long sizeOfEndOfCentralDirectoryRecord = readLong();
+ short versionMadeBy = readShort();
+ short versionNeeded = readShort();
+ int thisDiskNumber = readInt();
+ int cdDiskNumber = readInt();
+ long numCDEntriesOnThisDisk = readLong();
+ long totalNumCDEntries = readLong();
+ long cdSize = readLong();
+ long cdOffset = readLong();
- public short getFileNameLength() {
- return fileNameLength;
+ return new CentralDirectoryRecord(totalNumCDEntries, cdOffset);
}
- public short getExtraFieldLength() {
- return extraFieldLength;
- }
+ public class Entry {
+ private final long fileSize;
+ private final String fileName;
+ final long offsetToLocalHeader;
- public long getCDOffset() {
- return offsetToStartOfCentralDirectory;
- }
+ private Entry(byte[] fileName, long offsetToLocalHeader, long fileSize) {
+ this.fileName = new String(fileName, StandardCharsets.UTF_8);
+ this.offsetToLocalHeader = offsetToLocalHeader;
+ this.fileSize = fileSize;
+ }
- public long readCentralDirectoryFileHeader(ByteBuffer buffer) {
- System.out.println("Buffer position: " + buffer.position());
- int signature = buffer.getInt();
- if (signature != CENTRAL_DIRECTORY_LOCATOR_SIGNATURE) {
- throw new RuntimeException("Invalid Central Directory File Header Signature");
+ public String getName() {
+ return fileName;
}
- short versionMadeBy = buffer.getShort();
- short versionNeededToExtract = buffer.getShort();
- short generalPurposeBitFlag = buffer.getShort();
- short compressionMethod = buffer.getShort();
- short lastModFileTime = buffer.getShort();
- short lastModFileDate = buffer.getShort();
- int crc32 = buffer.getInt();
- int compressedSize = buffer.getInt();
- int uncompressedSize = buffer.getInt();
- fileNameLength = buffer.getShort();
- extraFieldLength = buffer.getShort();
- short fileCommentLength = buffer.getShort();
- short diskNumberStart = buffer.getShort();
- short internalFileAttributes = buffer.getShort();
- int externalFileAttributes = buffer.getInt();
- long relativeOffsetOfLocalHeader = buffer.getInt() ;
-
- byte[] fileName = new byte[fileNameLength];
- buffer.get(fileName);
- String fileNameString = new String(fileName, StandardCharsets.UTF_8);
-////
- if (compressedSize == ZIP64_MAGICVAL || uncompressedSize == ZIP64_MAGICVAL || relativeOffsetOfLocalHeader == ZIP64_MAGICVAL) {
- // Parse the extra field
- for (int i = 0; i < extraFieldLength; ) {
- int headerId = buffer.getShort();
- int dataSize = buffer.getShort();
- i += 4;
-
- if (headerId == ZIP64_EXTID) {
- if (compressedSize == ZIP64_MAGICVAL) {
- compressedSize = (int)buffer.getLong();
- i += 8;
+
+ public InputStream getData() throws IOException {
+ zipChannel.position(offsetToLocalHeader);
+ if (readInt() != LOCAL_FILE_HEADER_SIGNATURE) {
+ throw new RuntimeException("Invalid Local Header Signature");
+ }
+ zipChannel.position(zipChannel.position()
+ + Short.BYTES
+ + Short.BYTES
+ + Short.BYTES
+ + Short.BYTES
+ + Short.BYTES
+ + Integer.BYTES);
+
+ long compressedSize = readInt();
+ long uncompressedSize = readInt();
+ int filenameLength = readShort();
+ int extrafieldLength = readShort();
+
+ final long startPosition = zipChannel.position() + filenameLength + extrafieldLength;
+ final long endPosition = startPosition + fileSize;
+ final ByteBuffer buf = ByteBuffer.allocate(1);
+ return new InputStream() {
+ long offset = 0;
+ @Override
+ public int read() throws IOException {
+ if (doneReading()) {
+ return -1;
+ }
+ setChannelPosition();
+ while (buf.position() != buf.capacity()) {
+ if (zipChannel.read(buf) < 0) {
+ return -1;
+ }
+ }
+ offset += 1;
+ return buf.array()[0] & 0xFF;
+ }
+
+ private boolean doneReading() {
+ return offset >= fileSize;
+ }
+
+ private void setChannelPosition() throws IOException {
+ var nextPosition = startPosition + offset;
+ if (zipChannel.position() != nextPosition) {
+ zipChannel.position(nextPosition);
}
- if (uncompressedSize == ZIP64_MAGICVAL) {
- uncompressedSize = (int)buffer.getLong();
- i += 8;
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (doneReading()) {
+ return -1;
}
- if (relativeOffsetOfLocalHeader == ZIP64_MAGICVAL) {
- relativeOffsetOfLocalHeader = buffer.getLong();
- i += 8;
+ setChannelPosition();
+ var lenToRead = (int)Math.min(len, fileSize - offset); // cast is always valid because len is an int
+ var buf = ByteBuffer.wrap(b, off, lenToRead);
+ var nread = zipChannel.read(buf);
+ if (nread > 0) {
+ offset += nread;
}
- } else {
- // Skip other extra fields
- buffer.position(buffer.position() + dataSize);
- i += dataSize;
+ return nread;
+ }
+ };
+ }
+ }
+ public Entry readCentralDirectoryFileHeader() throws IOException {
+ int signature = readInt();
+ if (signature != CENTRAL_FILE_HEADER_SIGNATURE) {
+ throw new RuntimeException("Invalid Central Directory File Header Signature");
+ }
+ short versionMadeBy = readShort();
+ short versionNeededToExtract = readShort();
+ short generalPurposeBitFlag = readShort();
+ short compressionMethod = readShort();
+ short lastModFileTime = readShort();
+ short lastModFileDate = readShort();
+ int crc32 = readInt();
+ long compressedSize = readInt();
+ long uncompressedSize = readInt();
+ int fileNameLength = readShort();
+ int extraFieldLength = readShort();
+ short fileCommentLength = readShort();
+ int diskNumberStart = readShort();
+ short internalFileAttributes = readShort();
+ int externalFileAttributes = readInt();
+ long relativeOffsetOfLocalHeader = readInt();
+
+ ByteBuffer fileName = ByteBuffer.allocate(fileNameLength);
+ while (fileName.position() != fileName.capacity()) {
+ zipChannel.read(fileName);
+ }
+
+ // Parse the extra field
+ for (final long startPos = zipChannel.position(); zipChannel.position() < startPos + extraFieldLength; ) {
+ long fieldStart = zipChannel.position();
+ int headerId = readShort();
+ int dataSize = readShort();
+
+ if (headerId == ZIP64_EXTID) {
+ if (compressedSize == -1) {
+ compressedSize = readLong();
+ }
+ if (uncompressedSize == -1) {
+ uncompressedSize = readLong();
+ }
+ if (relativeOffsetOfLocalHeader == -1) {
+ relativeOffsetOfLocalHeader = readLong();
+ }
+ if (diskNumberStart == ZIP64_MAGICVAL) {
+ diskNumberStart = readInt();
}
}
+ // Skip other extra fields
+ zipChannel.position(fieldStart + dataSize + 4);
}
-////
- byte[] extraField = new byte[extraFieldLength];
- buffer.get(extraField);
- byte[] fileComment = new byte[fileCommentLength];
- buffer.get(fileComment);
- String fileCommentString = new String(fileComment, StandardCharsets.UTF_8);
- return relativeOffsetOfLocalHeader;
+ zipChannel.position(zipChannel.position() + fileCommentLength);
+
+ return new Entry(fileName.array(), relativeOffsetOfLocalHeader, uncompressedSize);
+ }
+
+
+ public ZipReader(SeekableByteChannel channel) throws IOException {
+ zipChannel = channel;
+ var centralDirectoryRecord = readEndOfCentralDirectory();
+ zipChannel.position(centralDirectoryRecord.offsetToStart);
+ for (int i = 0; i < centralDirectoryRecord.numEntries; i++) {
+ entries.add(readCentralDirectoryFileHeader());
+ }
}
+
+ final SeekableByteChannel zipChannel;
+ final ArrayList entries = new ArrayList<>();
- public void readLocalFileHeader(ByteBuffer buffer) {
- int signature = buffer.getInt();
- if (signature != LOCAL_FILE_HEADER_SIGNATURE) {
- throw new RuntimeException("Invalid Local File Header Signature");
- }
- short versionNeededToExtract = buffer.getShort();
- short generalPurposeBitFlag = buffer.getShort();
- short compressionMethod = buffer.getShort();
- short lastModFileTime = buffer.getShort();
- short lastModFileDate = buffer.getShort();
- int crc32 = buffer.getInt();
- int compressedSize = buffer.getInt();
- int uncompressedSize = buffer.getInt();
- short fileNameLength = buffer.getShort();
- short extraFieldLength = buffer.getShort();
-
- byte[] fileName = new byte[fileNameLength];
- buffer.get(fileName);
- String fileNameString = new String(fileName, StandardCharsets.UTF_8);
- System.out.println("File name: " + fileNameString);
-
- byte[] extraField = new byte[extraFieldLength];
- buffer.get(extraField);
-
- /*byte[] fileData = new byte[compressedSize];
- buffer.get(fileData);
-
- if (compressionMethod == 0) {
- String fileContent = new String(fileData, StandardCharsets.UTF_8);
- System.out.println("File content: " + fileContent);
- } else {
- System.out.println("File is compressed, need to decompress it first");
- }*/
+ public List getEntries() {
+ return entries;
}
}
\ No newline at end of file
diff --git a/sdk/src/main/java/io/opentdf/platform/sdk/ZipWriter.java b/sdk/src/main/java/io/opentdf/platform/sdk/ZipWriter.java
index 50395b9a..70af96f4 100644
--- a/sdk/src/main/java/io/opentdf/platform/sdk/ZipWriter.java
+++ b/sdk/src/main/java/io/opentdf/platform/sdk/ZipWriter.java
@@ -1,366 +1,293 @@
package io.opentdf.platform.sdk;
-import java.io.FileOutputStream;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
-import java.time.ZoneOffset;
import java.util.ArrayList;
-import java.util.List;
import java.util.zip.CRC32;
public class ZipWriter {
- private enum WriteState {Initial, Appending, Finished}
-
- private static final int ZIP_VERSION = 20;
+ private static final int ZIP_VERSION = 0x2D;
private static final int ZIP_64_MAGIC_VAL = 0xFFFFFFFF;
- private static final int ZIP_64_EXTENDED_LOCAL_INFO_EXTRA_FIELD_SIZE = 24;
- private static final int ZIP_64_EXTENDED_INFO_EXTRA_FIELD_SIZE = 28;
+ private static final long ZIP_64_END_OF_CD_RECORD_SIZE = 56;
+ private static final int ZIP_64_LOCAL_EXTENDED_INFO_EXTRA_FIELD_SIZE = 24;
+
+ private static final int ZIP_64_GLOBAL_EXTENDED_INFO_EXTRA_FIELD_SIZE = 28;
private static final int ZIP_32_DATA_DESCRIPTOR_SIZE = 16;
+
+ private static final int ZIP_64_DATA_DESCRIPTOR_SIZE = 24;
private static final int HALF_SECOND = 2;
private static final int BASE_YEAR = 1980;
private static final int DEFAULT_SECOND_VALUE = 29;
private static final int MONTH_SHIFT = 5;
- private OutputStream writer;
- private long currentOffset;
- private long lastOffsetCDFileHeader;
- private FileInfo fileInfo;
- private List fileInfoEntries;
- private WriteState writeState;
- private boolean isZip64;
- private long totalBytes;
-
- public ZipWriter(OutputStream writer) {
- this.writer = writer;
- this.currentOffset = 0;
- this.lastOffsetCDFileHeader = 0;
- this.fileInfo = new FileInfo();
- this.fileInfoEntries = new ArrayList<>();
- this.writeState = WriteState.Initial;
- this.isZip64 = false;
- this.totalBytes = 0;
- }
+ private static class FileBytes {
+ public FileBytes(String name, byte[] data) {
+ this.name = name;
+ this.data = data;
+ }
- public void enableZip64() {
- this.isZip64 = true;
+ final String name;
+ final byte[] data;
}
- public void addHeader(String filename, long size) throws IOException {
- if (filename == null || filename.isEmpty()) {
- throw new IllegalArgumentException("Filename cannot be null or empty");
+ private static class InputStream {
+ public InputStream(String name, java.io.InputStream data) {
+ this.name = name;
+ this.data = data;
}
- if (this.writeState != WriteState.Initial && this.writeState != WriteState.Finished) {
- throw new IOException("Cannot add a new file until the current file write is completed: " + this.fileInfo.filename);
- }
-
- this.fileInfo = new FileInfo();
- this.fileInfo.filename = filename;
+ final String name;
+ private final java.io.InputStream data;
+ }
- if (!this.isZip64) {
- this.isZip64 = size > 4L * 1024 * 1024 * 1024; // if file size is greater than 4GB
- }
+ private final ArrayList byteFiles = new ArrayList<>();
+ private final ArrayList streamFiles = new ArrayList<>();
- this.writeState = WriteState.Initial;
- this.fileInfo.size = size;
- this.fileInfo.filename = filename;
+ public ZipWriter file(String name, java.io.InputStream data) {
+ streamFiles.add(new InputStream(name, data));
+ return this;
}
- public void addData(byte[] data) throws IOException {
- long fileTime, fileDate;
- fileTime = fileDate = getTimeDateUnMSDosFormat();
-
- if (this.writeState == WriteState.Initial) {
- LocalFileHeader localFileHeader = new LocalFileHeader();
- localFileHeader.signature = 0x04034b50;
- localFileHeader.version = ZIP_VERSION;
- localFileHeader.generalPurposeBitFlag = 0x08;
- localFileHeader.compressionMethod = 0;
- localFileHeader.lastModifiedTime = (int) fileTime;
- localFileHeader.lastModifiedDate = (int) fileDate;
- localFileHeader.crc32 = 0;
- localFileHeader.compressedSize = 0;
- localFileHeader.uncompressedSize = 0;
- localFileHeader.extraFieldLength = 0;
-
- if (this.isZip64) {
- localFileHeader.compressedSize = ZIP_64_MAGIC_VAL;
- localFileHeader.uncompressedSize = ZIP_64_MAGIC_VAL;
- localFileHeader.extraFieldLength = ZIP_64_EXTENDED_LOCAL_INFO_EXTRA_FIELD_SIZE;
- }
+ public ZipWriter file(String name, byte[] content) {
+ byteFiles.add(new FileBytes(name, content));
+ return this;
+ }
- localFileHeader.filenameLength = (short) this.fileInfo.filename.length();
+ public void build(OutputStream sink) throws IOException {
+ var out = new CountingOutputStream(sink);
+ ArrayList fileInfos = new ArrayList<>();
- // Write local file header
- ByteBuffer buffer = ByteBuffer.allocate(30 + this.fileInfo.filename.length());
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putInt(localFileHeader.signature);
- buffer.putShort((short) localFileHeader.version);
- buffer.putShort((short) localFileHeader.generalPurposeBitFlag);
- buffer.putShort((short) localFileHeader.compressionMethod);
- buffer.putShort((short) localFileHeader.lastModifiedTime);
- buffer.putShort((short) localFileHeader.lastModifiedDate);
- buffer.putInt(localFileHeader.crc32);
- buffer.putInt(localFileHeader.compressedSize);
- buffer.putInt(localFileHeader.uncompressedSize);
- buffer.putShort(localFileHeader.filenameLength);
- buffer.putShort(localFileHeader.extraFieldLength);
- buffer.put(this.fileInfo.filename.getBytes(StandardCharsets.UTF_8));
-
- this.writer.write(buffer.array());
-
- if (this.isZip64) {
- Zip64ExtendedLocalInfoExtraField zip64ExtendedLocalInfoExtraField = new Zip64ExtendedLocalInfoExtraField();
- zip64ExtendedLocalInfoExtraField.signature = 0x0001;
- zip64ExtendedLocalInfoExtraField.size = ZIP_64_EXTENDED_LOCAL_INFO_EXTRA_FIELD_SIZE - 4;
- zip64ExtendedLocalInfoExtraField.originalSize = this.fileInfo.size;
- zip64ExtendedLocalInfoExtraField.compressedSize = this.fileInfo.size;
-
- buffer = ByteBuffer.allocate(ZIP_64_EXTENDED_LOCAL_INFO_EXTRA_FIELD_SIZE);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putShort((short) zip64ExtendedLocalInfoExtraField.signature);
- buffer.putShort((short) zip64ExtendedLocalInfoExtraField.size);
- buffer.putLong(zip64ExtendedLocalInfoExtraField.originalSize);
- buffer.putLong(zip64ExtendedLocalInfoExtraField.compressedSize);
-
- this.writer.write(buffer.array());
- }
+ for (var byteFile : byteFiles) {
+ var fileInfo = writeByteArray(byteFile.name, byteFile.data, out);
+ fileInfos.add(fileInfo);
+ }
- this.writeState = WriteState.Appending;
- this.fileInfo.crc = new CRC32().getValue();
- this.fileInfo.fileTime = (short) fileTime;
- this.fileInfo.fileDate = (short) fileDate;
+ for (var streamFile : streamFiles) {
+ var fileInfo = writeStream(streamFile.name, streamFile.data, out);
+ fileInfos.add(fileInfo);
}
- // Write the data contents
- this.writer.write(data);
+ final var startOfCentralDirectory = out.position;
+ for (var fileInfo : fileInfos) {
+ writeCentralDirectoryHeader(fileInfo, out);
+ }
- // Update CRC32
- CRC32 crc32 = new CRC32();
- crc32.update(data);
- this.fileInfo.crc = crc32.getValue();
+ final var sizeOfCentralDirectory = out.position - startOfCentralDirectory;
+ writeEndOfCentralDirectory(!streamFiles.isEmpty(), fileInfos.size(), startOfCentralDirectory, sizeOfCentralDirectory, out);
+ }
- // Update file size
- this.fileInfo.offset += data.length;
+ public byte[] build() throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ build(out);
+ return out.toByteArray();
+ }
- // Check if we reached the end
- if (this.fileInfo.offset >= this.fileInfo.size) {
- this.writeState = WriteState.Finished;
- this.fileInfo.offset = this.currentOffset;
- this.fileInfo.flag = 0x08;
- this.fileInfoEntries.add(this.fileInfo);
+ private static void writeCentralDirectoryHeader(FileInfo fileInfo, OutputStream out) throws IOException {
+ CDFileHeader cdFileHeader = new CDFileHeader();
+ cdFileHeader.generalPurposeBitFlag = fileInfo.flag;
+ cdFileHeader.lastModifiedTime = fileInfo.fileTime;
+ cdFileHeader.lastModifiedDate = fileInfo.fileDate;
+ cdFileHeader.crc32 = (int) fileInfo.crc;
+ cdFileHeader.filenameLength = (short) fileInfo.filename.length();
+ cdFileHeader.extraFieldLength = 0;
+ cdFileHeader.compressedSize = (int) fileInfo.size;
+ cdFileHeader.uncompressedSize = (int) fileInfo.size;
+ cdFileHeader.localHeaderOffset = (int) fileInfo.offset;
+
+ if (fileInfo.isZip64) {
+ cdFileHeader.compressedSize = ZIP_64_MAGIC_VAL;
+ cdFileHeader.uncompressedSize = ZIP_64_MAGIC_VAL;
+ cdFileHeader.localHeaderOffset = ZIP_64_MAGIC_VAL;
+ cdFileHeader.extraFieldLength = ZIP_64_GLOBAL_EXTENDED_INFO_EXTRA_FIELD_SIZE;
}
- if (this.writeState == WriteState.Finished) {
- if (this.isZip64) {
- // Write Zip64 data descriptor
- Zip64DataDescriptor zip64DataDescriptor = new Zip64DataDescriptor();
- zip64DataDescriptor.signature = 0x08074b50;
- zip64DataDescriptor.crc32 = this.fileInfo.crc;
- zip64DataDescriptor.compressedSize = this.fileInfo.size;
- zip64DataDescriptor.uncompressedSize = this.fileInfo.size;
-
- ByteBuffer buffer = ByteBuffer.allocate(ZIP_32_DATA_DESCRIPTOR_SIZE);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putInt(zip64DataDescriptor.signature);
- buffer.putInt((int) zip64DataDescriptor.crc32);
- buffer.putInt((int) zip64DataDescriptor.compressedSize);
- buffer.putInt((int) zip64DataDescriptor.uncompressedSize);
-
- this.writer.write(buffer.array());
-
- this.currentOffset += 30 + this.fileInfo.filename.length() + this.fileInfo.size + ZIP_64_EXTENDED_LOCAL_INFO_EXTRA_FIELD_SIZE + ZIP_32_DATA_DESCRIPTOR_SIZE;
- } else {
- // Write Zip32 data descriptor
- Zip32DataDescriptor zip32DataDescriptor = new Zip32DataDescriptor();
- zip32DataDescriptor.signature = 0x08074b50;
- zip32DataDescriptor.crc32 = this.fileInfo.crc;
- zip32DataDescriptor.compressedSize = (int) this.fileInfo.size;
- zip32DataDescriptor.uncompressedSize = (int) this.fileInfo.size;
-
- ByteBuffer buffer = ByteBuffer.allocate(ZIP_32_DATA_DESCRIPTOR_SIZE);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putInt(zip32DataDescriptor.signature);
- buffer.putInt((int) zip32DataDescriptor.crc32);
- buffer.putInt(zip32DataDescriptor.compressedSize);
- buffer.putInt(zip32DataDescriptor.uncompressedSize);
-
- this.writer.write(buffer.array());
-
- this.currentOffset += 30 + this.fileInfo.filename.length() + this.fileInfo.size + ZIP_32_DATA_DESCRIPTOR_SIZE;
- }
+ cdFileHeader.write(out, fileInfo.filename.getBytes(StandardCharsets.UTF_8));
- this.fileInfo = new FileInfo();
+ if (fileInfo.isZip64) {
+ Zip64GlobalExtendedInfoExtraField zip64ExtendedInfoExtraField = new Zip64GlobalExtendedInfoExtraField();
+ zip64ExtendedInfoExtraField.originalSize = fileInfo.size;
+ zip64ExtendedInfoExtraField.compressedSize = fileInfo.size;
+ zip64ExtendedInfoExtraField.localFileHeaderOffset = fileInfo.offset;
+ zip64ExtendedInfoExtraField.write(out);
}
}
- public void finish() throws IOException {
- writeCentralDirectory();
- writeEndOfCentralDirectory();
- }
+ private FileInfo writeStream(String name, java.io.InputStream data, CountingOutputStream out) throws IOException {
+ var startPosition = out.position;
+ long fileTime, fileDate;
+ fileTime = fileDate = getTimeDateUnMSDosFormat();
- private void writeCentralDirectory() throws IOException {
- this.lastOffsetCDFileHeader = this.currentOffset;
-
- for (FileInfo fileInfo : this.fileInfoEntries) {
- CDFileHeader cdFileHeader = new CDFileHeader();
- cdFileHeader.signature = 0x02014b50;
- cdFileHeader.versionCreated = ZIP_VERSION;
- cdFileHeader.versionNeeded = ZIP_VERSION;
- cdFileHeader.generalPurposeBitFlag = fileInfo.flag;
- cdFileHeader.compressionMethod = 0;
- cdFileHeader.lastModifiedTime = fileInfo.fileTime;
- cdFileHeader.lastModifiedDate = fileInfo.fileDate;
- cdFileHeader.crc32 = (int) fileInfo.crc;
- cdFileHeader.filenameLength = (short) fileInfo.filename.length();
- cdFileHeader.fileCommentLength = 0;
- cdFileHeader.diskNumberStart = 0;
- cdFileHeader.internalFileAttributes = 0;
- cdFileHeader.externalFileAttributes = 0;
- cdFileHeader.compressedSize = (int) fileInfo.size;
- cdFileHeader.uncompressedSize = (int) fileInfo.size;
- cdFileHeader.localHeaderOffset = (int) fileInfo.offset;
- cdFileHeader.extraFieldLength = 0;
-
- if (this.isZip64) {
- cdFileHeader.compressedSize = ZIP_64_MAGIC_VAL;
- cdFileHeader.uncompressedSize = ZIP_64_MAGIC_VAL;
- cdFileHeader.localHeaderOffset = ZIP_64_MAGIC_VAL;
- cdFileHeader.extraFieldLength = ZIP_64_EXTENDED_INFO_EXTRA_FIELD_SIZE;
+ var nameBytes = name.getBytes(StandardCharsets.UTF_8);
+ LocalFileHeader localFileHeader = new LocalFileHeader();
+ localFileHeader.lastModifiedTime = (int) fileTime;
+ localFileHeader.lastModifiedDate = (int) fileDate;
+ localFileHeader.filenameLength = (short) nameBytes.length;
+ localFileHeader.crc32 = 0;
+ localFileHeader.generalPurposeBitFlag = (1 << 3) | (1 << 11); // we are using the data descriptor and we are using UTF-8
+ localFileHeader.compressedSize = ZIP_64_MAGIC_VAL;
+ localFileHeader.uncompressedSize = ZIP_64_MAGIC_VAL;
+ localFileHeader.extraFieldLength = 0;
+
+ localFileHeader.write(out, nameBytes);
+
+ var crc = new CRC32();
+ var outputStream = new OutputStream() {
+ @Override
+ public void write(int b) throws IOException {
+ crc.update(b);
+ out.write(b);
}
- ByteBuffer buffer = ByteBuffer.allocate(46 + fileInfo.filename.length());
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putInt(cdFileHeader.signature);
- buffer.putShort((short) cdFileHeader.versionCreated);
- buffer.putShort((short) cdFileHeader.versionNeeded);
- buffer.putShort((short) cdFileHeader.generalPurposeBitFlag);
- buffer.putShort((short) cdFileHeader.compressionMethod);
- buffer.putShort((short) cdFileHeader.lastModifiedTime);
- buffer.putShort((short) cdFileHeader.lastModifiedDate);
- buffer.putInt((int) cdFileHeader.crc32);
- buffer.putInt(cdFileHeader.compressedSize);
- buffer.putInt(cdFileHeader.uncompressedSize);
- buffer.putShort(cdFileHeader.filenameLength);
- buffer.putShort(cdFileHeader.fileCommentLength);
- buffer.putShort(cdFileHeader.diskNumberStart);
- buffer.putShort(cdFileHeader.internalFileAttributes);
- buffer.putInt(cdFileHeader.externalFileAttributes);
- buffer.putInt(cdFileHeader.localHeaderOffset);
- buffer.putShort(cdFileHeader.extraFieldLength);
- buffer.put(fileInfo.filename.getBytes(StandardCharsets.UTF_8));
-
- this.writer.write(buffer.array());
-
- if (this.isZip64) {
- Zip64ExtendedInfoExtraField zip64ExtendedInfoExtraField = new Zip64ExtendedInfoExtraField();
- zip64ExtendedInfoExtraField.signature = 0x0001;
- zip64ExtendedInfoExtraField.size = ZIP_64_EXTENDED_INFO_EXTRA_FIELD_SIZE - 4;
- zip64ExtendedInfoExtraField.originalSize = fileInfo.size;
- zip64ExtendedInfoExtraField.compressedSize = fileInfo.size;
- zip64ExtendedInfoExtraField.localFileHeaderOffset = fileInfo.offset;
-
- buffer = ByteBuffer.allocate(ZIP_64_EXTENDED_INFO_EXTRA_FIELD_SIZE);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putShort((short) zip64ExtendedInfoExtraField.signature);
- buffer.putShort((short) zip64ExtendedInfoExtraField.size);
- buffer.putLong(zip64ExtendedInfoExtraField.originalSize);
- buffer.putLong(zip64ExtendedInfoExtraField.compressedSize);
- buffer.putLong(zip64ExtendedInfoExtraField.localFileHeaderOffset);
-
- this.writer.write(buffer.array());
+ @Override
+ public void write(byte[] b) throws IOException {
+ crc.update(b);
+ out.write(b);
}
- this.lastOffsetCDFileHeader += 46 + fileInfo.filename.length();
-
- if (this.isZip64) {
- this.lastOffsetCDFileHeader += ZIP_64_EXTENDED_INFO_EXTRA_FIELD_SIZE;
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ crc.update(b, off, len);
+ out.write(b, off, len);
}
- }
+ };
+
+ long fileStart = out.position;
+ data.transferTo(outputStream);
+ long fileSize = out.position - fileStart;
+ long crcValue = crc.getValue();
+
+ // Write Zip64 data descriptor
+ Zip64DataDescriptor dataDescriptor = new Zip64DataDescriptor();
+ dataDescriptor.crc32 = crcValue;
+ dataDescriptor.compressedSize = fileSize;
+ dataDescriptor.uncompressedSize = fileSize;
+ dataDescriptor.write(out);
+
+ var fileInfo = new FileInfo();
+ fileInfo.offset = startPosition;
+ fileInfo.flag = (short) localFileHeader.generalPurposeBitFlag;
+ fileInfo.size = fileSize;
+ fileInfo.crc = crcValue;
+ fileInfo.filename = name;
+ fileInfo.fileTime = (short) fileTime;
+ fileInfo.fileDate = (short) fileDate;
+ fileInfo.isZip64 = true;
+
+ return fileInfo;
}
- private void writeEndOfCentralDirectory() throws IOException {
- if (this.isZip64) {
- writeZip64EndOfCentralDirectory();
- writeZip64EndOfCentralDirectoryLocator();
+ private FileInfo writeByteArray(String name, byte[] data, CountingOutputStream out) throws IOException {
+ var startPosition = out.position;
+ long fileTime, fileDate;
+ fileTime = fileDate = getTimeDateUnMSDosFormat();
+
+ var crc = new CRC32();
+ crc.update(data);
+ var crcValue = crc.getValue();
+
+ var nameBytes = name.getBytes(StandardCharsets.UTF_8);
+ LocalFileHeader localFileHeader = new LocalFileHeader();
+ localFileHeader.lastModifiedTime = (int) fileTime;
+ localFileHeader.lastModifiedDate = (int) fileDate;
+ localFileHeader.filenameLength = (short) nameBytes.length;
+ localFileHeader.generalPurposeBitFlag = 0;
+ localFileHeader.crc32 = (int) crcValue;
+ localFileHeader.compressedSize = data.length;
+ localFileHeader.uncompressedSize = data.length;
+ localFileHeader.extraFieldLength = 0;
+
+ localFileHeader.write(out, name.getBytes(StandardCharsets.UTF_8));
+
+ out.write(data);
+
+ var fileInfo = new FileInfo();
+ fileInfo.offset = startPosition;
+ fileInfo.flag = (1 << 11);
+ fileInfo.size = data.length;
+ fileInfo.crc = crcValue;
+ fileInfo.filename = name;
+ fileInfo.fileTime = (short) fileTime;
+ fileInfo.fileDate = (short) fileDate;
+ fileInfo.isZip64 = false;
+
+ return fileInfo;
+ }
+
+
+ private void writeEndOfCentralDirectory(boolean hasZip64Entry, long numEntries, long startOfCentralDirectory, long sizeOfCentralDirectory, CountingOutputStream out) throws IOException {
+ var isZip64 = hasZip64Entry
+ || (numEntries & ~0xFF) != 0
+ || (startOfCentralDirectory & ~0xFFFF) != 0
+ || (sizeOfCentralDirectory & ~0xFFFF) != 0;
+
+ if (isZip64) {
+ var endPosition = out.position;
+ writeZip64EndOfCentralDirectory(numEntries, startOfCentralDirectory, sizeOfCentralDirectory, out);
+ writeZip64EndOfCentralDirectoryLocator(endPosition, out);
}
EndOfCDRecord endOfCDRecord = new EndOfCDRecord();
- endOfCDRecord.signature = 0x06054b50;
- endOfCDRecord.diskNumber = 0;
- endOfCDRecord.startDiskNumber = 0;
- endOfCDRecord.numberOfCDRecordEntries = (short) this.fileInfoEntries.size();
- endOfCDRecord.totalCDRecordEntries = (short) this.fileInfoEntries.size();
- endOfCDRecord.centralDirectoryOffset = (int) this.currentOffset;
- endOfCDRecord.sizeOfCentralDirectory = (int) (this.lastOffsetCDFileHeader - this.currentOffset);
- endOfCDRecord.commentLength = 0;
-
- ByteBuffer buffer = ByteBuffer.allocate(22);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putInt(endOfCDRecord.signature);
- buffer.putShort(endOfCDRecord.diskNumber);
- buffer.putShort(endOfCDRecord.startDiskNumber);
- buffer.putShort(endOfCDRecord.numberOfCDRecordEntries);
- buffer.putShort(endOfCDRecord.totalCDRecordEntries);
- buffer.putInt(endOfCDRecord.sizeOfCentralDirectory);
- buffer.putInt(endOfCDRecord.centralDirectoryOffset);
- buffer.putShort(endOfCDRecord.commentLength);
-
- this.writer.write(buffer.array());
+ endOfCDRecord.numberOfCDRecordEntries = isZip64 ? ZIP_64_MAGIC_VAL : (short) numEntries;
+ endOfCDRecord.totalCDRecordEntries = isZip64 ? ZIP_64_MAGIC_VAL : (short) numEntries;
+ endOfCDRecord.centralDirectoryOffset = isZip64 ? ZIP_64_MAGIC_VAL : (int) startOfCentralDirectory;
+ endOfCDRecord.sizeOfCentralDirectory = isZip64 ? ZIP_64_MAGIC_VAL : (int) sizeOfCentralDirectory;
+
+ endOfCDRecord.write(out);
}
- private void writeZip64EndOfCentralDirectory() throws IOException {
+ private void writeZip64EndOfCentralDirectory(long numEntries, long startOfCentralDirectory, long sizeOfCentralDirectory, OutputStream out) throws IOException {
Zip64EndOfCDRecord zip64EndOfCDRecord = new Zip64EndOfCDRecord();
- zip64EndOfCDRecord.signature = 0x06064b50;
- zip64EndOfCDRecord.recordSize = ZIP_64_EXTENDED_INFO_EXTRA_FIELD_SIZE - 12;
- zip64EndOfCDRecord.versionMadeBy = ZIP_VERSION;
- zip64EndOfCDRecord.versionToExtract = ZIP_VERSION;
- zip64EndOfCDRecord.diskNumber = 0;
- zip64EndOfCDRecord.startDiskNumber = 0;
- zip64EndOfCDRecord.numberOfCDRecordEntries = this.fileInfoEntries.size();
- zip64EndOfCDRecord.totalCDRecordEntries = this.fileInfoEntries.size();
- zip64EndOfCDRecord.centralDirectorySize = this.lastOffsetCDFileHeader - this.currentOffset;
- zip64EndOfCDRecord.startingDiskCentralDirectoryOffset = this.currentOffset;
-
- ByteBuffer buffer = ByteBuffer.allocate(56);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putInt(zip64EndOfCDRecord.signature);
- buffer.putLong(zip64EndOfCDRecord.recordSize);
- buffer.putShort(zip64EndOfCDRecord.versionMadeBy);
- buffer.putShort(zip64EndOfCDRecord.versionToExtract);
- buffer.putInt(zip64EndOfCDRecord.diskNumber);
- buffer.putInt(zip64EndOfCDRecord.startDiskNumber);
- buffer.putLong(zip64EndOfCDRecord.numberOfCDRecordEntries);
- buffer.putLong(zip64EndOfCDRecord.totalCDRecordEntries);
- buffer.putLong(zip64EndOfCDRecord.centralDirectorySize);
- buffer.putLong(zip64EndOfCDRecord.startingDiskCentralDirectoryOffset);
-
- this.writer.write(buffer.array());
+ zip64EndOfCDRecord.numberOfCDRecordEntries = numEntries;
+ zip64EndOfCDRecord.totalCDRecordEntries = numEntries;
+ zip64EndOfCDRecord.centralDirectorySize = sizeOfCentralDirectory;
+ zip64EndOfCDRecord.startingDiskCentralDirectoryOffset = startOfCentralDirectory;
+
+ zip64EndOfCDRecord.write(out);
}
- private void writeZip64EndOfCentralDirectoryLocator() throws IOException {
+ private void writeZip64EndOfCentralDirectoryLocator(long startOfEndOfCD, OutputStream out) throws IOException {
Zip64EndOfCDRecordLocator zip64EndOfCDRecordLocator = new Zip64EndOfCDRecordLocator();
- zip64EndOfCDRecordLocator.signature = 0x07064b50;
- zip64EndOfCDRecordLocator.CDStartDiskNumber = 0;
- zip64EndOfCDRecordLocator.CDOffset = this.lastOffsetCDFileHeader;
- zip64EndOfCDRecordLocator.numberOfDisks = 1;
-
- ByteBuffer buffer = ByteBuffer.allocate(20);
- buffer.order(ByteOrder.LITTLE_ENDIAN);
- buffer.putInt(zip64EndOfCDRecordLocator.signature);
- buffer.putInt(zip64EndOfCDRecordLocator.CDStartDiskNumber);
- buffer.putLong(zip64EndOfCDRecordLocator.CDOffset);
- buffer.putInt(zip64EndOfCDRecordLocator.numberOfDisks);
-
- this.writer.write(buffer.array());
+ zip64EndOfCDRecordLocator.CDOffset = startOfEndOfCD;
+
+ zip64EndOfCDRecordLocator.write(out);
+ }
+
+ private static class CountingOutputStream extends OutputStream {
+
+ private final OutputStream inner;
+ private long position;
+
+ public CountingOutputStream(OutputStream inner) {
+ this.inner = inner;
+ this.position = 0;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ inner.write(b);
+ position += 1;
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ inner.write(b);
+ position += b.length;
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ inner.write(b, off, len);
+ position += len;
+ }
}
- private long getTimeDateUnMSDosFormat() {
+ private static long getTimeDateUnMSDosFormat() {
LocalDateTime now = LocalDateTime.now();
int timeInDos = now.getHour() << 11 | now.getMinute() << 5 | Math.max(now.getSecond() / HALF_SECOND, DEFAULT_SECOND_VALUE);
int dateInDos = (now.getYear() - BASE_YEAR) << 9 | ((now.getMonthValue() + 1) << MONTH_SHIFT) | now.getDayOfMonth();
@@ -368,97 +295,238 @@ private long getTimeDateUnMSDosFormat() {
}
private static class LocalFileHeader {
- int signature;
- int version;
+ final int signature = 0x04034b50;
+ final int version = ZIP_VERSION;
int generalPurposeBitFlag;
- int compressionMethod;
+ final int compressionMethod = 0;
int lastModifiedTime;
int lastModifiedDate;
int crc32;
int compressedSize;
int uncompressedSize;
+
short filenameLength;
- short extraFieldLength;
- }
+ short extraFieldLength = 0;
- private static class Zip64ExtendedLocalInfoExtraField {
- short signature;
- short size;
- long originalSize;
- long compressedSize;
+ void write(OutputStream out, byte[] filename) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(30 + filename.length);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(signature);
+ buffer.putShort((short) version);
+ buffer.putShort((short) generalPurposeBitFlag);
+ buffer.putShort((short) compressionMethod);
+ buffer.putShort((short) lastModifiedTime);
+ buffer.putShort((short) lastModifiedDate);
+ buffer.putInt(crc32);
+ buffer.putInt(compressedSize);
+ buffer.putInt(uncompressedSize);
+ buffer.putShort(filenameLength);
+ buffer.putShort(extraFieldLength);
+ buffer.put(filename);
+
+ out.write(buffer.array());
+ assert buffer.position() == buffer.capacity();
+ }
}
private static class Zip64DataDescriptor {
- int signature;
+ final int signature = 0x08074b50;
long crc32;
long compressedSize;
long uncompressedSize;
+
+ void write(OutputStream out) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(ZIP_64_DATA_DESCRIPTOR_SIZE);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(signature);
+ buffer.putInt((int) crc32);
+ buffer.putLong(compressedSize);
+ buffer.putLong(uncompressedSize);
+
+ out.write(buffer.array());
+ assert buffer.position() == buffer.capacity();
+ }
}
private static class Zip32DataDescriptor {
- int signature;
+ final int signature = 0x08074b50;
+ ;
long crc32;
int compressedSize;
int uncompressedSize;
+
+ void write(OutputStream out) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(ZIP_32_DATA_DESCRIPTOR_SIZE);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(signature);
+ buffer.putInt((int) crc32);
+ buffer.putInt(compressedSize);
+ buffer.putInt(uncompressedSize);
+ out.write(buffer.array());
+ assert buffer.position() == buffer.capacity();
+ }
}
private static class CDFileHeader {
- int signature;
- int versionCreated;
- int versionNeeded;
+ final int signature = 0x02014b50;
+ final short versionCreated = ZIP_VERSION;
+ final short versionNeeded = ZIP_VERSION;
int generalPurposeBitFlag;
- int compressionMethod;
+ final int compressionMethod = 0;
int lastModifiedTime;
int lastModifiedDate;
int crc32;
int compressedSize;
int uncompressedSize;
short filenameLength;
- short fileCommentLength;
- short diskNumberStart;
- short internalFileAttributes;
- int externalFileAttributes;
- int localHeaderOffset;
short extraFieldLength;
+ final short fileCommentLength = 0;
+ final short diskNumberStart = 0;
+ final short internalFileAttributes = 0;
+ final int externalFileAttributes = 0;
+ int localHeaderOffset;
+
+ void write(OutputStream out, byte[] filename) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(46 + filename.length);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(signature);
+ buffer.putShort(versionCreated);
+ buffer.putShort(versionNeeded);
+ buffer.putShort((short) generalPurposeBitFlag);
+ buffer.putShort((short) compressionMethod);
+ buffer.putShort((short) lastModifiedTime);
+ buffer.putShort((short) lastModifiedDate);
+ buffer.putInt(crc32);
+ buffer.putInt(compressedSize);
+ buffer.putInt(uncompressedSize);
+ buffer.putShort((short) filename.length);
+ buffer.putShort(extraFieldLength);
+ buffer.putShort(fileCommentLength);
+ buffer.putShort(diskNumberStart);
+ buffer.putShort(internalFileAttributes);
+ buffer.putInt(externalFileAttributes);
+ buffer.putInt(localHeaderOffset);
+ buffer.put(filename);
+
+ out.write(buffer.array());
+ assert buffer.position() == buffer.capacity();
+ }
}
- private static class Zip64ExtendedInfoExtraField {
- short signature;
- short size;
+ private static class Zip64LocalExtendedInfoExtraField {
+ final short signature = 0x0001;
+ final short size = ZIP_64_LOCAL_EXTENDED_INFO_EXTRA_FIELD_SIZE;
+ long originalSize;
+ long compressedSize;
+
+ void write(OutputStream out) throws IOException {
+ var buffer = ByteBuffer.allocate(ZIP_64_LOCAL_EXTENDED_INFO_EXTRA_FIELD_SIZE);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putShort(signature);
+ buffer.putShort(size);
+ buffer.putLong(originalSize);
+ buffer.putLong(compressedSize);
+
+ out.write(buffer.array());
+ assert buffer.position() == buffer.capacity();
+ }
+ }
+
+ private static class Zip64GlobalExtendedInfoExtraField {
+ final short signature = 0x0001;
+ final short size = ZIP_64_GLOBAL_EXTENDED_INFO_EXTRA_FIELD_SIZE - 4;
long originalSize;
long compressedSize;
long localFileHeaderOffset;
+
+ void write(OutputStream out) throws IOException {
+ var buffer = ByteBuffer.allocate(ZIP_64_GLOBAL_EXTENDED_INFO_EXTRA_FIELD_SIZE);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putShort(signature);
+ buffer.putShort(size);
+ buffer.putLong(compressedSize);
+ buffer.putLong(originalSize);
+ buffer.putLong(localFileHeaderOffset);
+
+ out.write(buffer.array());
+ assert buffer.position() == buffer.capacity();
+ }
}
private static class EndOfCDRecord {
- int signature;
- short diskNumber;
- short startDiskNumber;
+ final int signature = 0x06054b50;
+ final short diskNumber = 0;
+ final short startDiskNumber = 0;
short numberOfCDRecordEntries;
short totalCDRecordEntries;
int sizeOfCentralDirectory;
int centralDirectoryOffset;
- short commentLength;
+ final short commentLength = 0;
+
+ void write(OutputStream out) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(22);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(signature);
+ buffer.putShort(diskNumber);
+ buffer.putShort(startDiskNumber);
+ buffer.putShort(numberOfCDRecordEntries);
+ buffer.putShort(totalCDRecordEntries);
+ buffer.putInt(sizeOfCentralDirectory);
+ buffer.putInt(centralDirectoryOffset);
+ buffer.putShort(commentLength);
+
+ out.write(buffer.array());
+ assert buffer.position() == buffer.capacity();
+ }
}
private static class Zip64EndOfCDRecord {
- int signature;
- long recordSize;
- short versionMadeBy;
- short versionToExtract;
- int diskNumber;
- int startDiskNumber;
+ final int signature = 0x06064b50;
+ final long recordSize = ZIP_64_END_OF_CD_RECORD_SIZE - 12;
+ final short versionMadeBy = ZIP_VERSION;
+ final short versionToExtract = ZIP_VERSION;
+ final int diskNumber = 0;
+ final int startDiskNumber = 0;
long numberOfCDRecordEntries;
long totalCDRecordEntries;
long centralDirectorySize;
long startingDiskCentralDirectoryOffset;
+
+ void write(OutputStream out) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(56);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(signature);
+ buffer.putLong(recordSize);
+ buffer.putShort(versionMadeBy);
+ buffer.putShort(versionToExtract);
+ buffer.putInt(diskNumber);
+ buffer.putInt(startDiskNumber);
+ buffer.putLong(numberOfCDRecordEntries);
+ buffer.putLong(totalCDRecordEntries);
+ buffer.putLong(centralDirectorySize);
+ buffer.putLong(startingDiskCentralDirectoryOffset);
+
+ out.write(buffer.array());
+ }
}
+
private static class Zip64EndOfCDRecordLocator {
- int signature;
- int CDStartDiskNumber;
+ final int signature = 0x07064b50;
+ final int CDStartDiskNumber = 0;
long CDOffset;
- int numberOfDisks;
+ final int numberOfDisks = 1;
+
+ void write(OutputStream out) throws IOException {
+ ByteBuffer buffer = ByteBuffer.allocate(20);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ buffer.putInt(signature);
+ buffer.putInt(CDStartDiskNumber);
+ buffer.putLong(CDOffset);
+ buffer.putInt(numberOfDisks);
+ out.write(buffer.array());
+ assert buffer.position() == buffer.capacity();
+ }
}
private static class FileInfo {
@@ -469,5 +537,6 @@ private static class FileInfo {
short fileTime;
short fileDate;
short flag;
+ boolean isZip64;
}
}
\ No newline at end of file
diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java
index 83ae6add..5d5311fa 100644
--- a/sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java
+++ b/sdk/src/test/java/io/opentdf/platform/sdk/ZipReaderTest.java
@@ -1,63 +1,137 @@
package io.opentdf.platform.sdk;
-import org.junit.jupiter.api.BeforeEach;
+import com.google.gson.Gson;
+import org.apache.commons.compress.archivers.zip.Zip64Mode;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;
+import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
import org.junit.jupiter.api.Test;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.io.RandomAccessFile;
-import java.nio.ByteBuffer;
-import java.nio.channels.FileChannel;
-import java.nio.file.Files;
-import java.nio.file.Paths;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class ZipReaderTest {
- private ZipReader zipReader;
- private ByteBuffer buffer;
- private RandomAccessFile raf;
- private FileChannel fileChannel;
+ @Test
+ public void testReadingExistingZip() throws Exception {
+ try (RandomAccessFile raf = new RandomAccessFile("src/test/resources/sample.txt.tdf", "r")) {
+ var fileChannel = raf.getChannel();
+ var zipReader = new ZipReader(fileChannel);
+ var entries = zipReader.getEntries();
+ assertThat(entries.size()).isEqualTo(2);
+ for (var entry: entries) {
+ var stream = new ByteArrayOutputStream();
+ if (entry.getName().endsWith(".json")) {
+ entry.getData().transferTo(stream);
+ var data = stream.toString(StandardCharsets.UTF_8);
+ var map = new Gson().fromJson(data, Map.class);
+ assertThat(map.get("encryptionInformation")).isNotNull();
+ }
+ }
+ }
+ }
@Test
- public void testZipReader() throws Exception {
- RandomAccessFile raf = new RandomAccessFile("src/test/resources/sample.txt.tdf", "r");
- FileChannel fileChannel = raf.getChannel();
- int bufferSize = 1024;
- long fileSize = fileChannel.size();
- long position = fileSize - bufferSize;
- if (position < 0) {
- position = fileSize;
+ public void testReadingAFileWrittenUsingCommons() throws IOException {
+ SeekableInMemoryByteChannel outputChannel = new SeekableInMemoryByteChannel();
+ ZipArchiveOutputStream zip = new ZipArchiveOutputStream(outputChannel);
+ zip.setUseZip64(Zip64Mode.Always);
+ ZipArchiveEntry entry1 = new ZipArchiveEntry("the first entry");
+ entry1.setMethod(0);
+ zip.putArchiveEntry(entry1);
+ new ByteArrayInputStream("this is the first entry contents".getBytes(StandardCharsets.UTF_8)).transferTo(zip);
+ zip.closeArchiveEntry();
+ ZipArchiveEntry entry2 = new ZipArchiveEntry("the second entry");
+ entry2.setMethod(0);
+ zip.putArchiveEntry(entry2);
+ new ByteArrayInputStream("this is the second entry contents".getBytes(StandardCharsets.UTF_8)).transferTo(zip);
+ zip.closeArchiveEntry();
+ zip.close();
+
+ SeekableInMemoryByteChannel inputChannel = new SeekableInMemoryByteChannel(outputChannel.array());
+
+ var reader = new ZipReader(inputChannel);
+
+ for (ZipReader.Entry entry: reader.getEntries()) {
+ try (var data = entry.getData()) {
+ var bytes = new ByteArrayOutputStream();
+ data.transferTo(bytes);
+
+ var stringData = bytes.toString(StandardCharsets.UTF_8);
+ if (entry.getName().equals("the first entry")) {
+ assertThat(stringData).isEqualTo("this is the first entry contents");
+ } else {
+ assertThat(entry.getName()).isEqualTo("the second entry");
+ assertThat(stringData).isEqualTo("this is the second entry contents");
+ }
+ }
}
+ }
- ByteBuffer buffer = ByteBuffer.allocate((int)bufferSize);
- fileChannel.position(position);
- fileChannel.read(buffer);
- buffer.flip();
-
- ZipReader zipReader = new ZipReader();
- zipReader.readEndOfCentralDirectory(buffer);
- buffer.clear();
- long centralDirectoryOffset = zipReader.getCDOffset();
- int numEntries = zipReader.getNumEntries();
- for (int i = 0; i < numEntries; i++) {
- fileChannel.position(centralDirectoryOffset);
- fileChannel.read(buffer);
- buffer.flip();
- long offset = zipReader.readCentralDirectoryFileHeader(buffer);
- buffer.clear();
- fileChannel.position(offset);
- fileChannel.read(buffer);
- buffer.flip();
- zipReader.readLocalFileHeader(buffer);
- centralDirectoryOffset += 46 + zipReader.getFileNameLength() + zipReader.getExtraFieldLength();
- buffer.clear();
+ @Test
+ public void testReadingAndWritingRandomFiles() throws IOException {
+ Random r = new Random();
+ int numEntries = r.nextInt(500) + 10;
+ var testData = IntStream.range(0, numEntries)
+ .mapToObj(ignored -> {
+ int fileNameLength = r.nextInt(1000);
+ String name = IntStream.range(0, fileNameLength)
+ .mapToObj(idx -> {
+ var chars = "abcdefghijklmnopqrstuvwxyz ≈ç´ƒ∆∂߃åˆß∂øƒ¨åß∂∆˚¬…∆˚¬ˆøπ¨πøƒ∂åß˚¬…∆¬…ˆøåπƒ∆";
+ var randIdx = r.nextInt(chars.length());
+ return chars.substring(randIdx, randIdx + 1);
+ })
+ .collect(Collectors.joining());
+ int fileSize = r.nextInt(3000);
+ byte[] fileContent = new byte[fileSize];
+ r.nextBytes(fileContent);
+
+ return new Object[] {name, fileContent};
+ }).collect(Collectors.toList());
+
+ ZipWriter writer = new ZipWriter();
+ HashMap namesToData = new HashMap<>();
+ for (var data: testData) {
+ var fileName = (String)data[0];
+ var content = (byte[])data[1];
+
+ if (namesToData.containsKey(fileName)) {
+ continue;
+ }
+
+ namesToData.put(fileName, content);
+
+ if (r.nextBoolean()) {
+ writer = writer.file(fileName, content);
+ } else {
+ writer = writer.file(fileName, new ByteArrayInputStream(content));
+ }
}
- assertEquals(2, zipReader.getNumEntries());
- assertNotNull(zipReader.getFileNameLength());
- assertNotNull(zipReader.getCDOffset());
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ writer.build(out);
+
+ var channel = new SeekableInMemoryByteChannel(out.toByteArray());
- raf.close();
+ ZipReader reader = new ZipReader(channel);
+
+ for (var entry: reader.getEntries()) {
+ assertThat(namesToData).containsKey(entry.getName());
+ var zipData = new ByteArrayOutputStream();
+ entry.getData().transferTo(zipData);
+ assertThat(zipData.toByteArray()).isEqualTo(namesToData.get(entry.getName()));
+ }
}
}
\ No newline at end of file
diff --git a/sdk/src/test/java/io/opentdf/platform/sdk/ZipWriterTest.java b/sdk/src/test/java/io/opentdf/platform/sdk/ZipWriterTest.java
index 62010e6b..53cf0fdd 100644
--- a/sdk/src/test/java/io/opentdf/platform/sdk/ZipWriterTest.java
+++ b/sdk/src/test/java/io/opentdf/platform/sdk/ZipWriterTest.java
@@ -1,46 +1,157 @@
package io.opentdf.platform.sdk;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+import org.apache.commons.compress.utils.SeekableInMemoryByteChannel;
+import org.jetbrains.annotations.NotNull;
+import org.junit.Ignore;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.nio.channels.FileChannel;
+import java.nio.channels.SeekableByteChannel;
import java.nio.charset.StandardCharsets;
+import java.nio.file.StandardOpenOption;
+import java.util.Random;
+import java.util.zip.CRC32;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
public class ZipWriterTest {
@Test
public void writesMultipleFilesToArchive() throws IOException {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- ZipWriter archiveWriter = new ZipWriter(outputStream);
-
- String filename1 = "file1.txt";
- String content1 = "Hello, world!";
- archiveWriter.addHeader(filename1, content1.getBytes(StandardCharsets.UTF_8).length);
- archiveWriter.addData(content1.getBytes(StandardCharsets.UTF_8));
- archiveWriter.finish();
-
- String filename2 = "file2.txt";
- String content2 = "This is another file.";
- archiveWriter.addHeader(filename2, content2.getBytes(StandardCharsets.UTF_8).length);
- archiveWriter.addData(content2.getBytes(StandardCharsets.UTF_8));
- archiveWriter.finish();
-
- byte[] zipData = outputStream.toByteArray();
- assertTrue(zipData.length > 0);
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new ZipWriter()
+ .file("file1∞®ƒ両†.txt", "Hello world!".getBytes(StandardCharsets.UTF_8))
+ .file("file2.txt", "Here are some more things to look at".getBytes(StandardCharsets.UTF_8))
+ .file("the streaming one", new ByteArrayInputStream("this is a long long stream".getBytes(StandardCharsets.UTF_8)))
+ .build(out);
+
+
+ SeekableByteChannel chan = new SeekableInMemoryByteChannel(out.toByteArray());
+ ZipFile z = new ZipFile.Builder().setSeekableByteChannel(chan).get();
+ var entry1 = z.getEntry("file1∞®ƒ両†.txt");
+ assertThat(entry1).isNotNull();
+ var entry1Data = getDataStream(z, entry1);
+ assertThat(entry1Data.toString(StandardCharsets.UTF_8)).isEqualTo("Hello world!");
+
+ var entry2 = z.getEntry("file2.txt");
+ assertThat(entry1).isNotNull();
+ assertThat(getDataStream(z, entry2).toString(StandardCharsets.UTF_8)).isEqualTo("Here are some more things to look at");
+
+ var entry3 = z.getEntry("the streaming one");
+ assertThat(entry3).isNotNull();
+ assertThat(getDataStream(z, entry3).toString(StandardCharsets.UTF_8)).isEqualTo("this is a long long stream");
+ }
+ @Test
+ public void createsNonZip64Archive() throws IOException {
+ // when we create things using only byte arrays we create an archive that is non zip64
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ new ZipWriter()
+ .file("file1∞®ƒ両†.txt", "Hello world!".getBytes(StandardCharsets.UTF_8))
+ .file("file2.txt", "Here are some more things to look at".getBytes(StandardCharsets.UTF_8))
+ .build(out);
+
+ SeekableByteChannel chan = new SeekableInMemoryByteChannel(out.toByteArray());
+ ZipFile z = new ZipFile.Builder().setSeekableByteChannel(chan).get();
+ var entry1 = z.getEntry("file1∞®ƒ両†.txt");
+ assertThat(entry1).isNotNull();
+ var entry1Data = getDataStream(z, entry1);
+ assertThat(entry1Data.toString(StandardCharsets.UTF_8)).isEqualTo("Hello world!");
+
+ var entry2 = z.getEntry("file2.txt");
+ assertThat(entry1).isNotNull();
+ assertThat(getDataStream(z, entry2).toString(StandardCharsets.UTF_8)).isEqualTo("Here are some more things to look at");
}
@Test
- public void throwsExceptionForEmptyFilename() {
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- ZipWriter archiveWriter = new ZipWriter(outputStream);
+ @Disabled("this takes a long time and shouldn't run on build machines")
+ public void testWritingLargeFile() throws IOException {
+ var random = new Random();
+ long fileSize = 4096 + random.nextInt(4096);
+ var testFile = File.createTempFile("big-file", "");
+ testFile.deleteOnExit();
+ try (var out = new FileOutputStream(testFile)) {
+ var buf = new byte[2048];
+ for (long i = 0; i < fileSize * 1024 * 1024; i += buf.length) {
+ random.nextBytes(buf);
+ out.write(buf);
+ }
+ }
- String filename = "";
- String content = "Hello, world!";
+ var zipFile = File.createTempFile("zip-file", "zip");
+ zipFile.deleteOnExit();
+ try (var in = new FileInputStream(testFile)) {
+ try (var out = new FileOutputStream(zipFile)) {
+ new ZipWriter().file("a big one", in).build(out);
+ }
+ }
+
+ var unzippedData = File.createTempFile("big-file-unzipped", "");
+ unzippedData.deleteOnExit();
+ try (var unzippedStream = new FileOutputStream(unzippedData)) {
+ try (var chan = FileChannel.open(zipFile.toPath(), StandardOpenOption.READ)) {
+ ZipFile z = new ZipFile.Builder().setSeekableByteChannel(chan).get();
+ var entry = z.getEntry("a big one");
+ z.getInputStream(entry).transferTo(unzippedStream);
+ }
+ }
+
+ assertThat(unzippedData.length())
+ .withFailMessage("extracted file was of the wrong length")
+ .isEqualTo(testFile.length());
+
+
+ var buf = new byte[2048];
+ var unzippedCRC = new CRC32();
+ try (var inputStream = new FileInputStream(unzippedData)) {
+ var read = inputStream.read(buf);
+ unzippedCRC.update(buf, 0, read);
+ }
+ unzippedData.delete();
+
+ var testFileCRC = new CRC32();
+ try (var inputStream = new FileInputStream(testFile)) {
+ var read = inputStream.read(buf);
+ testFileCRC.update(buf, 0, read);
+ }
+ testFile.delete();
+
+ assertThat(unzippedCRC.getValue())
+ .withFailMessage("the extracted file's CRC differs from the CRC of the test data")
+ .isEqualTo(testFileCRC.getValue());
+
+ var ourUnzippedData = File.createTempFile("big-file-we-unzipped", "");
+ ourUnzippedData.deleteOnExit();
+ try (var unzippedStream = new FileOutputStream(ourUnzippedData)) {
+ try (var chan = FileChannel.open(zipFile.toPath(), StandardOpenOption.READ)) {
+ ZipReader reader = new ZipReader(chan);
+ assertThat(reader.getEntries().size()).isEqualTo(1);
+ reader.getEntries().get(0).getData().transferTo(unzippedStream);
+ }
+ }
+
+ var ourTestFileCRC = new CRC32();
+ try (var inputStream = new FileInputStream(ourUnzippedData)) {
+ var read = inputStream.read(buf);
+ ourTestFileCRC.update(buf, 0, read);
+ }
+
+ assertThat(ourTestFileCRC.getValue())
+ .withFailMessage("the file we extracted differs from the CRC of the test data")
+ .isEqualTo(testFileCRC.getValue());
+ }
- assertThrows(IllegalArgumentException.class, () -> {
- archiveWriter.addHeader(filename, content.getBytes(StandardCharsets.UTF_8).length);
- });
+ @NotNull
+ private static ByteArrayOutputStream getDataStream(ZipFile z, ZipArchiveEntry entry) throws IOException {
+ var entry1Data = new ByteArrayOutputStream();
+ z.getInputStream(entry).transferTo(entry1Data);
+ return entry1Data;
}
}
\ No newline at end of file