diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..b6d0ae5
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,34 @@
+# Auto detect text files and perform LF normalization
+* text=auto
+
+# Java sources
+*.java text diff=java eol=lf
+*.gradle text diff=java eol=lf
+
+# These files are text and should be normalized (Convert crlf => lf)
+*.css text diff=css eol=lf
+*.html text diff=html eol=lf
+*.md text diff=markdown eol=lf
+*.js text eol=lf
+*.csv text eol=lf
+*.json text eol=lf
+*.properties text eol=lf
+*.svg text eol=lf
+*.xml text eol=lf
+*.yaml text eol=lf
+*.yml text eol=lf
+*.toml text eol=lf
+*.lang text eol=lf
+
+# These files are binary and should be left untouched
+*.png binary
+*.gif binary
+*.jpg binary
+*.jpeg binary
+
+# Common build-tool wrapper scripts
+mvnw text eol=lf
+gradlew text eol=lf
+*.sh text eol=lf
+*.bat text eol=crlf
+*.cmd text eol=crlf
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 5bd2284..bc8c5d3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
@@ -41,16 +41,13 @@ public class CentralDirectoryFileHeader extends AbstractZipFileHeader { private transient LocalFileHeader linkedFileHeader; // CentralDirectoryFileHeader spec (plus common elements between this and local file) - private LazyInt versionMadeBy; - private LazyInt fileCommentLength; - private LazyMemorySegment fileComment; - private LazyInt diskNumberStart; - private LazyInt internalFileAttributes; - private LazyInt externalFileAttributes; - private LazyLong relativeOffsetOfLocalHeader; - - // String cache values - private transient String fileCommentCache; + private int versionMadeBy; + private int fileCommentLength; + private StringData fileComment; + private int diskNumberStart; + private int internalFileAttributes; + private int externalFileAttributes; + private long relativeOffsetOfLocalHeader; /** * @return Copy. @@ -61,22 +58,22 @@ public CentralDirectoryFileHeader copy() { copy.data = data; copy.offset = offset; copy.linkedFileHeader = linkedFileHeader; - copy.versionMadeBy = versionMadeBy.copy(); - copy.versionNeededToExtract = versionNeededToExtract.copy(); - copy.generalPurposeBitFlag = generalPurposeBitFlag.copy(); - copy.compressionMethod = compressionMethod.copy(); - copy.lastModFileTime = lastModFileTime.copy(); - copy.lastModFileDate = lastModFileDate.copy(); - copy.crc32 = crc32.copy(); - copy.compressedSize = compressedSize.copy(); - copy.uncompressedSize = uncompressedSize.copy(); - copy.fileNameLength = fileNameLength.copy(); - copy.extraFieldLength = extraFieldLength.copy(); - copy.fileCommentLength = fileCommentLength.copy(); - copy.diskNumberStart = diskNumberStart.copy(); - copy.internalFileAttributes = internalFileAttributes.copy(); - copy.externalFileAttributes = externalFileAttributes.copy(); - copy.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader.copy(); + copy.versionMadeBy = versionMadeBy; + copy.versionNeededToExtract = versionNeededToExtract; + copy.generalPurposeBitFlag = generalPurposeBitFlag; + copy.compressionMethod = compressionMethod; + copy.lastModFileTime = lastModFileTime; + copy.lastModFileDate = lastModFileDate; + copy.crc32 = crc32; + copy.compressedSize = compressedSize; + copy.uncompressedSize = uncompressedSize; + copy.fileNameLength = fileNameLength; + copy.extraFieldLength = extraFieldLength; + copy.fileCommentLength = fileCommentLength; + copy.diskNumberStart = diskNumberStart; + copy.internalFileAttributes = internalFileAttributes; + copy.externalFileAttributes = externalFileAttributes; + copy.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader; copy.fileName = fileName.copy(); copy.extraField = extraField.copy(); copy.fileComment = fileComment.copy(); @@ -84,35 +81,57 @@ public CentralDirectoryFileHeader copy() { } @Override - public void read(@Nonnull MemorySegment data, long offset) { + public void read(@Nonnull MemorySegment data, long offset) throws ZipParseException { super.read(data, offset); - versionMadeBy = MemorySegmentUtil.readLazyWord(data, offset, 4).withId("versionMadeBy"); - versionNeededToExtract = MemorySegmentUtil.readLazyWord(data, offset, 6).withId("versionNeededToExtract"); - generalPurposeBitFlag = MemorySegmentUtil.readLazyWord(data, offset, 8).withId("generalPurposeBitFlag"); - compressionMethod = MemorySegmentUtil.readLazyWord(data, offset, 10).withId("compressionMethod"); - lastModFileTime = MemorySegmentUtil.readLazyWord(data, offset, 12).withId("lastModFileTime"); - lastModFileDate = MemorySegmentUtil.readLazyWord(data, offset, 14).withId("lastModFileDate"); - crc32 = MemorySegmentUtil.readLazyQuad(data, offset, 16).withId("crc32"); - compressedSize = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 20).withId("compressedSize"); - uncompressedSize = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 24).withId("uncompressedSize"); - fileNameLength = MemorySegmentUtil.readLazyWord(data, offset, 28).withId("fileNameLength"); - extraFieldLength = MemorySegmentUtil.readLazyWord(data, offset, 30).withId("extraFieldLength"); - fileCommentLength = MemorySegmentUtil.readLazyWord(data, offset, 32).withId("fileCommentLength"); - diskNumberStart = MemorySegmentUtil.readLazyWord(data, offset, 34).withId("diskNumberStart"); - internalFileAttributes = MemorySegmentUtil.readLazyWord(data, offset, 36).withId("internalFileAttributes"); - externalFileAttributes = MemorySegmentUtil.readLazyQuad(data, offset, 38).withId("externalFileAttributes"); - relativeOffsetOfLocalHeader = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 42).withId("relativeOffsetOfLocalHeader"); - fileName = MemorySegmentUtil.readLazySlice(data, offset, new LazyInt(() -> 46), fileNameLength).withId("fileName"); - extraField = MemorySegmentUtil.readLazySlice(data, offset, fileNameLength.add(46), extraFieldLength).withId("extraField"); - fileComment = MemorySegmentUtil.readLazySlice(data, offset, fileNameLength.add(46).add(extraFieldLength), fileCommentLength).withId("fileComment"); + try { + versionMadeBy = readWord(data, offset, 4); + versionNeededToExtract = readWord(data, offset, 6); + generalPurposeBitFlag = readWord(data, offset, 8); + compressionMethod = readWord(data, offset, 10); + lastModFileTime = readWord(data, offset, 12); + lastModFileDate = readWord(data, offset, 14); + crc32 = readQuad(data, offset, 16); + compressedSize = readMaskedLongQuad(data, offset, 20); + uncompressedSize = readMaskedLongQuad(data, offset, 24); + fileNameLength = readWord(data, offset, 28); + extraFieldLength = readWord(data, offset, 30); + fileCommentLength = readWord(data, offset, 32); + diskNumberStart = readWord(data, offset, 34); + internalFileAttributes = readWord(data, offset, 36); + externalFileAttributes = readQuad(data, offset, 38); + relativeOffsetOfLocalHeader = readMaskedLongQuad(data, offset, 42); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } + try { + fileName = StringData.of(readSlice(data, offset, 46, fileNameLength)); + } catch (IndexOutOfBoundsException ex) { + throw new ZipParseException(ex, ZipParseException.Type.IOOBE_FILE_NAME); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } + try { + extraField = MemorySegmentData.of(data, offset + 46 + fileNameLength, extraFieldLength); + } catch (IndexOutOfBoundsException ex) { + throw new ZipParseException(ex, ZipParseException.Type.IOOBE_FILE_EXTRA); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } + try { + fileComment = StringData.of(readSlice(data, offset, 46 + fileNameLength + extraFieldLength, fileCommentLength)); + } catch (IndexOutOfBoundsException ex) { + throw new ZipParseException(ex, ZipParseException.Type.IOOBE_CEN_COMMENT); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } } @Override public long length() { return MIN_FIXED_SIZE + - fileNameLength.get() + - extraFieldLength.get() + - fileCommentLength.get(); + fileNameLength + + extraFieldLength + + fileCommentLength; } @Nonnull @@ -139,7 +158,7 @@ public int getFileNameLength() { * @return File name. */ @Override - public MemorySegment getFileName() { + public StringData getFileName() { return super.getFileName(); } @@ -173,7 +192,7 @@ public void link(LocalFileHeader header) { * @return Version of zip software used to make the archive. */ public int getVersionMadeBy() { - return versionMadeBy.get(); + return versionMadeBy; } /** @@ -181,14 +200,14 @@ public int getVersionMadeBy() { * Version of zip software used to make the archive. */ public void setVersionMadeBy(int versionMadeBy) { - this.versionMadeBy.set(versionMadeBy); + this.versionMadeBy = versionMadeBy; } /** * @return Disk number where the archive starts from, or {@code 0xFFFF} for ZIP64. */ public int getDiskNumberStart() { - return diskNumberStart.get(); + return diskNumberStart; } /** @@ -196,7 +215,7 @@ public int getDiskNumberStart() { * Disk number where the archive starts from, or {@code 0xFFFF} for ZIP64. */ public void setDiskNumberStart(int diskNumberStart) { - this.diskNumberStart.set(diskNumberStart); + this.diskNumberStart = diskNumberStart; } /** @@ -211,7 +230,7 @@ public void setDiskNumberStart(int diskNumberStart) { * @return Internal attributes used for inferring content type. */ public int getInternalFileAttributes() { - return internalFileAttributes.get(); + return internalFileAttributes; } /** @@ -219,7 +238,7 @@ public int getInternalFileAttributes() { * Internal attributes used for inferring content type. */ public void setInternalFileAttributes(int internalFileAttributes) { - this.internalFileAttributes.set(internalFileAttributes); + this.internalFileAttributes = internalFileAttributes; } /** @@ -229,7 +248,7 @@ public void setInternalFileAttributes(int internalFileAttributes) { * @return Host system dependent attributes. */ public int getExternalFileAttributes() { - return externalFileAttributes.get(); + return externalFileAttributes; } /** @@ -237,7 +256,7 @@ public int getExternalFileAttributes() { * Host system dependent attributes. */ public void setExternalFileAttributes(int externalFileAttributes) { - this.externalFileAttributes.set(externalFileAttributes); + this.externalFileAttributes = externalFileAttributes; } /** @@ -245,7 +264,7 @@ public void setExternalFileAttributes(int externalFileAttributes) { * This should also be where the {@link LocalFileHeader} is located. Or {@code 0xFFFFFFFF} for ZIP64. */ public long getRelativeOffsetOfLocalHeader() { - return relativeOffsetOfLocalHeader.get(); + return relativeOffsetOfLocalHeader; } /** @@ -254,14 +273,14 @@ public long getRelativeOffsetOfLocalHeader() { * This should also be where the {@link LocalFileHeader} is located. Or {@code 0xFFFFFFFF} for ZIP64. */ public void setRelativeOffsetOfLocalHeader(long relativeOffsetOfLocalHeader) { - this.relativeOffsetOfLocalHeader.set(relativeOffsetOfLocalHeader & 0xFFFFFFFFL); + this.relativeOffsetOfLocalHeader = relativeOffsetOfLocalHeader & 0xFFFFFFFFL; } /** * @return Length of {@link #getFileComment()}. */ public int getFileCommentLength() { - return fileCommentLength.get(); + return fileCommentLength; } @@ -270,33 +289,29 @@ public int getFileCommentLength() { * Length of {@link #getFileComment()}. */ public void setFileCommentLength(int fileCommentLength) { - this.fileCommentLength.set(fileCommentLength & 0xFFFF); + this.fileCommentLength = fileCommentLength & 0xFFFF; } /** * @return File comment. */ - public MemorySegment getFileComment() { - return fileComment.get(); + public StringData getFileComment() { + return fileComment; } /** * @param fileComment * File comment. */ - public void setFileComment(MemorySegment fileComment) { - this.fileComment.set(fileComment); + public void setFileComment(StringData fileComment) { + this.fileComment = fileComment; } /** * @return File comment. */ public String getFileCommentAsString() { - String fileCommentCache = this.fileCommentCache; - if (fileCommentCache == null) { - return this.fileCommentCache = MemorySegmentUtil.toString(fileComment.get()); - } - return fileCommentCache; + return fileComment.get(); } @Override @@ -327,54 +342,30 @@ public String toString() { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - CentralDirectoryFileHeader that = (CentralDirectoryFileHeader) o; - + if (!(o instanceof CentralDirectoryFileHeader that)) return false; + if (!super.equals(o)) return false; + + if (versionMadeBy != that.versionMadeBy) return false; + if (fileCommentLength != that.fileCommentLength) return false; + if (diskNumberStart != that.diskNumberStart) return false; + if (internalFileAttributes != that.internalFileAttributes) return false; + if (externalFileAttributes != that.externalFileAttributes) return false; + if (relativeOffsetOfLocalHeader != that.relativeOffsetOfLocalHeader) return false; if (!Objects.equals(linkedFileHeader, that.linkedFileHeader)) return false; - if (!Objects.equals(versionMadeBy, that.versionMadeBy)) return false; - if (!Objects.equals(versionNeededToExtract, that.versionNeededToExtract)) return false; - if (!Objects.equals(generalPurposeBitFlag, that.generalPurposeBitFlag)) return false; - if (!Objects.equals(compressionMethod, that.compressionMethod)) return false; - if (!Objects.equals(lastModFileTime, that.lastModFileTime)) return false; - if (!Objects.equals(lastModFileDate, that.lastModFileDate)) return false; - if (!Objects.equals(crc32, that.crc32)) return false; - if (!Objects.equals(compressedSize, that.compressedSize)) return false; - if (!Objects.equals(uncompressedSize, that.uncompressedSize)) return false; - if (!Objects.equals(fileNameLength, that.fileNameLength)) return false; - if (!Objects.equals(extraFieldLength, that.extraFieldLength)) return false; - if (!Objects.equals(fileCommentLength, that.fileCommentLength)) return false; - if (!Objects.equals(diskNumberStart, that.diskNumberStart)) return false; - if (!Objects.equals(internalFileAttributes, that.internalFileAttributes)) return false; - if (!Objects.equals(externalFileAttributes, that.externalFileAttributes)) return false; - if (!Objects.equals(relativeOffsetOfLocalHeader, that.relativeOffsetOfLocalHeader)) return false; - if (!Objects.equals(fileName, that.fileName)) return false; - if (!Objects.equals(extraField, that.extraField)) return false; return Objects.equals(fileComment, that.fileComment); } @Override public int hashCode() { - int result = linkedFileHeader != null ? linkedFileHeader.hashCode() : 0; - result = 31 * result + (versionMadeBy != null ? versionMadeBy.hashCode() : 0); - result = 31 * result + (versionNeededToExtract != null ? versionNeededToExtract.hashCode() : 0); - result = 31 * result + (generalPurposeBitFlag != null ? generalPurposeBitFlag.hashCode() : 0); - result = 31 * result + (compressionMethod != null ? compressionMethod.hashCode() : 0); - result = 31 * result + (lastModFileTime != null ? lastModFileTime.hashCode() : 0); - result = 31 * result + (lastModFileDate != null ? lastModFileDate.hashCode() : 0); - result = 31 * result + (crc32 != null ? crc32.hashCode() : 0); - result = 31 * result + (compressedSize != null ? compressedSize.hashCode() : 0); - result = 31 * result + (uncompressedSize != null ? uncompressedSize.hashCode() : 0); - result = 31 * result + (fileNameLength != null ? fileNameLength.hashCode() : 0); - result = 31 * result + (extraFieldLength != null ? extraFieldLength.hashCode() : 0); - result = 31 * result + (fileCommentLength != null ? fileCommentLength.hashCode() : 0); - result = 31 * result + (diskNumberStart != null ? diskNumberStart.hashCode() : 0); - result = 31 * result + (internalFileAttributes != null ? internalFileAttributes.hashCode() : 0); - result = 31 * result + (externalFileAttributes != null ? externalFileAttributes.hashCode() : 0); - result = 31 * result + (relativeOffsetOfLocalHeader != null ? relativeOffsetOfLocalHeader.hashCode() : 0); - result = 31 * result + (fileName != null ? fileName.hashCode() : 0); - result = 31 * result + (extraField != null ? extraField.hashCode() : 0); + int result = super.hashCode(); + result = 31 * result + (linkedFileHeader != null ? linkedFileHeader.hashCode() : 0); + result = 31 * result + versionMadeBy; + result = 31 * result + fileCommentLength; result = 31 * result + (fileComment != null ? fileComment.hashCode() : 0); + result = 31 * result + diskNumberStart; + result = 31 * result + internalFileAttributes; + result = 31 * result + externalFileAttributes; + result = 31 * result + (int) (relativeOffsetOfLocalHeader ^ (relativeOffsetOfLocalHeader >>> 32)); return result; } } diff --git a/src/main/java/software/coley/lljzip/format/model/EndOfCentralDirectory.java b/src/main/java/software/coley/lljzip/format/model/EndOfCentralDirectory.java index db3069a..420e661 100644 --- a/src/main/java/software/coley/lljzip/format/model/EndOfCentralDirectory.java +++ b/src/main/java/software/coley/lljzip/format/model/EndOfCentralDirectory.java @@ -1,11 +1,13 @@ package software.coley.lljzip.format.model; -import software.coley.lljzip.util.MemorySegmentUtil; +import software.coley.lljzip.util.data.StringData; import javax.annotation.Nonnull; import java.lang.foreign.MemorySegment; import java.util.Objects; +import static software.coley.lljzip.util.MemorySegmentUtil.readQuad; +import static software.coley.lljzip.util.MemorySegmentUtil.readWord; /** * ZIP EndOfCentralDirectory structure. @@ -35,8 +37,7 @@ public class EndOfCentralDirectory implements ZipPart, ZipRead { private long centralDirectorySize; private long centralDirectoryOffset; private int zipCommentLength; - private MemorySegment zipComment; - private transient String zipCommentCache; + private StringData zipComment; /** * @return Copy. @@ -53,26 +54,25 @@ public EndOfCentralDirectory copy() { copy.centralDirectoryOffset = centralDirectoryOffset; copy.zipCommentLength = zipCommentLength; copy.zipComment = zipComment; - copy.zipCommentCache = zipCommentCache; return copy; } @Override public void read(@Nonnull MemorySegment data, long offset) { this.offset = offset; - diskNumber = MemorySegmentUtil.readWord(data, offset + 4); - centralDirectoryStartDisk = MemorySegmentUtil.readWord(data, offset + 6); - centralDirectoryStartOffset = MemorySegmentUtil.readWord(data, offset + 8); - numEntries = MemorySegmentUtil.readWord(data, offset + 10); - setCentralDirectorySize(MemorySegmentUtil.readQuad(data, offset + 12)); - setCentralDirectoryOffset(MemorySegmentUtil.readQuad(data, offset + 16)); - setZipCommentLength(MemorySegmentUtil.readWord(data, offset + 20)); - zipComment = data.asSlice(offset + 22, zipCommentLength); + diskNumber = readWord(data, offset + 4); + centralDirectoryStartDisk = readWord(data, offset + 6); + centralDirectoryStartOffset = readWord(data, offset + 8); + numEntries = readWord(data, offset + 10); + setCentralDirectorySize(readQuad(data, offset + 12)); + setCentralDirectoryOffset(readQuad(data, offset + 16)); + setZipCommentLength(readWord(data, offset + 20)); + zipComment = StringData.of(data.asSlice(offset + 22, zipCommentLength)); } @Override public long length() { - return 22L + zipComment.byteSize(); + return 22L + zipComment.get().length(); } @Nonnull @@ -196,7 +196,7 @@ public void setZipCommentLength(int zipCommentLength) { /** * @return Optional comment, or empty string. */ - public MemorySegment getZipComment() { + public StringData getZipComment() { return zipComment; } @@ -204,9 +204,7 @@ public MemorySegment getZipComment() { * @param zipComment * Optional comment, or empty string. */ - public void setZipComment(MemorySegment zipComment) { - if (zipComment == null) - zipComment = MemorySegment.ofArray(new byte[0]); + public void setZipComment(StringData zipComment) { this.zipComment = zipComment; } @@ -214,11 +212,7 @@ public void setZipComment(MemorySegment zipComment) { * @return Optional comment, or empty string. */ public String getZipCommentAsString() { - String zipCommentCache = this.zipCommentCache; - if (zipCommentCache == null) { - return this.zipCommentCache = MemorySegmentUtil.toString(zipComment); - } - return zipCommentCache; + return zipComment.get(); } @Override @@ -239,21 +233,30 @@ public String toString() { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - EndOfCentralDirectory that = (EndOfCentralDirectory) o; - return diskNumber == that.diskNumber && - centralDirectoryStartDisk == that.centralDirectoryStartDisk && - centralDirectoryStartOffset == that.centralDirectoryStartOffset && - numEntries == that.numEntries && - centralDirectorySize == that.centralDirectorySize && - centralDirectoryOffset == that.centralDirectoryOffset && - zipCommentLength == that.zipCommentLength && - zipComment.equals(that.zipComment); + if (!(o instanceof EndOfCentralDirectory that)) return false; + + if (offset != that.offset) return false; + if (diskNumber != that.diskNumber) return false; + if (centralDirectoryStartDisk != that.centralDirectoryStartDisk) return false; + if (centralDirectoryStartOffset != that.centralDirectoryStartOffset) return false; + if (numEntries != that.numEntries) return false; + if (centralDirectorySize != that.centralDirectorySize) return false; + if (centralDirectoryOffset != that.centralDirectoryOffset) return false; + if (zipCommentLength != that.zipCommentLength) return false; + return Objects.equals(zipComment, that.zipComment); } @Override public int hashCode() { - return Objects.hash(diskNumber, centralDirectoryStartDisk, centralDirectoryStartOffset, - numEntries, centralDirectorySize, centralDirectoryOffset, zipCommentLength, zipComment); + int result = (int) (offset ^ (offset >>> 32)); + result = 31 * result + diskNumber; + result = 31 * result + centralDirectoryStartDisk; + result = 31 * result + centralDirectoryStartOffset; + result = 31 * result + numEntries; + result = 31 * result + (int) (centralDirectorySize ^ (centralDirectorySize >>> 32)); + result = 31 * result + (int) (centralDirectoryOffset ^ (centralDirectoryOffset >>> 32)); + result = 31 * result + zipCommentLength; + result = 31 * result + (zipComment != null ? zipComment.hashCode() : 0); + return result; } } diff --git a/src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java index 7994422..d987321 100644 --- a/src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java @@ -3,7 +3,7 @@ import software.coley.lljzip.format.ZipPatterns; import software.coley.lljzip.format.compression.ZipCompressions; import software.coley.lljzip.util.MemorySegmentUtil; -import software.coley.lljzip.util.lazy.LazyLong; +import software.coley.lljzip.util.data.MemorySegmentData; import javax.annotation.Nonnull; import java.lang.foreign.MemorySegment; @@ -32,8 +32,15 @@ public void setOffsets(@Nonnull NavigableSetoffsets) { } @Override - public void read(@Nonnull MemorySegment data, long offset) { - super.read(data, offset); + public void read(@Nonnull MemorySegment data, long offset) throws ZipParseException { + try { + super.read(data, offset); + } catch (ZipParseException ex) { + // If the error was that the data couldn't be read, that's OK because we're going to try reading + // that in a slightly different way below. + if (ex.getType() != ZipParseException.Type.IOOBE_FILE_DATA) + throw ex; + } // JVM file data reading does NOT use the compressed/uncompressed fields. // Instead, it scans data until the next header. @@ -77,23 +84,27 @@ public void read(@Nonnull MemorySegment data, long offset) { // Update the file data ranges this.relativeDataOffsetStart = relativeDataOffsetStart; this.relativeDataOffsetEnd = relativeDataOffsetEnd; - fileDataLength = new LazyLong(() -> relativeDataOffsetEnd - relativeDataOffsetStart).withId("fileDataLength"); - - fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, - new LazyLong(() -> relativeDataOffsetStart), fileDataLength).withId("fileData"); + long fileDataLength = relativeDataOffsetEnd - relativeDataOffsetStart; + + try { + fileData = MemorySegmentData.of(MemorySegmentUtil.readLongSlice(data, offset, relativeDataOffsetStart, fileDataLength)); + } catch (IndexOutOfBoundsException ex) { + throw new ZipParseException(ex, ZipParseException.Type.IOOBE_FILE_DATA); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } // Update sizes where possible - long size = fileDataLength.get(); if (getCompressionMethod() == ZipCompressions.STORED) { - setUncompressedSize(size); - setCompressedSize(size); + setUncompressedSize(fileDataLength); + setCompressedSize(fileDataLength); } else { - setCompressedSize(size); + setCompressedSize(fileDataLength); } // If we have a size, we can assume we found some data. // Whether its valid, who really knows? - foundData = size != 0; + foundData = fileDataLength != 0; } @Override @@ -103,7 +114,6 @@ public void adoptLinkedCentralDirectoryValues() { return; // JVM trusts central directory file header contents over local - // - Using fields as this maintains the lazy model versionNeededToExtract = linkedDirectoryFileHeader.versionNeededToExtract; generalPurposeBitFlag = linkedDirectoryFileHeader.generalPurposeBitFlag; compressionMethod = linkedDirectoryFileHeader.compressionMethod; @@ -137,9 +147,7 @@ public void adoptLinkedCentralDirectoryValues() { // Data should not be overflowing into adjacent header entries. // - If it is, the data here is likely intentionally tampered with to screw with parsers if (fileDataLength < relativeDataOffsetEnd) { - fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, - new LazyLong(() -> relativeDataOffsetStart - offset), - new LazyLong(() -> fileDataLength)).withId("fileData"); + fileData = MemorySegmentData.of(MemorySegmentUtil.readLongSlice(data, offset, relativeDataOffsetStart - offset, fileDataLength)); } } } diff --git a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java index 6883c43..fb96cd7 100644 --- a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java @@ -2,10 +2,8 @@ import software.coley.lljzip.format.compression.Decompressor; import software.coley.lljzip.format.read.ZipReader; -import software.coley.lljzip.util.MemorySegmentUtil; -import software.coley.lljzip.util.lazy.LazyMemorySegment; -import software.coley.lljzip.util.lazy.LazyInt; -import software.coley.lljzip.util.lazy.LazyLong; +import software.coley.lljzip.util.data.MemorySegmentData; +import software.coley.lljzip.util.data.StringData; import javax.annotation.Nonnull; import java.io.IOException; @@ -13,6 +11,7 @@ import java.util.Objects; import static software.coley.lljzip.format.compression.ZipCompressions.STORED; +import static software.coley.lljzip.util.MemorySegmentUtil.*; /** * ZIP LocalFileHeader structure. @@ -38,14 +37,11 @@ * @author Matt Coley */ public class LocalFileHeader extends AbstractZipFileHeader { - protected static final int MIN_FIXED_SIZE = 30; + public static final int MIN_FIXED_SIZE = 30; protected transient CentralDirectoryFileHeader linkedDirectoryFileHeader; // LocalFileHeader spec (plus common elements between this and central file) - protected LazyMemorySegment fileData; - - // Caches - protected transient LazyLong fileDataLength; + protected MemorySegmentData fileData; /** * @return Copy. @@ -56,53 +52,63 @@ public LocalFileHeader copy() { copy.data = data; copy.offset = offset; copy.linkedDirectoryFileHeader = linkedDirectoryFileHeader; - copy.versionNeededToExtract = versionNeededToExtract.copy(); - copy.generalPurposeBitFlag = generalPurposeBitFlag.copy(); - copy.compressionMethod = compressionMethod.copy(); - copy.lastModFileTime = lastModFileTime.copy(); - copy.lastModFileDate = lastModFileDate.copy(); - copy.crc32 = crc32.copy(); - copy.compressedSize = compressedSize.copy(); - copy.uncompressedSize = uncompressedSize.copy(); - copy.fileNameLength = fileNameLength.copy(); - copy.extraFieldLength = extraFieldLength.copy(); + copy.versionNeededToExtract = versionNeededToExtract; + copy.generalPurposeBitFlag = generalPurposeBitFlag; + copy.compressionMethod = compressionMethod; + copy.lastModFileTime = lastModFileTime; + copy.lastModFileDate = lastModFileDate; + copy.crc32 = crc32; + copy.compressedSize = compressedSize; + copy.uncompressedSize = uncompressedSize; + copy.fileNameLength = fileNameLength; + copy.extraFieldLength = extraFieldLength; copy.fileName = fileName.copy(); copy.extraField = extraField.copy(); - copy.fileDataLength = fileDataLength.copy(); copy.fileData = fileData.copy(); return copy; } @Override - public void read(@Nonnull MemorySegment data, long offset) { + public void read(@Nonnull MemorySegment data, long offset) throws ZipParseException { super.read(data, offset); - versionNeededToExtract = MemorySegmentUtil.readLazyWord(data, offset, 4).withId("versionNeededToExtract"); - generalPurposeBitFlag = MemorySegmentUtil.readLazyWord(data, offset, 6).withId("generalPurposeBitFlag"); - compressionMethod = MemorySegmentUtil.readLazyWord(data, offset, 8).withId("compressionMethod"); - lastModFileTime = MemorySegmentUtil.readLazyWord(data, offset, 10).withId("lastModFileTime"); - lastModFileDate = MemorySegmentUtil.readLazyWord(data, offset, 12).withId("lastModFileDate"); - crc32 = MemorySegmentUtil.readLazyQuad(data, offset, 14).withId("crc32"); - compressedSize = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 18).withId("compressedSize"); - uncompressedSize = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 22).withId("uncompressedSize"); - fileNameLength = MemorySegmentUtil.readLazyWord(data, offset, 26).withId("fileNameLength"); - extraFieldLength = MemorySegmentUtil.readLazyWord(data, offset, 28).withId("extraFieldLength"); - fileName = MemorySegmentUtil.readLazySlice(data, offset, new LazyInt(() -> MIN_FIXED_SIZE), fileNameLength).withId("fileName"); - extraField = MemorySegmentUtil.readLazySlice(data, offset, fileNameLength.add(MIN_FIXED_SIZE), extraFieldLength).withId("extraField"); - fileDataLength = new LazyLong(() -> { - long fileDataLength; - if (compressionMethod.get() == STORED) { - fileDataLength = uncompressedSize.get(); - } else { - fileDataLength = compressedSize.get(); - } - return fileDataLength; - }).withId("fileDataLength"); - fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, - fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), fileDataLength).withId("fileData"); + try { + versionNeededToExtract = readWord(data, offset, 4); + generalPurposeBitFlag = readWord(data, offset, 6); + compressionMethod = readWord(data, offset, 8); + lastModFileTime = readWord(data, offset, 10); + lastModFileDate = readWord(data, offset, 12); + crc32 = readQuad(data, offset, 14); + compressedSize = readMaskedLongQuad(data, offset, 18); + uncompressedSize = readMaskedLongQuad(data, offset, 22); + fileNameLength = readWord(data, offset, 26); + extraFieldLength = readWord(data, offset, 28); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } + try { + fileName = StringData.of(readSlice(data, offset, MIN_FIXED_SIZE, fileNameLength)); + } catch (IndexOutOfBoundsException ex) { + throw new ZipParseException(ex, ZipParseException.Type.IOOBE_FILE_NAME); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } + try { + extraField = MemorySegmentData.of(data, offset + MIN_FIXED_SIZE + fileNameLength, extraFieldLength); + } catch (IndexOutOfBoundsException ex) { + throw new ZipParseException(ex, ZipParseException.Type.IOOBE_FILE_EXTRA); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } + long fileDataLength = (compressionMethod == STORED) ? uncompressedSize : compressedSize; + try { + fileData = MemorySegmentData.of(readLongSlice(data, offset, MIN_FIXED_SIZE + fileNameLength + extraFieldLength, fileDataLength)); + } catch (IndexOutOfBoundsException ex) { + throw new ZipParseException(ex, ZipParseException.Type.IOOBE_FILE_DATA); + } catch (Throwable t) { + throw new ZipParseException(ZipParseException.Type.OTHER); + } } - - /** * Should match {@link CentralDirectoryFileHeader#getFileNameLength()} but is not a strict requirement. * If they do not match, the central directory file name length should be trusted instead. @@ -121,8 +127,7 @@ public int getFileNameLength() { * @return File name. */ @Override - public MemorySegment getFileName() { - + public StringData getFileName() { return super.getFileName(); } @@ -168,7 +173,7 @@ public boolean hasDifferentValuesThanCentralDirectoryHeader() { * In some cases the {@link LocalFileHeader} file size may be 0, but the authoritative CEN states a non-0 value, * which you may want to adopt. */ - public void adoptLinkedCentralDirectoryValues() { + public void adoptLinkedCentralDirectoryValues() throws ZipParseException { if (linkedDirectoryFileHeader != null) { versionNeededToExtract = linkedDirectoryFileHeader.versionNeededToExtract; generalPurposeBitFlag = linkedDirectoryFileHeader.generalPurposeBitFlag; @@ -183,55 +188,41 @@ public void adoptLinkedCentralDirectoryValues() { extraField = linkedDirectoryFileHeader.extraField; // We're using the same slices/data locations from the central directory. // If we wanted to use local data but with updated offsets from the central directory it would look like this: - // fileName = ByteDataUtil.readLazySlice(data, offset, new LazyInt(() -> MIN_FIXED_SIZE), fileNameLength).withId("fileName"); - // extraField = ByteDataUtil.readLazySlice(data, offset, fileNameLength.add(MIN_FIXED_SIZE), extraFieldLength).withId("extraField"); - fileDataLength = new LazyLong(() -> { - long fileDataLength; - if (compressionMethod.get() == STORED) { - fileDataLength = uncompressedSize.get(); - } else { - fileDataLength = compressedSize.get(); - } - return fileDataLength; - }).withId("fileDataLength"); + // fileName = ByteDataUtil.readSlice(data, offset, MIN_FIXED_SIZE, fileNameLength) + // extraField = ByteDataUtil.readSlice(data, offset, MIN_FIXED_SIZE + fileNameLength, extraFieldLength) + long fileDataLength = (compressionMethod == STORED) ? uncompressedSize : compressedSize; if (data != null) - fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), fileDataLength).withId("fileData"); + try { + fileData = MemorySegmentData.of(readLongSlice(data, offset, MIN_FIXED_SIZE + fileNameLength + extraFieldLength, fileDataLength)); + } catch (IndexOutOfBoundsException ex) { + throw new ZipParseException(ex, ZipParseException.Type.IOOBE_FILE_DATA); + } } } /** * Sets the file data length to go up to the given offset. * - * @param endOffset New file data length. + * @param endOffset + * New file data length. */ public void setFileDataEndOffset(long endOffset) { - long fileDataStartOffset = offset + fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE).get(); + long fileDataStartOffset = offset + MIN_FIXED_SIZE + fileNameLength + extraFieldLength; long length = endOffset - fileDataStartOffset; setFileDataLength(length); } /** - * @param newLength New file data length. + * @param newLength + * New file data length. */ public void setFileDataLength(long newLength) { - fileDataLength.set(newLength); - fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), newLength).withId("fileData"); - } - - /** - * @param newLength New file data length. - */ - public void setFileDataLength(@Nonnull LazyLong newLength) { - fileDataLength = newLength; - fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), newLength).withId("fileData"); + fileData = MemorySegmentData.of(readLongSlice(data, offset, MIN_FIXED_SIZE + fileNameLength + extraFieldLength, newLength)); } @Override public long length() { - return MIN_FIXED_SIZE + - fileNameLength.get() + - extraFieldLength.get() + - fileDataLength.get(); + return MIN_FIXED_SIZE + fileNameLength + extraFieldLength + fileData.length(); } @Nonnull @@ -281,15 +272,14 @@ public MemorySegment getFileData() { * @param fileData * Compressed file contents. */ - public void setFileData(MemorySegment fileData) { - this.fileData.set(fileData); + public void setFileData(MemorySegmentData fileData) { + this.fileData = fileData; } @Override public String toString() { return "LocalFileHeader{" + "fileData=" + fileData + - ", fileDataLength=" + fileDataLength + ", data=" + data + ", versionNeededToExtract=" + versionNeededToExtract + ", generalPurposeBitFlag=" + generalPurposeBitFlag + @@ -309,42 +299,17 @@ public String toString() { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - LocalFileHeader that = (LocalFileHeader) o; + if (!(o instanceof LocalFileHeader that)) return false; + if (!super.equals(o)) return false; - if (!Objects.equals(versionNeededToExtract, that.versionNeededToExtract)) return false; - if (!Objects.equals(generalPurposeBitFlag, that.generalPurposeBitFlag)) return false; - if (!Objects.equals(compressionMethod, that.compressionMethod)) return false; - if (!Objects.equals(lastModFileTime, that.lastModFileTime)) return false; - if (!Objects.equals(lastModFileDate, that.lastModFileDate)) return false; - if (!Objects.equals(crc32, that.crc32)) return false; - if (!Objects.equals(compressedSize, that.compressedSize)) return false; - if (!Objects.equals(uncompressedSize, that.uncompressedSize)) return false; - if (!Objects.equals(fileNameLength, that.fileNameLength)) return false; - if (!Objects.equals(extraFieldLength, that.extraFieldLength)) return false; - if (!Objects.equals(fileName, that.fileName)) return false; - if (!Objects.equals(extraField, that.extraField)) return false; - if (!Objects.equals(fileDataLength, that.fileDataLength)) return false; + if (!Objects.equals(linkedDirectoryFileHeader, that.linkedDirectoryFileHeader)) return false; return Objects.equals(fileData, that.fileData); } @Override public int hashCode() { - int result = 0; - result = 31 * result + (versionNeededToExtract != null ? versionNeededToExtract.hashCode() : 0); - result = 31 * result + (generalPurposeBitFlag != null ? generalPurposeBitFlag.hashCode() : 0); - result = 31 * result + (compressionMethod != null ? compressionMethod.hashCode() : 0); - result = 31 * result + (lastModFileTime != null ? lastModFileTime.hashCode() : 0); - result = 31 * result + (lastModFileDate != null ? lastModFileDate.hashCode() : 0); - result = 31 * result + (crc32 != null ? crc32.hashCode() : 0); - result = 31 * result + (compressedSize != null ? compressedSize.hashCode() : 0); - result = 31 * result + (uncompressedSize != null ? uncompressedSize.hashCode() : 0); - result = 31 * result + (fileNameLength != null ? fileNameLength.hashCode() : 0); - result = 31 * result + (extraFieldLength != null ? extraFieldLength.hashCode() : 0); - result = 31 * result + (fileName != null ? fileName.hashCode() : 0); - result = 31 * result + (extraField != null ? extraField.hashCode() : 0); - result = 31 * result + (fileDataLength != null ? fileDataLength.hashCode() : 0); + int result = super.hashCode(); + result = 31 * result + (linkedDirectoryFileHeader != null ? linkedDirectoryFileHeader.hashCode() : 0); result = 31 * result + (fileData != null ? fileData.hashCode() : 0); return result; } diff --git a/src/main/java/software/coley/lljzip/format/model/ZipArchive.java b/src/main/java/software/coley/lljzip/format/model/ZipArchive.java index 5ec7daf..50f1a66 100644 --- a/src/main/java/software/coley/lljzip/format/model/ZipArchive.java +++ b/src/main/java/software/coley/lljzip/format/model/ZipArchive.java @@ -9,7 +9,12 @@ import java.io.Closeable; import java.io.IOException; import java.lang.foreign.MemorySegment; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -160,6 +165,7 @@ public List getNameFilteredLocalFiles(Predicate nameFil return centralDirectories.stream() .filter(c -> nameFilter.test(c.getFileNameAsString())) .map(CentralDirectoryFileHeader::getLinkedFileHeader) + .filter(Objects::nonNull) .collect(Collectors.toList()); // Fallback to using local entries diff --git a/src/main/java/software/coley/lljzip/format/model/ZipParseException.java b/src/main/java/software/coley/lljzip/format/model/ZipParseException.java new file mode 100644 index 0000000..b5a3866 --- /dev/null +++ b/src/main/java/software/coley/lljzip/format/model/ZipParseException.java @@ -0,0 +1,52 @@ +package software.coley.lljzip.format.model; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Exception thrown when parsing {@link ZipPart} contents from a data source. + * + * @author Matt Coley + * @see Type + */ +public class ZipParseException extends Exception { + private final Type type; + + public ZipParseException(@Nullable IndexOutOfBoundsException cause, @Nonnull Type type) { + super(type.getMessage(), cause); + this.type = type; + } + + public ZipParseException(@Nonnull Type type) { + this(null, type); + } + + /** + * @return Parse failure case. + */ + @Nonnull + public Type getType() { + return type; + } + + /** + * Enum of common parse failure cases. + */ + public enum Type { + IOOBE_FILE_NAME("Bounds check failed reading file name"), + IOOBE_FILE_DATA("Bounds check failed reading file data"), + IOOBE_FILE_EXTRA("Bounds check failed reading file extra"), + IOOBE_CEN_COMMENT("Bounds check failed reading directory comment"), + OTHER("Unknown zip parse error"); + + private final String message; + + Type(String message) { + this.message = message; + } + + String getMessage() { + return message; + } + } +} diff --git a/src/main/java/software/coley/lljzip/format/model/ZipRead.java b/src/main/java/software/coley/lljzip/format/model/ZipRead.java index 3773f45..f5529aa 100644 --- a/src/main/java/software/coley/lljzip/format/model/ZipRead.java +++ b/src/main/java/software/coley/lljzip/format/model/ZipRead.java @@ -15,5 +15,5 @@ public interface ZipRead { * @param offset * Initial offset in data to start at. */ - void read(@Nonnull MemorySegment data, long offset); + void read(@Nonnull MemorySegment data, long offset) throws ZipParseException; } diff --git a/src/main/java/software/coley/lljzip/format/read/AbstractZipReader.java b/src/main/java/software/coley/lljzip/format/read/AbstractZipReader.java index 81f9cf2..eb4ccf0 100644 --- a/src/main/java/software/coley/lljzip/format/read/AbstractZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/AbstractZipReader.java @@ -9,7 +9,8 @@ */ public abstract class AbstractZipReader extends DelegatingZipPartAllocator implements ZipReader { /** - * @param allocator Allocator to use. + * @param allocator + * Allocator to use. */ public AbstractZipReader(@Nonnull ZipPartAllocator allocator) { super(allocator); diff --git a/src/main/java/software/coley/lljzip/format/read/ForwardScanZipReader.java b/src/main/java/software/coley/lljzip/format/read/ForwardScanZipReader.java index 8ef34fe..63b4690 100644 --- a/src/main/java/software/coley/lljzip/format/read/ForwardScanZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/ForwardScanZipReader.java @@ -7,13 +7,15 @@ import software.coley.lljzip.format.model.EndOfCentralDirectory; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; +import software.coley.lljzip.format.model.ZipParseException; import software.coley.lljzip.util.MemorySegmentUtil; import software.coley.lljzip.util.OffsetComparator; import javax.annotation.Nonnull; import java.io.IOException; import java.lang.foreign.MemorySegment; -import java.util.*; +import java.util.NavigableSet; +import java.util.TreeSet; /** * The standard read strategy that should work with standard zip archives. @@ -33,7 +35,8 @@ public ForwardScanZipReader() { /** * New reader with given allocator. * - * @param allocator Allocator to use. + * @param allocator + * Allocator to use. */ public ForwardScanZipReader(@Nonnull ZipPartAllocator allocator) { super(allocator); @@ -59,7 +62,12 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO long centralDirectoryOffset = zipStart + end.getCentralDirectoryOffset(); while (centralDirectoryOffset < len && MemorySegmentUtil.readQuad(data, centralDirectoryOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD) { CentralDirectoryFileHeader directory = new CentralDirectoryFileHeader(); - directory.read(data, centralDirectoryOffset); + try { + directory.read(data, centralDirectoryOffset); + } catch (ZipParseException ex) { + // When the CEN cannot be read, we can't recover + throw new IOException(ex); + } centralDirectoryOffset += directory.length(); zip.addPart(directory); } @@ -73,7 +81,12 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO LocalFileHeader file = newLocalFileHeader(); directory.link(file); file.link(directory); - file.read(data, offset); + try { + file.read(data, offset); + } catch (ZipParseException ex) { + // Unlike the other readers, we aren't going to fall back to using CEN data, so we won't recover from this. + throw new IOException(ex); + } zip.addPart(file); postProcessLocalFileHeader(file); offsets.add(offset); diff --git a/src/main/java/software/coley/lljzip/format/read/JvmZipReader.java b/src/main/java/software/coley/lljzip/format/read/JvmZipReader.java index 47a3576..bc8516c 100644 --- a/src/main/java/software/coley/lljzip/format/read/JvmZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/JvmZipReader.java @@ -3,7 +3,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.coley.lljzip.format.ZipPatterns; -import software.coley.lljzip.format.model.*; +import software.coley.lljzip.format.model.CentralDirectoryFileHeader; +import software.coley.lljzip.format.model.EndOfCentralDirectory; +import software.coley.lljzip.format.model.JvmLocalFileHeader; +import software.coley.lljzip.format.model.LocalFileHeader; +import software.coley.lljzip.format.model.ZipArchive; +import software.coley.lljzip.format.model.ZipParseException; import software.coley.lljzip.util.MemorySegmentUtil; import software.coley.lljzip.util.OffsetComparator; @@ -27,12 +32,13 @@ public class JvmZipReader extends AbstractZipReader { private static final Logger logger = LoggerFactory.getLogger(JvmZipReader.class); private final boolean skipRevisitedCenToLocalLinks; + private final boolean allowBasicJvmBaseOffsetZeroCheck; /** * New reader with jvm allocator. */ public JvmZipReader() { - this(new JvmZipPartAllocator(), true); + this(new JvmZipPartAllocator(), true, true); } /** @@ -41,9 +47,11 @@ public JvmZipReader() { * @param skipRevisitedCenToLocalLinks * Flag to skip creating duplicate {@link LocalFileHeader} entries if multiple * {@link CentralDirectoryFileHeader} point to the same location. + * @param allowBasicJvmBaseOffsetZeroCheck + * Flag to check for {@code jvmBaseFileOffset == 0} before using the logic adapted from {@code ZipFile.Source#findEND()}. */ - public JvmZipReader(boolean skipRevisitedCenToLocalLinks) { - this(new JvmZipPartAllocator(), skipRevisitedCenToLocalLinks); + public JvmZipReader(boolean skipRevisitedCenToLocalLinks, boolean allowBasicJvmBaseOffsetZeroCheck) { + this(new JvmZipPartAllocator(), skipRevisitedCenToLocalLinks, allowBasicJvmBaseOffsetZeroCheck); } /** @@ -54,10 +62,13 @@ public JvmZipReader(boolean skipRevisitedCenToLocalLinks) { * @param skipRevisitedCenToLocalLinks * Flag to skip creating duplicate {@link LocalFileHeader} entries if multiple * {@link CentralDirectoryFileHeader} point to the same location. + * @param allowBasicJvmBaseOffsetZeroCheck + * Flag to check for {@code jvmBaseFileOffset == 0} before using the logic adapted from {@code ZipFile.Source#findEND()}. */ - public JvmZipReader(@Nonnull ZipPartAllocator allocator, boolean skipRevisitedCenToLocalLinks) { + public JvmZipReader(@Nonnull ZipPartAllocator allocator, boolean skipRevisitedCenToLocalLinks, boolean allowBasicJvmBaseOffsetZeroCheck) { super(allocator); this.skipRevisitedCenToLocalLinks = skipRevisitedCenToLocalLinks; + this.allowBasicJvmBaseOffsetZeroCheck = allowBasicJvmBaseOffsetZeroCheck; } @Override @@ -85,7 +96,12 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO centralDirectoryOffset = MemorySegmentUtil.lastIndexOfQuad(data, centralDirectoryOffset - 1L, ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD); if (centralDirectoryOffset >= 0L) { CentralDirectoryFileHeader directory = newCentralDirectoryFileHeader(); - directory.read(data, centralDirectoryOffset); + try { + directory.read(data, centralDirectoryOffset); + } catch (ZipParseException ex) { + // We cannot recover from the CEN reading encountering failures. + throw new IOException(ex); + } zip.addPart(directory); if (directory.getRelativeOffsetOfLocalHeader() > maxRelativeOffset) maxRelativeOffset = directory.getRelativeOffsetOfLocalHeader(); @@ -122,29 +138,42 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO // Search for the first valid PK header if there was either no prior ZIP file // or if the prior ZIP detection was bogus. - if (priorZipEndWasBogus || precedingEndOfCentralDirectory == -1L) { - // There was no match for a prior end part. We will seek forwards until finding a *VALID* PK starting header. - // - Java's zip parser does not always start from zero. It uses the computation: - // locpos = (end.endpos - end.cenlen) - end.cenoff; - // - This computation is taken from: ZipFile.Source#initCEN - jvmBaseFileOffset = (end.offset() - end.getCentralDirectorySize()) - end.getCentralDirectoryOffset(); - - // Now that we have the start offset, scan forward. We can match the current value as well. - jvmBaseFileOffset = MemorySegmentUtil.indexOfWord(data, jvmBaseFileOffset, ZipPatterns.PK_WORD); - while (jvmBaseFileOffset >= 0L) { - // Check that the PK discovered represents a valid zip part - try { - if (MemorySegmentUtil.readQuad(data, jvmBaseFileOffset) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) - new LocalFileHeader().read(data, jvmBaseFileOffset); - else if (MemorySegmentUtil.readQuad(data, jvmBaseFileOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD) - new CentralDirectoryFileHeader().read(data, jvmBaseFileOffset); - else + scan: + { + if (priorZipEndWasBogus || precedingEndOfCentralDirectory == -1L) { + // If the start of the file is valid, we don't have to actually do much more anti-bogus work. + // This whole 'scan' block is incredibly yucky but seems to work for all of our edge case inputs. + if (allowBasicJvmBaseOffsetZeroCheck && MemorySegmentUtil.readQuad(data, 0) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) { + if (MemorySegmentUtil.indexOfQuad(data, 1, ZipPatterns.LOCAL_FILE_HEADER_QUAD) > LocalFileHeader.MIN_FIXED_SIZE) { + break scan; + } + } + + // There was no match for a prior end part. We will seek forwards until finding a *VALID* PK starting header. + // - Java's zip parser does not always start from zero. It uses the computation: + // locpos = (end.endpos - end.cenlen) - end.cenoff; + // - This computation is taken from: ZipFile.Source#initCEN + long endPos = end.offset(); + long cenLen = end.getCentralDirectorySize(); + long cenOff = end.getCentralDirectoryOffset(); + jvmBaseFileOffset = (endPos - cenLen) - cenOff; + + // Now that we have the start offset, scan forward. We can match the current value as well. + jvmBaseFileOffset = MemorySegmentUtil.indexOfWord(data, jvmBaseFileOffset, ZipPatterns.PK_WORD); + while (jvmBaseFileOffset >= 0L) { + // Check that the PK discovered represents a valid zip part + try { + if (MemorySegmentUtil.readQuad(data, jvmBaseFileOffset) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) + break; + else if (MemorySegmentUtil.readQuad(data, jvmBaseFileOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD) + break; + + // Not an expected value... throw new IllegalStateException("No match for LocalFileHeader/CentralDirectoryFileHeader"); - // Valid, we're good to go - break; - } catch (Exception ex) { - // Invalid, seek forward - jvmBaseFileOffset = MemorySegmentUtil.indexOfWord(data, jvmBaseFileOffset + 1L, ZipPatterns.PK_WORD); + } catch (Exception ex) { + // Invalid, seek forward + jvmBaseFileOffset = MemorySegmentUtil.indexOfWord(data, jvmBaseFileOffset + 1L, ZipPatterns.PK_WORD); + } } } } @@ -195,7 +224,11 @@ else if (MemorySegmentUtil.readQuad(data, jvmBaseFileOffset) == ZipPatterns.CENT if (file instanceof JvmLocalFileHeader) { ((JvmLocalFileHeader) file).setOffsets(entryOffsets); } - file.read(data, offset); + try { + file.read(data, offset); + } catch (IndexOutOfBoundsException t) { + // Its intended that if this fails the adopting of CEN values below will work instead. + } directory.link(file); file.link(directory); file.adoptLinkedCentralDirectoryValues(); @@ -209,7 +242,8 @@ else if (MemorySegmentUtil.readQuad(data, jvmBaseFileOffset) == ZipPatterns.CENT // Record any data appearing at the front of the file not associated with the ZIP file contents. if (!entryOffsets.isEmpty()) { long firstOffset = entryOffsets.first(); - zip.setPrefixData(data.asSlice(0, firstOffset)); + if (firstOffset > 0) + zip.setPrefixData(data.asSlice(0, firstOffset)); } // Sort based on order diff --git a/src/main/java/software/coley/lljzip/format/read/NaiveLocalFileZipReader.java b/src/main/java/software/coley/lljzip/format/read/NaiveLocalFileZipReader.java index c422618..82667e7 100644 --- a/src/main/java/software/coley/lljzip/format/read/NaiveLocalFileZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/NaiveLocalFileZipReader.java @@ -3,6 +3,7 @@ import software.coley.lljzip.format.ZipPatterns; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; +import software.coley.lljzip.format.model.ZipParseException; import software.coley.lljzip.util.MemorySegmentUtil; import javax.annotation.Nonnull; @@ -43,7 +44,11 @@ public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IO } do { LocalFileHeader file = newLocalFileHeader(); - file.read(data, localFileOffset); + try { + file.read(data, localFileOffset); + } catch (ZipParseException ex) { + throw new IOException(ex); + } zip.addPart(file); postProcessLocalFileHeader(file); } while ((localFileOffset = MemorySegmentUtil.indexOfQuad(data, localFileOffset + 1, ZipPatterns.LOCAL_FILE_HEADER_QUAD)) >= 0); diff --git a/src/main/java/software/coley/lljzip/format/read/SimpleZipPartAllocator.java b/src/main/java/software/coley/lljzip/format/read/SimpleZipPartAllocator.java index d103f26..8ff8a28 100644 --- a/src/main/java/software/coley/lljzip/format/read/SimpleZipPartAllocator.java +++ b/src/main/java/software/coley/lljzip/format/read/SimpleZipPartAllocator.java @@ -11,7 +11,7 @@ * * @author Matt Coley */ -public class SimpleZipPartAllocator implements ZipPartAllocator{ +public class SimpleZipPartAllocator implements ZipPartAllocator { @Nonnull @Override public LocalFileHeader newLocalFileHeader() { diff --git a/src/main/java/software/coley/lljzip/format/read/ZipReader.java b/src/main/java/software/coley/lljzip/format/read/ZipReader.java index 2ae3465..ea10a3f 100644 --- a/src/main/java/software/coley/lljzip/format/read/ZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/ZipReader.java @@ -25,7 +25,8 @@ public interface ZipReader { void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IOException; /** - * @param file File to post-process. + * @param file + * File to post-process. */ default void postProcessLocalFileHeader(@Nonnull LocalFileHeader file) { // no-op by default diff --git a/src/main/java/software/coley/lljzip/format/transform/CentralAdoptingMapper.java b/src/main/java/software/coley/lljzip/format/transform/CentralAdoptingMapper.java index a2133a6..debe625 100644 --- a/src/main/java/software/coley/lljzip/format/transform/CentralAdoptingMapper.java +++ b/src/main/java/software/coley/lljzip/format/transform/CentralAdoptingMapper.java @@ -3,6 +3,7 @@ import software.coley.lljzip.format.model.CentralDirectoryFileHeader; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; +import software.coley.lljzip.format.model.ZipParseException; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -26,7 +27,11 @@ public CentralAdoptingMapper(@Nonnull ZipPartMapper delegate) { public LocalFileHeader mapLocal(@Nonnull ZipArchive archive, @Nonnull LocalFileHeader localFileHeader) { if (localFileHeader.getLinkedDirectoryFileHeader() != null) { LocalFileHeader copy = localFileHeader.copy(); - copy.adoptLinkedCentralDirectoryValues(); + try { + copy.adoptLinkedCentralDirectoryValues(); + } catch (ZipParseException ex) { + throw new IllegalStateException(ex); + } return copy; } return super.mapLocal(archive, localFileHeader); diff --git a/src/main/java/software/coley/lljzip/format/transform/JvmClassDirectoryMapper.java b/src/main/java/software/coley/lljzip/format/transform/JvmClassDirectoryMapper.java index 42f0e10..c5c5f91 100644 --- a/src/main/java/software/coley/lljzip/format/transform/JvmClassDirectoryMapper.java +++ b/src/main/java/software/coley/lljzip/format/transform/JvmClassDirectoryMapper.java @@ -28,7 +28,7 @@ public LocalFileHeader mapLocal(@Nonnull ZipArchive archive, @Nonnull LocalFileH if (name.endsWith(".class/")) { int newLength = name.length() - 1; LocalFileHeader copy = localFileHeader.copy(); - copy.setFileName(copy.getFileName().asSlice(0, newLength)); + copy.setFileName(copy.getFileName().substring(0, newLength)); copy.setFileNameLength(newLength); localFileHeader = copy; } @@ -42,7 +42,7 @@ public CentralDirectoryFileHeader mapCentral(@Nonnull ZipArchive archive, @Nonnu if (name.endsWith(".class/")) { int newLength = name.length() - 1; CentralDirectoryFileHeader copy = centralDirectoryFileHeader.copy(); - copy.setFileName(copy.getFileName().asSlice(0, newLength)); + copy.setFileName(copy.getFileName().substring(0, newLength)); copy.setFileNameLength(newLength); centralDirectoryFileHeader = copy; } diff --git a/src/main/java/software/coley/lljzip/format/transform/ZipPartMapper.java b/src/main/java/software/coley/lljzip/format/transform/ZipPartMapper.java index 7aed95d..d9df231 100644 --- a/src/main/java/software/coley/lljzip/format/transform/ZipPartMapper.java +++ b/src/main/java/software/coley/lljzip/format/transform/ZipPartMapper.java @@ -1,6 +1,10 @@ package software.coley.lljzip.format.transform; -import software.coley.lljzip.format.model.*; +import software.coley.lljzip.format.model.CentralDirectoryFileHeader; +import software.coley.lljzip.format.model.EndOfCentralDirectory; +import software.coley.lljzip.format.model.LocalFileHeader; +import software.coley.lljzip.format.model.ZipArchive; +import software.coley.lljzip.format.model.ZipPart; import javax.annotation.Nonnull; import javax.annotation.Nullable; diff --git a/src/main/java/software/coley/lljzip/format/write/DelegatingZipWriter.java b/src/main/java/software/coley/lljzip/format/write/DelegatingZipWriter.java index 64d1a10..ecece63 100644 --- a/src/main/java/software/coley/lljzip/format/write/DelegatingZipWriter.java +++ b/src/main/java/software/coley/lljzip/format/write/DelegatingZipWriter.java @@ -19,7 +19,8 @@ public class DelegatingZipWriter implements ZipWriter { private final ZipWriter delegate; /** - * @param delegate Delegate writer. + * @param delegate + * Delegate writer. */ public DelegatingZipWriter(@Nullable ZipWriter delegate) { this.delegate = delegate; diff --git a/src/main/java/software/coley/lljzip/format/write/DirectZipWriter.java b/src/main/java/software/coley/lljzip/format/write/DirectZipWriter.java index 5be8497..daaf20f 100644 --- a/src/main/java/software/coley/lljzip/format/write/DirectZipWriter.java +++ b/src/main/java/software/coley/lljzip/format/write/DirectZipWriter.java @@ -46,8 +46,8 @@ protected void writeLocalFile(@Nonnull LocalFileHeader fileHeader, @Nonnull Outp writeIntLE(os, (int) fileHeader.getUncompressedSize()); writeShortLE(os, fileHeader.getFileNameLength()); writeShortLE(os, fileHeader.getExtraFieldLength()); - os.write(MemorySegmentUtil.toByteArray(fileHeader.getFileName())); - os.write(MemorySegmentUtil.toByteArray(fileHeader.getExtraField())); + os.write(fileHeader.getFileName().get().getBytes()); + os.write(MemorySegmentUtil.toByteArray(fileHeader.getExtraField().get())); os.write(MemorySegmentUtil.toByteArray(fileHeader.getFileData())); } @@ -69,9 +69,9 @@ protected void writeCentralDirectory(@Nonnull CentralDirectoryFileHeader directo writeShortLE(os, directory.getInternalFileAttributes()); writeIntLE(os, directory.getExternalFileAttributes()); writeIntLE(os, (int) directory.getRelativeOffsetOfLocalHeader()); - os.write(MemorySegmentUtil.toByteArray(directory.getFileName())); - os.write(MemorySegmentUtil.toByteArray(directory.getExtraField())); - os.write(MemorySegmentUtil.toByteArray(directory.getFileComment())); + os.write(directory.getFileName().get().getBytes()); + os.write(MemorySegmentUtil.toByteArray(directory.getExtraField().get())); + os.write(directory.getFileComment().get().getBytes()); } protected void writeEnd(@Nonnull EndOfCentralDirectory end, @Nonnull OutputStream os) throws IOException { @@ -83,7 +83,7 @@ protected void writeEnd(@Nonnull EndOfCentralDirectory end, @Nonnull OutputStrea writeIntLE(os, (int) end.getCentralDirectorySize()); writeIntLE(os, (int) end.getCentralDirectoryOffset()); writeShortLE(os, end.getZipCommentLength()); - os.write(MemorySegmentUtil.toByteArray(end.getZipComment())); + os.write(end.getZipComment().get().getBytes()); } protected static void writeShortLE(OutputStream os, int value) throws IOException { diff --git a/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java b/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java index f7c48bd..75566cf 100644 --- a/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java +++ b/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java @@ -26,7 +26,7 @@ public class ExtraFieldTime { public static TimeWrapper read(@Nonnull CentralDirectoryFileHeader header) { int extraLen = header.getExtraFieldLength(); if (extraLen > 0 && extraLen < 0xFFFF) { - MemorySegment extra = header.getExtraField(); + MemorySegment extra = header.getExtraField().get(); return read(extra); } return null; @@ -42,7 +42,7 @@ public static TimeWrapper read(@Nonnull CentralDirectoryFileHeader header) { public static TimeWrapper read(@Nonnull LocalFileHeader header) { int extraLen = header.getExtraFieldLength(); if (extraLen > 0 && extraLen < 0xFFFF) { - MemorySegment extra = header.getExtraField(); + MemorySegment extra = header.getExtraField().get(); return read(extra); } return null; diff --git a/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java b/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java index 7ce25cc..8b06156 100644 --- a/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java +++ b/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java @@ -1,9 +1,6 @@ package software.coley.lljzip.util; import software.coley.lljzip.format.model.ZipPart; -import software.coley.lljzip.util.lazy.LazyMemorySegment; -import software.coley.lljzip.util.lazy.LazyInt; -import software.coley.lljzip.util.lazy.LazyLong; import java.lang.foreign.MemorySegment; import java.lang.foreign.ValueLayout; @@ -17,6 +14,7 @@ */ public class MemorySegmentUtil { public static final int WILDCARD = Integer.MIN_VALUE; + public static final MemorySegment EMPTY = MemorySegment.ofArray(new byte[0]); private static final ValueLayout.OfInt LITTLE_INT = ValueLayout.JAVA_INT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); private static final ValueLayout.OfShort LITTLE_SHORT = ValueLayout.JAVA_SHORT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); @@ -319,12 +317,10 @@ public static String toString(MemorySegment data) { * @param localOffset * Local offset from the header offset. * - * @return Lazily populated word. + * @return Value of word. */ - public static LazyInt readLazyWord(MemorySegment data, long headerOffset, int localOffset) { - return new LazyInt(() -> { - return readWord(data, headerOffset + localOffset); - }); + public static int readWord(MemorySegment data, long headerOffset, int localOffset) { + return readWord(data, headerOffset + localOffset); } /** @@ -335,12 +331,10 @@ public static LazyInt readLazyWord(MemorySegment data, long headerOffset, int lo * @param localOffset * Local offset from the header offset. * - * @return Lazily populated quad. + * @return Value of quad. */ - public static LazyInt readLazyQuad(MemorySegment data, long headerOffset, int localOffset) { - return new LazyInt(() -> { - return readQuad(data, headerOffset + localOffset); - }); + public static int readQuad(MemorySegment data, long headerOffset, int localOffset) { + return readQuad(data, headerOffset + localOffset); } /** @@ -351,12 +345,10 @@ public static LazyInt readLazyQuad(MemorySegment data, long headerOffset, int lo * @param localOffset * Local offset from the header offset. * - * @return Lazily populated masked quad. + * @return Value of masked quad. */ - public static LazyInt readLazyMaskedQuad(MemorySegment data, long headerOffset, int localOffset) { - return new LazyInt(() -> { - return readQuad(data, headerOffset + localOffset) & 0xFFFF; - }); + public static int readMaskedQuad(MemorySegment data, long headerOffset, int localOffset) { + return readQuad(data, headerOffset + localOffset) & 0xFFFF; } /** @@ -367,12 +359,10 @@ public static LazyInt readLazyMaskedQuad(MemorySegment data, long headerOffset, * @param localOffset * Local offset from the header offset. * - * @return Lazily populated long word. + * @return Value of long word. */ - public static LazyLong readLazyLongWord(MemorySegment data, long headerOffset, int localOffset) { - return new LazyLong(() -> { - return readWord(data, headerOffset + localOffset); - }); + public static long readLongWord(MemorySegment data, long headerOffset, int localOffset) { + return readWord(data, headerOffset + localOffset); } /** @@ -383,12 +373,10 @@ public static LazyLong readLazyLongWord(MemorySegment data, long headerOffset, i * @param localOffset * Local offset from the header offset. * - * @return Lazily populated masked long quad. + * @return Value of masked long quad. */ - public static LazyLong readLazyMaskedLongQuad(MemorySegment data, long headerOffset, int localOffset) { - return new LazyLong(() -> { - return readQuad(data, headerOffset + localOffset) & 0xFFFFFFFFL; - }); + public static long readMaskedLongQuad(MemorySegment data, long headerOffset, int localOffset) { + return readQuad(data, headerOffset + localOffset) & 0xFFFFFFFFL; } /** @@ -399,28 +387,10 @@ public static LazyLong readLazyMaskedLongQuad(MemorySegment data, long headerOff * @param localOffset * Local offset from the header offset. * - * @return Lazily populated slice. - */ - public static LazyMemorySegment readLazySlice(MemorySegment data, long headerOffset, LazyInt localOffset, LazyInt length) { - return new LazyMemorySegment(() -> { - return data.asSlice(headerOffset + localOffset.get(), length.get()); - }); - } - - /** - * @param data - * Content to get long slice from. - * @param headerOffset - * Offset of {@link ZipPart} header. - * @param localOffset - * Local offset from the header offset. - * - * @return Lazily populated long slice. + * @return Value of slice. */ - public static LazyMemorySegment readLazyLongSlice(MemorySegment data, long headerOffset, LazyInt localOffset, LazyLong length) { - return new LazyMemorySegment(() -> { - return data.asSlice(headerOffset + localOffset.get(), length.get()); - }); + public static MemorySegment readSlice(MemorySegment data, long headerOffset, int localOffset, int length) { + return data.asSlice(headerOffset + localOffset, length); } /** @@ -431,12 +401,10 @@ public static LazyMemorySegment readLazyLongSlice(MemorySegment data, long heade * @param localOffset * Local offset from the header offset. * - * @return Lazily populated long slice. + * @return Value of long slice. */ - public static LazyMemorySegment readLazyLongSlice(MemorySegment data, long headerOffset, LazyLong localOffset, LazyLong length) { - return new LazyMemorySegment(() -> { - return data.asSlice(headerOffset + localOffset.get(), length.get()); - }); + public static MemorySegment readLongSlice(MemorySegment data, long headerOffset, int localOffset, long length) { + return data.asSlice(headerOffset + localOffset, length); } /** @@ -447,11 +415,9 @@ public static LazyMemorySegment readLazyLongSlice(MemorySegment data, long heade * @param localOffset * Local offset from the header offset. * - * @return Lazily populated long slice. + * @return Value of long slice. */ - public static LazyMemorySegment readLazyLongSlice(MemorySegment data, long headerOffset, LazyInt localOffset, long length) { - return new LazyMemorySegment(() -> { - return data.asSlice(headerOffset + localOffset.get(), length); - }); + public static MemorySegment readLongSlice(MemorySegment data, long headerOffset, long localOffset, long length) { + return data.asSlice(headerOffset + localOffset, length); } } diff --git a/src/main/java/software/coley/lljzip/util/data/MemorySegmentData.java b/src/main/java/software/coley/lljzip/util/data/MemorySegmentData.java new file mode 100644 index 0000000..8b1607c --- /dev/null +++ b/src/main/java/software/coley/lljzip/util/data/MemorySegmentData.java @@ -0,0 +1,107 @@ +package software.coley.lljzip.util.data; + +import software.coley.lljzip.util.MemorySegmentUtil; + +import javax.annotation.Nonnull; +import java.lang.foreign.MemorySegment; + +/** + * Wrapper for reading {@link MemorySegment} content. + * + * @author Matt Coley + */ +public interface MemorySegmentData { + @Nonnull + MemorySegment get(); + + default long length() { + return get().byteSize(); + } + + @Nonnull + default MemorySegmentData copy() { + return of(get()); + } + + @Nonnull + static MemorySegmentData empty() { + return FullSegment.EMPTY; + } + + @Nonnull + static MemorySegmentData of(@Nonnull byte[] source) { + if (source.length == 0) + return empty(); + return of(MemorySegment.ofArray(source)); + } + + @Nonnull + static MemorySegmentData of(@Nonnull MemorySegment segment) { + if (segment.byteSize() == 0) + return empty(); + return new FullSegment(segment); + } + + @Nonnull + static MemorySegmentData of(@Nonnull MemorySegment segment, long offset, long length) { + if (length == 0) + return empty(); + return new Caching(new PartialSegment(segment, offset, length)); + } + + class Caching implements MemorySegmentData { + private final MemorySegmentData delegate; + private MemorySegment cached; + + public Caching(@Nonnull MemorySegmentData delegate) { + this.delegate = delegate; + } + + @Nonnull + @Override + public MemorySegment get() { + if (cached == null) + cached = delegate.get(); + return cached; + } + } + + class PartialSegment implements MemorySegmentData { + private final MemorySegment segment; + private final long offset; + private final long length; + + public PartialSegment(@Nonnull MemorySegment segment, long offset, long length) { + this.segment = segment; + this.offset = offset; + this.length = length; + } + + @Nonnull + @Override + public MemorySegment get() { + return segment.asSlice(offset, length); + } + } + + class FullSegment implements MemorySegmentData { + private static final FullSegment EMPTY = new FullSegment(MemorySegmentUtil.EMPTY); + private final MemorySegment segment; + + public FullSegment(@Nonnull MemorySegment segment) { + this.segment = segment; + } + + @Nonnull + @Override + public MemorySegment get() { + return segment; + } + + @Nonnull + @Override + public MemorySegmentData copy() { + return this; + } + } +} diff --git a/src/main/java/software/coley/lljzip/util/data/StringData.java b/src/main/java/software/coley/lljzip/util/data/StringData.java new file mode 100644 index 0000000..ce004ce --- /dev/null +++ b/src/main/java/software/coley/lljzip/util/data/StringData.java @@ -0,0 +1,123 @@ +package software.coley.lljzip.util.data; + +import software.coley.lljzip.util.MemorySegmentUtil; + +import javax.annotation.Nonnull; +import java.lang.foreign.MemorySegment; + +/** + * Wrapper for reading {@link String} content from a variety of sources. + * + * @author Matt Coley + */ +public interface StringData { + @Nonnull + String get(); + + @Nonnull + default StringData substring(int begin, int end) { + return of(get().substring(begin, end)); + } + + @Nonnull + default StringData copy() { + return of(get()); + } + + @Nonnull + static StringData empty() { + return Literal.EMPTY; + } + + @Nonnull + static StringData of(@Nonnull String source) { + if (source.isEmpty()) + return empty(); + return new Literal(source); + } + + @Nonnull + static StringData of(@Nonnull MemorySegment segment) { + if (segment.byteSize() == 0) + return empty(); + return new Caching(new FullSegment(segment)); + } + + @Nonnull + static StringData of(@Nonnull MemorySegment segment, long offset, long length) { + if (length == 0) + return empty(); + return new Caching(new PartialSegment(segment, offset, length)); + } + + class Caching implements StringData { + + private final StringData delegate; + private String cached; + + public Caching(@Nonnull StringData delegate) { + this.delegate = delegate; + } + + @Nonnull + @Override + public String get() { + if (cached == null) + cached = delegate.get(); + return cached; + } + } + + class PartialSegment implements StringData { + private final MemorySegment segment; + private final long offset; + private final long length; + + public PartialSegment(@Nonnull MemorySegment segment, long offset, long length) { + this.segment = segment; + this.offset = offset; + this.length = length; + } + + @Nonnull + @Override + public String get() { + return MemorySegmentUtil.readString(segment, offset, length); + } + } + + class FullSegment implements StringData { + private final MemorySegment segment; + + public FullSegment(@Nonnull MemorySegment segment) { + this.segment = segment; + } + + @Nonnull + @Override + public String get() { + return MemorySegmentUtil.toString(segment); + } + } + + class Literal implements StringData { + private static final Literal EMPTY = new Literal(""); + private final String content; + + public Literal(@Nonnull String content) { + this.content = content; + } + + @Nonnull + @Override + public String get() { + return content; + } + + @Nonnull + @Override + public StringData copy() { + return this; + } + } +} diff --git a/src/main/java/software/coley/lljzip/util/lazy/Lazy.java b/src/main/java/software/coley/lljzip/util/lazy/Lazy.java deleted file mode 100644 index daeb566..0000000 --- a/src/main/java/software/coley/lljzip/util/lazy/Lazy.java +++ /dev/null @@ -1,33 +0,0 @@ -package software.coley.lljzip.util.lazy; - -import javax.annotation.Nonnull; - -/** - * Common lazy type. - * - * @param - * Lazy supplier type. - */ -public abstract class Lazy{ - protected final S lookup; - protected boolean set; - protected String id = ""; - - public Lazy(@Nonnull S lookup) { - this.lookup = lookup; - } - - /** - * @param id - * Value id. - * @param- * Self type. - * - * @return Self. - */ - @SuppressWarnings("unchecked") - public > L withId(String id) { - this.id = id; - return (L) this; - } -} diff --git a/src/main/java/software/coley/lljzip/util/lazy/LazyInt.java b/src/main/java/software/coley/lljzip/util/lazy/LazyInt.java deleted file mode 100644 index 28dcd46..0000000 --- a/src/main/java/software/coley/lljzip/util/lazy/LazyInt.java +++ /dev/null @@ -1,90 +0,0 @@ -package software.coley.lljzip.util.lazy; - -import javax.annotation.Nonnull; -import java.util.function.IntSupplier; - -/** - * Lazy int getter. - */ -public class LazyInt extends Lazy { - private int value; - - /** - * @param lookup - * Lazy lookup. - */ - public LazyInt(@Nonnull IntSupplier lookup) { - super(lookup); - } - - /** - * @return Copy. - */ - @Nonnull - public LazyInt copy() { - LazyInt copy = new LazyInt(lookup); - copy.id = id; - if (set) copy.set(value); - return copy; - } - - /** - * @param value - * Value to add to the current. - * - * @return New value that maps the current value plus the additional value. - */ - public LazyInt add(int value) { - return new LazyInt(() -> value + lookup.getAsInt()); - } - - /** - * @param value - * Value to add to the current. - * - * @return New value that maps the current value plus the additional value. - */ - public LazyInt add(@Nonnull LazyInt value) { - return new LazyInt(() -> value.get() + lookup.getAsInt()); - } - - /** - * @param value - * Int value. - */ - public void set(int value) { - set = true; - this.value = value; - } - - /** - * @return Int value. - */ - public int get() { - if (!set) { - value = lookup.getAsInt(); - set = true; - } - return value; - } - - @Override - public String toString() { - return id + " " + get(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - LazyInt lazyInt = (LazyInt) o; - - return get() == lazyInt.get(); - } - - @Override - public int hashCode() { - return get(); - } -} diff --git a/src/main/java/software/coley/lljzip/util/lazy/LazyIntChain.java b/src/main/java/software/coley/lljzip/util/lazy/LazyIntChain.java deleted file mode 100644 index ab93f12..0000000 --- a/src/main/java/software/coley/lljzip/util/lazy/LazyIntChain.java +++ /dev/null @@ -1,22 +0,0 @@ -package software.coley.lljzip.util.lazy; - -import javax.annotation.Nonnull; - -/** - * Lazy int getter. - */ -public class LazyIntChain extends LazyInt { - /** - * @param values - * Chained values to combined as a lookup. - */ - public LazyIntChain(@Nonnull LazyInt... values) { - super(() -> { - int sum = 0; - for (LazyInt lazy : values) { - sum += lazy.get(); - } - return sum; - }); - } -} diff --git a/src/main/java/software/coley/lljzip/util/lazy/LazyLong.java b/src/main/java/software/coley/lljzip/util/lazy/LazyLong.java deleted file mode 100644 index 424671c..0000000 --- a/src/main/java/software/coley/lljzip/util/lazy/LazyLong.java +++ /dev/null @@ -1,71 +0,0 @@ -package software.coley.lljzip.util.lazy; - -import javax.annotation.Nonnull; -import java.util.function.LongSupplier; - -/** - * Lazy int getter. - */ -public class LazyLong extends Lazy { - private long value; - - /** - * @param lookup - * Lazy lookup. - */ - public LazyLong(@Nonnull LongSupplier lookup) { - super(lookup); - } - - /** - * @return Copy. - */ - @Nonnull - public LazyLong copy() { - LazyLong copy = new LazyLong(lookup); - copy.id = id; - if (set) copy.set(value); - return copy; - } - - /** - * @param value - * Long value. - */ - public void set(long value) { - set = true; - this.value = value; - } - - /** - * @return Long value. - */ - public long get() { - if (!set) { - value = lookup.getAsLong(); - set = true; - } - return value; - } - - @Override - public String toString() { - return id + " " + get(); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - LazyLong lazyLong = (LazyLong) o; - - return get() == lazyLong.get(); - } - - @Override - public int hashCode() { - long v = get(); - return (int) (v ^ (v >>> 32)); - } -} diff --git a/src/main/java/software/coley/lljzip/util/lazy/LazyMemorySegment.java b/src/main/java/software/coley/lljzip/util/lazy/LazyMemorySegment.java deleted file mode 100644 index 983f2fb..0000000 --- a/src/main/java/software/coley/lljzip/util/lazy/LazyMemorySegment.java +++ /dev/null @@ -1,73 +0,0 @@ -package software.coley.lljzip.util.lazy; - -import javax.annotation.Nonnull; -import java.lang.foreign.MemorySegment; -import java.util.Objects; -import java.util.function.Supplier; - -/** - * Lazy {@link MemorySegment} getter. - */ -public class LazyMemorySegment extends Lazy > { - private MemorySegment value; - - /** - * @param lookup - * Lazy lookup. - */ - public LazyMemorySegment(@Nonnull Supplier lookup) { - super(lookup); - } - - /** - * @return Copy. - */ - @Nonnull - public LazyMemorySegment copy() { - LazyMemorySegment copy = new LazyMemorySegment(lookup); - copy.id = id; - if (set) copy.set(value); - return copy; - } - - /** - * @param value - * Data value. - */ - public void set(@Nonnull MemorySegment value) { - set = true; - this.value = value; - } - - /** - * @return Data value. - */ - @Nonnull - public MemorySegment get() { - if (!set) { - value = lookup.get(); - set = true; - } - return value; - } - - @Override - public String toString() { - return id + " data[" + get().byteSize() + "]"; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - LazyMemorySegment that = (LazyMemorySegment) o; - - return Objects.equals(get(), that.get()); - } - - @Override - public int hashCode() { - return Objects.hashCode(get()); - } -} diff --git a/src/test/java/software/coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java b/src/test/java/software/coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java index 80a9e19..95b8104 100644 --- a/src/test/java/software/coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java +++ b/src/test/java/software/coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java @@ -32,7 +32,7 @@ public void test(String name) { Path path = Paths.get("src/test/resources/" + name); try { - ZipArchive zipJvm = ZipIO.read(path, new JvmZipReader(false)); + ZipArchive zipJvm = ZipIO.read(path, new JvmZipReader(false, false)); ZipArchive zipAdapting = ZipIO.readAdaptingIO(path); int sizeDel = zipAdapting.getLocalFiles().size(); int sizeJvm = zipJvm.getLocalFiles().size(); diff --git a/src/test/java/software/coley/lljzip/PartParseTests.java b/src/test/java/software/coley/lljzip/PartParseTests.java index ee0ee11..17b4d1d 100644 --- a/src/test/java/software/coley/lljzip/PartParseTests.java +++ b/src/test/java/software/coley/lljzip/PartParseTests.java @@ -171,7 +171,7 @@ public void testLocalHeaderDetectMismatch() { ZipArchive zipStdAndAdopt = ZipIO.read(path, new ForwardScanZipReader() { @Override public void postProcessLocalFileHeader(@Nonnull LocalFileHeader file) { - file.adoptLinkedCentralDirectoryValues(); + assertDoesNotThrow(() -> file.adoptLinkedCentralDirectoryValues()); } }); LocalFileHeader helloAdopted = zipStdAndAdopt.getLocalFileByName("Hello.class");