blob: 9f704a740496e652b21491af9f258ef383e5e19f [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* 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 com.android.zipflinger;
import com.android.annotations.NonNull;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
class EndOfCentralDirectory {
private static final int SIGNATURE = 0x06054b50;
static final int SIZE = 22;
private static final long MAX_SIZE = Ints.USHRT_MAX + SIZE;
static final short DISK_NUMBER = 0;
private int numEntries;
private Location location;
private Location cdLocation;
private EndOfCentralDirectory() {
this.numEntries = 0;
this.location = Location.INVALID;
this.cdLocation = Location.INVALID;
}
private void parse(@NonNull ByteBuffer buffer) {
// skip diskNumber (2) + cdDiskNumber (2) + #entries (2)
buffer.position(buffer.position() + 6);
numEntries = Ints.ushortToInt(buffer.getShort());
long cdSize = Ints.uintToLong(buffer.getInt());
long cdOffset = Ints.uintToLong(buffer.getInt());
cdLocation = new Location(cdOffset, cdSize);
buffer.position(buffer.position() + 2); // Skip comment length
}
@NonNull
public Location getLocation() {
return location;
}
@NonNull
public Location getCdLocation() {
return cdLocation;
}
public int numEntries() {
return numEntries;
}
public void setLocation(@NonNull Location location) {
this.location = location;
}
// Search the EOCD. If not found the returned object location will be set to Location.INVALID.
@NonNull
public static EndOfCentralDirectory find(@NonNull FileChannel channel) throws IOException {
long fileSize = channel.size();
EndOfCentralDirectory eocd = new EndOfCentralDirectory();
if (fileSize < SIZE) {
return eocd;
}
int sizeToRead = Math.toIntExact(Math.min(fileSize, MAX_SIZE));
long offset = fileSize - sizeToRead;
ByteBuffer buffer = ByteBuffer.allocate(sizeToRead).order(ByteOrder.LITTLE_ENDIAN);
channel.read(buffer, offset);
buffer.position(buffer.capacity() - SIZE);
while (true) {
int signature = buffer.getInt(); // Read 4 bytes.
if (signature == EndOfCentralDirectory.SIGNATURE) {
eocd.parse(buffer);
eocd.setLocation(new Location(offset + buffer.position() - SIZE, SIZE));
break;
}
if (buffer.position() <= 4) {
break;
}
buffer.position(buffer.position() - Integer.BYTES - 1); // Backtrack 5 bytes.
}
return eocd;
}
@NonNull
public static Location write(
@NonNull ZipWriter writer, @NonNull Location cdLocation, long entriesCount)
throws IOException {
boolean isZip64 = Zip64.needZip64Footer(entriesCount, cdLocation);
short numEntries = isZip64 ? Zip64.SHORT_MAGIC : Ints.longToUshort(entriesCount);
int eocdSize = isZip64 ? Zip64.INT_MAGIC : Ints.longToUint(cdLocation.size());
int eocdOffset = isZip64 ? Zip64.INT_MAGIC : Ints.longToUint(cdLocation.first);
ByteBuffer eocd = ByteBuffer.allocate(SIZE).order(ByteOrder.LITTLE_ENDIAN);
eocd.putInt(SIGNATURE);
eocd.putShort(DISK_NUMBER);
eocd.putShort((short) 0); // cd disk number
eocd.putShort(numEntries);
eocd.putShort(numEntries);
eocd.putInt(eocdSize);
eocd.putInt(eocdOffset);
eocd.putShort((short) 0); // comment size
eocd.rewind();
long position = writer.position();
writer.write(eocd);
return new Location(position, SIZE);
}
}