CFHeader.java
/**
* Copyright 2019 Emmanuel Bourg
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.jsign.mscab;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SeekableByteChannel;
import java.security.MessageDigest;
/**
* Cabinet File Header structure (CFHEADER):
*
* <pre>
* signature 4 bytes (0x4643534d: 'MSCF')
* reserved1 (former header checksum) 4 bytes
* size of the cabinet file 4 bytes
* reserved2 (former folders checksum) 4 bytes
* offset of the first CFFILE entry 4 bytes
* reserved3 (former files checksum) 4 bytes
* minor format version 1 byte
* major format version 1 byte
* number of CFFOLDER entries 2 bytes
* number of CFFILE entries 2 bytes
* flags 2 bytes
* set identifier 2 bytes
* cabinet sequential number 2 bytes
* size of per-cabinet reserved area 2 bytes (optional)
* size of per-folder reserved area 1 byte (optional)
* size of per-datablock reserved area 1 byte (optional)
* reserved area (variable size, optional)
* file name of the previous cabinet (variable size, optional)
* media with the previous cabinet (variable size, optional)
* file name of the next cabinet (variable size, optional)
* media with the next cabinet (variable size, optional)
* </pre>
*
* @since 4.0
*/
class CFHeader {
public static final int SIGNATURE = 0x4643534d; // MSCF
public long csumHeader; // u4
public long cbCabinet; // u4
public long csumFolders; // u4
public long coffFiles; // u4
public long csumFiles; // u4
public byte versionMinor; // u1
public byte versionMajor; // u1
public int cFolders; // u2
public int cFiles; // u2
public int flags; // u2
public int setID; // u2
public int iCabinet; // u2
public int cbCFHeader; // u2
public short cbCFFolder; // u1
public short cbCFData; // u1
public byte[] abReserved;
/**
* FLAG_PREV_CABINET is set if this cabinet file is not the first in a set
* of cabinet files. When this bit is set, the szCabinetPrev and szDiskPrev
* fields are present in this CFHEADER.
*/
public static final int FLAG_PREV_CABINET = 0b00000001;
/**
* FLAG_NEXT_CABINET is set if this cabinet file is not the last in a set
* of cabinet files. When this bit is set, the szCabinetNext and szDiskNext
* fields are present in this CFHEADER.
*/
public static final int FLAG_NEXT_CABINET = 0b00000010;
/**
* FLAG_RESERVE_PRESENT is set if this cabinet file contains any reserved
* fields. When this bit is set, the cbCFHeader, cbCFFolder, and cbCFData
* fields are present in this CFHEADER.
*/
public static final int FLAG_RESERVE_PRESENT = 0b00000100;
/** Base size of the header (with no optional fields) */
public static final int BASE_SIZE = 36;
public CFHeader() {
}
public CFHeader(CFHeader header) {
this.csumHeader = header.csumHeader;
this.cbCabinet = header.cbCabinet;
this.csumFolders = header.csumFolders;
this.coffFiles = header.coffFiles;
this.csumFiles = header.csumFiles;
this.versionMinor = header.versionMinor;
this.versionMajor = header.versionMajor;
this.cFolders = header.cFolders;
this.cFiles = header.cFiles;
this.flags = header.flags;
this.setID = header.setID;
this.iCabinet = header.iCabinet;
this.cbCFHeader = header.cbCFHeader;
this.cbCFFolder = header.cbCFFolder;
this.cbCFData = header.cbCFData;
this.abReserved = header.abReserved != null ? header.abReserved.clone() : null;
}
public void read(SeekableByteChannel channel) throws IOException {
if ((channel.size()) < BASE_SIZE + CFFolder.BASE_SIZE) {
throw new IOException("MSCabinet file too short");
}
ByteBuffer buffer = ByteBuffer.allocate(BASE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer);
buffer.flip();
int signature = buffer.getInt();
if (signature != SIGNATURE) {
throw new IOException("Invalid MSCabinet header signature " + String.format("0x%04x", signature & 0xFFFFFFFFL));
}
this.csumHeader = buffer.getInt() & 0xFFFFFFFFL; // u4
this.cbCabinet = buffer.getInt() & 0xFFFFFFFFL; // u4 H
this.csumFolders = buffer.getInt() & 0xFFFFFFFFL; // u4 H
this.coffFiles = buffer.getInt() & 0xFFFFFFFFL; // u4 H
this.csumFiles = buffer.getInt() & 0xFFFFFFFFL; // u4 H
this.versionMinor = buffer.get(); // u1 H
this.versionMajor = buffer.get(); // u1 H
this.cFolders = buffer.getShort() & 0xFFFF; // u2 H
this.cFiles = buffer.getShort() & 0xFFFF; // u2 H
this.flags = buffer.getShort() & 0xFFFF; // u2 H
this.setID = buffer.getShort(); // u2 H
this.iCabinet = buffer.getShort() & 0xFFFF; // u2
this.abReserved = null;
if (isReservePresent()) {
buffer.clear();
buffer.limit(4);
channel.read(buffer);
buffer.flip();
this.cbCFHeader = buffer.getShort() & 0xFFFF; // u2
this.cbCFFolder = (short) (buffer.get() & 0xFF); // u1
this.cbCFData = (short) (buffer.get() & 0xFF); // u1
if (this.cbCFHeader > 0) {
this.abReserved = new byte[this.cbCFHeader];
channel.read(ByteBuffer.wrap(this.abReserved));
}
}
}
public void write(ByteBuffer buffer) {
buffer.putInt(SIGNATURE);
buffer.putInt((int) this.csumHeader);
buffer.putInt((int) this.cbCabinet);
buffer.putInt((int) this.csumFolders);
buffer.putInt((int) this.coffFiles);
buffer.putInt((int) this.csumFiles);
buffer.put(this.versionMinor);
buffer.put(this.versionMajor);
buffer.putShort((short) this.cFolders);
buffer.putShort((short) this.cFiles);
buffer.putShort((short) this.flags);
buffer.putShort((short) this.setID);
buffer.putShort((short) this.iCabinet);
if (isReservePresent()) {
buffer.putShort((short) this.cbCFHeader);
buffer.put((byte) this.cbCFFolder);
buffer.put((byte) this.cbCFData);
if (this.cbCFHeader > 0) {
buffer.put(this.abReserved);
}
}
}
public int getHeaderSize() {
if (isReservePresent()) {
return BASE_SIZE + 4 + this.cbCFHeader;
} else {
return BASE_SIZE;
}
}
public void headerDigestUpdate(MessageDigest digest) {
ByteBuffer buffer = ByteBuffer.allocate(BASE_SIZE).order(ByteOrder.LITTLE_ENDIAN);
buffer.putInt(SIGNATURE);
// the checksum of the header is skipped
buffer.putInt((int) this.cbCabinet);
buffer.putInt((int) this.csumFolders);
buffer.putInt((int) this.coffFiles);
buffer.putInt((int) this.csumFiles);
buffer.put(this.versionMinor);
buffer.put(this.versionMajor);
buffer.putShort((short) this.cFolders);
buffer.putShort((short) this.cFiles);
buffer.putShort((short) this.flags);
buffer.putShort((short) this.setID);
buffer.putShort((short) this.iCabinet);
buffer.flip();
digest.update(buffer);
if (this.abReserved != null) {
digest.update(abReserved, 0, 2); // 0x0000
}
}
public boolean hasPreviousCabinet() {
return (FLAG_PREV_CABINET & flags) != 0;
}
public boolean hasNextCabinet() {
return (FLAG_NEXT_CABINET & flags) != 0;
}
public boolean isReservePresent() {
return (FLAG_RESERVE_PRESENT & flags) != 0;
}
public boolean hasSignature() {
return this.abReserved != null;
}
public CABSignature getSignature() {
return abReserved != null ? new CABSignature(abReserved) : null;
}
}