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

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

package software.coley.llzip.format.model;

import software.coley.llzip.format.compression.ZipCompressions;
import software.coley.llzip.util.ByteData;
import software.coley.llzip.util.lazy.LazyInt;
import software.coley.llzip.util.lazy.LazyLong;

import java.util.NavigableSet;


/**
 * An extension of {@link LocalFileHeader} with adjustments to the file-data parse logic to support
 * how the Hotspot JVM handles parsing jar files.
 *
 * @author Matt Coley
 * @author Wolfie / win32kbase (Reverse engineering JVM specific zip handling)
 */
public class JvmLocalFileHeader extends LocalFileHeader {
	private final NavigableSet offsets;
	private long dataOffsetStart;
	private long dataOffsetEnd;
	private boolean foundData;
	private ByteData data;

	/**
	 * @param offsets
	 * 		Set containing all local file header offsets.
	 */
	public JvmLocalFileHeader(NavigableSet offsets) {
		this.offsets = offsets;
	}

	@Override
	public void read(ByteData data, long offset) {
		super.read(data, offset);

		// JVM file data reading does NOT use the compressed/uncompressed fields.
		// Instead, it scans data until the next header.
		long dataOffsetStart = offset + MIN_FIXED_SIZE + getFileNameLength() + getExtraFieldLength();
		Long dataOffsetEnd = offsets.ceiling(dataOffsetStart);
		this.dataOffsetStart = dataOffsetStart;
		this.dataOffsetEnd = dataOffsetEnd == null ? -1 : dataOffsetEnd;
		if (dataOffsetEnd != null) {
			// Valid data range found, map back to (localOffset, range)
			fileData = readLongSlice(data,
					new LazyLong(() -> dataOffsetStart - offset),
					new LazyLong(() -> dataOffsetEnd - offset));
			foundData = true;
		} else {
			// Keep data reference to attempt restoration with later when linking to the CEN.
			this.data = data;
		}
	}

	@Override
	public void link(CentralDirectoryFileHeader directoryFileHeader) {
		super.link(directoryFileHeader);

		// JVM trusts central directory file header contents over local
		//  - Using fields as this maintains the lazy model
		compressionMethod = directoryFileHeader.compressionMethod;
		compressedSize = directoryFileHeader.compressedSize;
		uncompressedSize = directoryFileHeader.uncompressedSize;
		fileName = directoryFileHeader.fileName;
		generalPurposeBitFlag = directoryFileHeader.generalPurposeBitFlag;
		crc32 = directoryFileHeader.crc32;
		lastModFileDate = directoryFileHeader.lastModFileDate;
		lastModFileTime = directoryFileHeader.lastModFileTime;

		// Update file data with new compressed/uncompressed size if it was not able to be found previously
		// with only the local data available.
		if (!foundData) {
			// Extract updated length from CEN values
			long fileDataLength;
			if (getCompressionMethod() == ZipCompressions.STORED) {
				fileDataLength = getUncompressedSize() & 0xFFFFFFFFL;
			} else {
				fileDataLength = getCompressedSize() & 0xFFFFFFFFL;
			}

			// Only allow lengths that are within a sensible range.
			// 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 + offset < dataOffsetEnd) {
				fileData = readLongSlice(data,
						new LazyLong(() -> dataOffsetStart - offset),
						new LazyLong(() -> fileDataLength));
				data = null;
			}
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy