// Copyright 2015 The Bazel Authors. All rights reserved. // // 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.google.devtools.build.android.ziputils; import static com.google.devtools.build.android.ziputils.DataDescriptor.EXTLEN; import static com.google.devtools.build.android.ziputils.DataDescriptor.EXTSIZ; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENFLG; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENLEN; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENOFF; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENSIZ; import static com.google.devtools.build.android.ziputils.DirectoryEntry.CENTIM; import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCFLG; import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCLEN; import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCSIZ; import static com.google.devtools.build.android.ziputils.LocalFileHeader.LOCTIM; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * Zip file builder for testing, For now it only supports building * valid zip files. */ class ZipFileBuilder { private final List<FileInfo> input; private static final Charset CHARSET = Charset.forName("UTF-8"); private static final byte[] EMPTY = {}; public ZipFileBuilder() { input = new ArrayList<>(); } public ZipFileBuilder add(String filename, String content) { input.add(new FileInfo(filename, content.getBytes(Charset.defaultCharset()))); return this; } public ZipFileBuilder add(FileInfo fileInfo) { input.add(fileInfo); return this; } public void create(String filename) throws IOException { ZipOut out = new ZipOut(FileSystem.fileSystem().getOutputChannel(filename, false), filename); for (FileInfo info : input) { int compressed = info.compressedSize(); int uncompressed = info.uncompressedSize(); int dirCompressed = info.dirCompressedSize(); int dirUncompressed = info.dirUncompressedSize(); short flags = info.flags(); DirectoryEntry entry = DirectoryEntry.allocate(info.name, info.extra, info.comment); out.nextEntry(entry) .set(CENOFF, out.fileOffset()) .set(CENFLG, flags) .set(CENTIM, info.date) .set(CENLEN, dirUncompressed) .set(CENSIZ, dirCompressed); LocalFileHeader header = LocalFileHeader.allocate(info.name, null) .set(LOCFLG, flags) .set(LOCTIM, info.date) .set(LOCLEN, uncompressed) .set(LOCSIZ, compressed); out.write(header); out.write(ByteBuffer.wrap(info.data)); if (flags != 0) { DataDescriptor desc = DataDescriptor.allocate() .set(EXTLEN, dirUncompressed) .set(EXTSIZ, dirCompressed); out.write(desc); } } out.close(); } public static class FileInfo { private final String name; private final short method; private final int date; private final int uncompressed; private final byte[] data; private final byte[] extra; private final String comment; boolean maskSize; static final short STORED = 0; static final short DEFLATED = 8; public FileInfo(String filename, String content) { this(filename, DosTime.EPOCH.time, STORED, 0, (content == null ? EMPTY : content.getBytes(CHARSET)), null, null); } public FileInfo(String filename, byte[] data) { this(filename, DosTime.EPOCH.time, STORED, 0, data, null, null); } public FileInfo(String filename, byte[] data, int uncompressed) { this(filename, DosTime.EPOCH.time, DEFLATED, uncompressed, data, null, null); } public FileInfo(String filename, int dosTime, String content) { this(filename, dosTime, STORED, 0, (content == null ? EMPTY : content.getBytes(CHARSET)), null, null); } public FileInfo(String filename, int dosTime, byte[] data) { this(filename, dosTime, STORED, 0, data, null, null); } public FileInfo(String filename, int dosTime, byte[] data, int uncompressed) { this(filename, dosTime, DEFLATED, uncompressed, data, null, null); } public FileInfo(String filename, int dosTime, short method, int uncompressed, byte[] content, byte[] extra, String comment) { this.name = filename; this.date = dosTime; this.method = method; this.uncompressed = uncompressed; this.data = content; this.extra = extra; this.comment = comment; maskSize = false; } public void setMaskSize(boolean ignore) { maskSize = ignore; } int compressedSize() { return method != 0 && !maskSize ? data.length : 0; } int uncompressedSize() { return method == 0 ? data.length : maskSize ? 0 : uncompressed; } int dirCompressedSize() { return method == 0 ? 0 : data.length; } int dirUncompressedSize() { return method == 0 ? data.length : uncompressed; } short flags() { return method != 0 && uncompressed == 0 ? LocalFileHeader.SIZE_MASKED_FLAG : 0; } } }