// 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.zip; import java.io.IOException; import java.io.InputStream; import java.util.Iterator; import java.util.LinkedHashMap; /** * A list of {@link ExtraData} records to be associated with a {@link ZipFileEntry}. Supports * creating the list directly from a byte array and modifying the list without reallocating the * underlying buffer. */ public class ExtraDataList { public static final short ZIP64 = 0x0001; public static final short EXTENDED_TIMESTAMP = 0x5455; // Some documentation says that this is actually 0x7855, but zip files do not seem to corroborate // this public static final short INFOZIP_UNIX_NEW = 0x7875; private final LinkedHashMap<Short, ExtraData> entries; /** * Create a new empty extra data list. * * <p><em>NOTE:</em> entries in a list created this way will be backed by their own storage. */ public ExtraDataList() { entries = new LinkedHashMap<>(); } public ExtraDataList(ExtraDataList other) { this.entries = new LinkedHashMap<>(); this.entries.putAll(other.entries); } /** * Creates an extra data list from the given extra data records. * * <p><em>NOTE:</em> entries in a list created this way will be backed by their own storage. * * @param extra the extra data records */ public ExtraDataList(ExtraData... extra) { this(); for (ExtraData e : extra) { add(e); } } /** * Creates an extra data list from the entries contained in the given array. * * <p><em>NOTE:</em> entries in a list created this way will be backed by the buffer. No defensive * copying is performed. * * @param buffer the array containing sequential extra data entries */ public ExtraDataList(byte[] buffer) { if (buffer.length > 0xffff) { throw new IllegalArgumentException("invalid extra field length"); } entries = new LinkedHashMap<>(); int index = 0; while (index < buffer.length) { ExtraData extra = new ExtraData(buffer, index); entries.put(extra.getId(), extra); index += extra.getLength(); } } /** * Returns the extra data record with the specified id, or null if it does not exist. */ public ExtraData get(short id) { return entries.get(id); } /** * Removes and returns the extra data record with the specified id if it exists. * * <p><em>NOTE:</em> does not modify the underlying storage, only marks the record as removed. */ public ExtraData remove(short id) { return entries.remove(id); } /** * Returns if the list contains an extra data record with the specified id. */ public boolean contains(short id) { return entries.containsKey(id); } /** * Adds a new entry to the end of the list. * * @throws IllegalArgumentException if adding the entry will make the list too long for the ZIP * format */ public void add(ExtraData entry) { if (getLength() + entry.getLength() > 0xffff) { throw new IllegalArgumentException("adding entry will make the extra field be too long"); } entries.put(entry.getId(), entry); } /** * Returns the overall length of the list in bytes. */ public int getLength() { int length = 0; for (ExtraData e : entries.values()) { length += e.getLength(); } return length; } /** * Creates and returns a byte array of the extra data list. */ public byte[] getBytes() { byte[] extra = new byte[getLength()]; try { getByteStream().read(extra); } catch (IOException impossible) { throw new AssertionError(impossible); } return extra; } /** * Returns an input stream for reading the extra data list entries. */ public InputStream getByteStream() { return new InputStream() { private final Iterator<ExtraData> itr = entries.values().iterator(); private ExtraData entry; private int index; @Override public int read() { if (entry == null) { if (itr.hasNext()) { entry = itr.next(); index = 0; } else { return -1; } } byte val = entry.getByte(index++); if (index >= entry.getLength()) { entry = null; } return val & 0xff; } }; } }