/** * Appends a zip64 extended info record to the extras contained in {@code ze}. If {@code ze} * contains no extras, a new extras array is created. */ public static void insertZip64ExtendedInfoToExtras(ZipEntry ze) throws ZipException { final byte[] output; // We always write the size, uncompressed size and local rel header offset in all our // Zip64 extended info headers (in both the local file header as well as the central // directory). We always omit the disk number because we don't support spanned // archives anyway. // // 2 bytes : Zip64 Extended Info Header ID // 2 bytes : Zip64 Extended Info Field Size. // 8 bytes : Uncompressed size // 8 bytes : Compressed size // 8 bytes : Local header rel offset. // ---------- // 28 bytes : total final int extendedInfoSize = 28; if (ze.extra == null) { output = new byte[extendedInfoSize]; } else { // If the existing extras are already too big, we have no choice but to throw // an error. if (ze.extra.length + extendedInfoSize > 65535) { throw new ZipException("No space in extras for zip64 extended entry info"); } // We copy existing extras over and put the zip64 extended info at the beginning. This // is to avoid breakages in the presence of "old style" extras which don't contain // headers and lengths. The spec is again silent about these inconsistencies. // // This means that people that for ZipOutputStream users, the value ZipEntry.getExtra // after an entry is written will be different from before. This shouldn't be an issue // in practice. output = new byte[ze.extra.length + extendedInfoSize]; System.arraycopy(ze.extra, 0, output, extendedInfoSize, ze.extra.length); } ByteBuffer bb = ByteBuffer.wrap(output).order(ByteOrder.LITTLE_ENDIAN); bb.putShort(ZIP64_EXTENDED_INFO_HEADER_ID); // We subtract four because extendedInfoSize includes the ID and field // size itself. bb.putShort((short) (extendedInfoSize - 4)); if (ze.getMethod() == ZipEntry.STORED) { bb.putLong(ze.size); bb.putLong(ze.compressedSize); } else { // Store these fields in the data descriptor instead. bb.putLong(0); // size. bb.putLong(0); // compressed size. } // The offset is only relevant in the central directory entry, but we write it out here // anyway, since we know what it is. bb.putLong(ze.localHeaderRelOffset); ze.extra = output; }
/** Returns a deep copy of this zip entry. */ @Override public Object clone() { try { ZipEntry result = (ZipEntry) super.clone(); result.extra = extra != null ? extra.clone() : null; return result; } catch (CloneNotSupportedException e) { throw new AssertionError(e); } }
/** Returns a copy of this entry. */ public Object clone() { try { ZipEntry e = (ZipEntry) super.clone(); e.extra = (extra == null) ? null : extra.clone(); return e; } catch (CloneNotSupportedException e) { // This should never happen, since we are Cloneable throw new InternalError(); } }
/** * Parse the zip64 extended info record from the extras present in {@code ze}. * * <p>If {@code fromCentralDirectory} is true, we assume we're parsing a central directory record. * We assume a local file header otherwise. The difference between the two is that a central * directory entry is required to be complete, whereas a local file header isn't. This is due to * the presence of an optional data descriptor after the file content. * * @return {@code} true iff. a zip64 extended info record was found. */ public static boolean parseZip64ExtendedInfo(ZipEntry ze, boolean fromCentralDirectory) throws ZipException { int extendedInfoSize = -1; int extendedInfoStart = -1; // If this file contains a zip64 central directory locator, entries might // optionally contain a zip64 extended information extra entry. if (ze.extra != null && ze.extra.length > 0) { // Extensible data fields are of the form header1+data1 + header2+data2 and so // on, where each header consists of a 2 byte header ID followed by a 2 byte size. // We need to iterate through the entire list of headers to find the header ID // for the zip64 extended information extra field (0x0001). final ByteBuffer buf = ByteBuffer.wrap(ze.extra).order(ByteOrder.LITTLE_ENDIAN); extendedInfoSize = getZip64ExtendedInfoSize(buf); if (extendedInfoSize != -1) { extendedInfoStart = buf.position(); try { // The size & compressed size only make sense in the central directory *or* if // we know them beforehand. If we don't know them beforehand, they're stored in // the data descriptor and should be read from there. // // Note that the spec says that the local file header "MUST" contain the // original and compressed size fields. We don't care too much about that. // The spec claims that the order of fields is fixed anyway. if (fromCentralDirectory || (ze.getMethod() == ZipEntry.STORED)) { if (ze.size == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) { ze.size = buf.getLong(); } if (ze.compressedSize == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) { ze.compressedSize = buf.getLong(); } } // The local header offset is significant only in the central directory. It makes no // sense within the local header itself. if (fromCentralDirectory) { if (ze.localHeaderRelOffset == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) { ze.localHeaderRelOffset = buf.getLong(); } } } catch (BufferUnderflowException bue) { ZipException zipException = new ZipException("Error parsing extended info"); zipException.initCause(bue); throw zipException; } } } // This entry doesn't contain a zip64 extended information data entry header. // We have to check that the compressedSize / size / localHeaderRelOffset values // are valid and don't require the presence of the extended header. if (extendedInfoSize == -1) { if (ze.compressedSize == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE || ze.size == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE || ze.localHeaderRelOffset == MAX_ZIP_ENTRY_AND_ARCHIVE_SIZE) { throw new ZipException( "File contains no zip64 extended information: " + "name=" + ze.name + "compressedSize=" + ze.compressedSize + ", size=" + ze.size + ", localHeader=" + ze.localHeaderRelOffset); } return false; } else { // If we're parsed the zip64 extended info header, we remove it from the extras // so that applications that set their own extras will see the data they set. // This is an unfortunate workaround needed due to a gap in the spec. The spec demands // that extras are present in the "extensible" format, which means that each extra field // must be prefixed with a header ID and a length. However, earlier versions of the spec // made no mention of this, nor did any existing API enforce it. This means users could // set "free form" extras without caring very much whether the implementation wanted to // extend or add to them. // The start of the extended info header. final int extendedInfoHeaderStart = extendedInfoStart - 4; // The total size of the extended info, including the header. final int extendedInfoTotalSize = extendedInfoSize + 4; final int extrasLen = ze.extra.length - extendedInfoTotalSize; byte[] extrasWithoutZip64 = new byte[extrasLen]; System.arraycopy(ze.extra, 0, extrasWithoutZip64, 0, extendedInfoHeaderStart); System.arraycopy( ze.extra, extendedInfoHeaderStart + extendedInfoTotalSize, extrasWithoutZip64, extendedInfoHeaderStart, (extrasLen - extendedInfoHeaderStart)); ze.extra = extrasWithoutZip64; return true; } }