All Downloads are FREE. Search and download functionalities are using the official Maven repository.

software.coley.llzip.format.model.LocalFileHeader Maven / Gradle / Ivy

package software.coley.llzip.format.model;

import software.coley.llzip.format.compression.Decompressor;
import software.coley.llzip.format.read.ZipReaderStrategy;
import software.coley.llzip.util.ByteData;
import software.coley.llzip.util.lazy.LazyByteData;
import software.coley.llzip.util.lazy.LazyInt;
import software.coley.llzip.util.lazy.LazyLong;

import java.io.IOException;
import java.util.Objects;

import static software.coley.llzip.format.compression.ZipCompressions.STORED;

/**
 * ZIP LocalFileHeader structure.
 * 
 * {@code
 *     SIGNATURE Signature ;
 *     WORD VersionNeededToExtract ;
 *     WORD GeneralPurposeBitFlag ;
 *     COMPRESSION_METHOD CompressionMethod ;
 *     DOSTIME LastModFileTime ;
 *     DOSDATE LastModFileDate ;
 *     DWORD Crc32 ;
 *     DWORD CompressedSize ;
 *     DWORD UncompressedSize ;
 *     WORD  FileNameLength ;
 *     WORD  ExtraFieldLength ;
 *     char  FileName[FileNameLength] ;
 *     blob  ExtraField[ExtraFieldLength] ;
 *     blob  FileData[CompressedSize] ;
 * }
 * 
* * @author Matt Coley */ public class LocalFileHeader extends AbstractZipFileHeader { protected static final long MIN_FIXED_SIZE = 30; private transient CentralDirectoryFileHeader linkedDirectoryFileHeader; // LocalFileHeader spec (plus common elements between this and central file) protected LazyByteData fileData; // Caches private transient LazyLong fileDataLength; private transient ByteData data; @Override public void read(ByteData data, long offset) { super.read(data, offset); this.data = data; versionNeededToExtract = readWord(data, 4); generalPurposeBitFlag = readWord(data, 6); compressionMethod = readWord(data, 8); lastModFileTime = readWord(data, 10); lastModFileDate = readWord(data, 12); crc32 = readQuad(data, 14); compressedSize = readMaskedLongQuad(data, 18); uncompressedSize = readMaskedLongQuad(data, 22); fileNameLength = readWord(data, 26); extraFieldLength = readWord(data, 28); fileName = readSlice(data, new LazyInt(() -> 30), fileNameLength); extraField = readSlice(data, fileNameLength.add(30), extraFieldLength); fileDataLength = new LazyLong(() -> { long fileDataLength; if (compressionMethod.get() == STORED) { fileDataLength = uncompressedSize.get(); } else { fileDataLength = compressedSize.get(); } return fileDataLength; }); fileData = readLongSlice(data, fileNameLength.add(extraFieldLength).add(30), fileDataLength); } /** * Checks if the contents do not match those described in {@link CentralDirectoryFileHeader}. * If this is the case you will probably want to change your ZIP reading configuration. *

* You can override the {@link ZipReaderStrategy#postProcessLocalFileHeader(LocalFileHeader)} * to call {@link #adoptLinkedCentralDirectoryValues()}. The implementations of {@link ZipReaderStrategy} * are non-final, so you can extend them to add the override. * * @return {@code true} when the contents of this file header do not match those outlined by the associated * {@link CentralDirectoryFileHeader}. */ public boolean hasDifferentValuesThanCentralDirectoryHeader() { if (linkedDirectoryFileHeader == null) return false; if (getVersionNeededToExtract() != linkedDirectoryFileHeader.getVersionNeededToExtract()) return true; if (getGeneralPurposeBitFlag() != linkedDirectoryFileHeader.getGeneralPurposeBitFlag()) return true; if (getCompressionMethod() != linkedDirectoryFileHeader.getCompressionMethod()) return true; if (getLastModFileTime() != linkedDirectoryFileHeader.getLastModFileTime()) return true; if (getLastModFileDate() != linkedDirectoryFileHeader.getLastModFileDate()) return true; if (getCrc32() != linkedDirectoryFileHeader.getCrc32()) return true; if (getCompressedSize() != linkedDirectoryFileHeader.getCompressedSize()) return true; if (getUncompressedSize() != linkedDirectoryFileHeader.getUncompressedSize()) return true; if (getFileNameLength() != linkedDirectoryFileHeader.getFileNameLength()) return true; return !Objects.equals(getFileNameAsString(), linkedDirectoryFileHeader.getFileNameAsString()); } /** * When called before being {@link #freeze() frozen} values can be adopted from the linked * {@link #getLinkedDirectoryFileHeader() CentralDirectoryFileHeader}. *

* 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() { if (data != null && linkedDirectoryFileHeader != null) { setVersionNeededToExtract(linkedDirectoryFileHeader.getVersionNeededToExtract()); setGeneralPurposeBitFlag(linkedDirectoryFileHeader.getGeneralPurposeBitFlag()); setCompressionMethod(linkedDirectoryFileHeader.getCompressionMethod()); setLastModFileTime(linkedDirectoryFileHeader.getLastModFileTime()); setLastModFileDate(linkedDirectoryFileHeader.getLastModFileDate()); setCrc32(linkedDirectoryFileHeader.getCrc32()); setCompressedSize(linkedDirectoryFileHeader.getCompressedSize()); setUncompressedSize(linkedDirectoryFileHeader.getUncompressedSize()); setFileNameLength(linkedDirectoryFileHeader.getFileNameLength()); fileName = readSlice(data, new LazyInt(() -> 30), fileNameLength); extraField = readSlice(data, fileNameLength.add(30), extraFieldLength); fileDataLength = new LazyLong(() -> { long fileDataLength; if (compressionMethod.get() == STORED) { fileDataLength = uncompressedSize.get(); } else { fileDataLength = compressedSize.get(); } return fileDataLength; }); fileData = readLongSlice(data, fileNameLength.add(extraFieldLength).add(30), fileDataLength); } } /** * Clears the reference to the source {@link ByteData}, preventing further modification. *

* Prevents usage of {@link #adoptLinkedCentralDirectoryValues()}. */ public void freeze() { data = null; } @Override public long length() { return MIN_FIXED_SIZE + fileNameLength.get() + extraFieldLength.get() + fileDataLength.get(); } @Override public PartType type() { return PartType.LOCAL_FILE_HEADER; } @Override public long offset() { return offset; } /** * @param decompressor * Decompressor implementation. * * @return Decompressed bytes. * * @throws IOException * When the decompressor fails. */ public ByteData decompress(Decompressor decompressor) throws IOException { return decompressor.decompress(this, fileData.get()); } /** * @return The central directory file header this file is associated with. */ public CentralDirectoryFileHeader getLinkedDirectoryFileHeader() { return linkedDirectoryFileHeader; } /** * @param directoryFileHeader * The central directory file header this file is associated with. */ public void link(CentralDirectoryFileHeader directoryFileHeader) { this.linkedDirectoryFileHeader = directoryFileHeader; } /** * @return Compressed file contents. * * @see #decompress(Decompressor) Decompresses this data. */ public ByteData getFileData() { return fileData.get(); } /** * @param fileData * Compressed file contents. */ public void setFileData(ByteData fileData) { this.fileData.set(fileData); } @Override public String toString() { return "LocalFileHeader{" + "fileData=" + fileData + ", fileDataLength=" + fileDataLength + ", data=" + data + ", versionNeededToExtract=" + versionNeededToExtract + ", generalPurposeBitFlag=" + generalPurposeBitFlag + ", compressionMethod=" + compressionMethod + ", lastModFileTime=" + lastModFileTime + ", lastModFileDate=" + lastModFileDate + ", crc32=" + crc32 + ", compressedSize=" + compressedSize + ", uncompressedSize=" + uncompressedSize + ", fileNameLength=" + fileNameLength + ", extraFieldLength=" + extraFieldLength + ", fileName='" + getFileNameAsString() + '\'' + ", extraField='" + getExtraFieldAsString() + '\'' + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LocalFileHeader that = (LocalFileHeader) o; 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; 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); result = 31 * result + (fileData != null ? fileData.hashCode() : 0); return result; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy