/* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to You 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 org.apache.poi.xssf.streaming; import java.io.IOException; import java.io.OutputStream; import java.util.zip.ZipEntry; import static java.nio.charset.StandardCharsets.US_ASCII; /** * Excel compatible Zip64 implementation. * For more information see https://github.com/rzymek/opczip */ class Zip64Impl { private static final long PK0102 = 0x02014b50L; private static final long PK0304 = 0x04034b50L; private static final long PK0506 = 0x06054b50L; private static final long PK0708 = 0x08074b50L; private static final int VERSION_20 = 20; private static final int VERSION_45 = 45; private static final int DATA_DESCRIPTOR_USED = 0x08; private static final int ZIP64_FIELD = 0x0001; private static final long MAX32 = 0xffffffffL; private final OutputStream out; private int written = 0; static class Entry { final String filename; long crc; long size; long compressedSize; long offset; Entry(String filename) { this.filename = filename; } } Zip64Impl(OutputStream out) { this.out = out; } /** * Write Local File Header */ int writeLFH(Entry entry) throws IOException { written = 0; writeInt(PK0304); // "PK\003\004" writeShort(VERSION_45); // version required: 4.5 writeShort(DATA_DESCRIPTOR_USED); // flags: 8 = data descriptor used writeShort(ZipEntry.DEFLATED); // compression method: 8 = deflate writeInt(0); // file modification time & date writeInt(entry.crc); // CRC-32 writeInt(0); // compressed file size writeInt(0); // uncompressed file size writeShort(entry.filename.length()); // filename length writeShort(0); // extra flags size byte[] filenameBytes = entry.filename.getBytes(US_ASCII); out.write(filenameBytes); // filename characters return written + filenameBytes.length; } /** * Write Data Descriptor */ int writeDAT(Entry entry) throws IOException { written = 0; writeInt(PK0708); // data descriptor signature "PK\007\008" writeInt(entry.crc); // crc-32 writeLong(entry.compressedSize); // compressed size (zip64) writeLong(entry.size); // uncompressed size (zip64) return written; } /** * Write Central directory file header */ int writeCEN(Entry entry) throws IOException { written = 0; boolean useZip64 = entry.size > MAX32; writeInt(PK0102); // "PK\001\002" writeShort(VERSION_45); // version made by: 4.5 writeShort(useZip64 ? VERSION_45 : VERSION_20);// version required: 4.5 writeShort(DATA_DESCRIPTOR_USED); // flags: 8 = data descriptor used writeShort(ZipEntry.DEFLATED); // compression method: 8 = deflate writeInt(0); // file modification time & date writeInt(entry.crc); // CRC-32 writeInt(entry.compressedSize); // compressed size writeInt(useZip64 ? MAX32 : entry.size); // uncompressed size writeShort(entry.filename.length()); // filename length writeShort(useZip64 ? (2 + 2 + 8) /* short + short + long*/ : 0); // extra field len writeShort(0); // comment length writeShort(0); // disk number where file starts writeShort(0); // internal file attributes (unused) writeInt(0); // external file attributes (unused) writeInt(entry.offset); // LFH offset byte[] filenameBytes = entry.filename.getBytes(US_ASCII); out.write(filenameBytes); // filename characters if (useZip64) { // Extra field: writeShort(ZIP64_FIELD); // ZIP64 field signature writeShort(8); // size of extra field (below) writeLong(entry.size); // uncompressed size } return written + filenameBytes.length; } /** * Write End of central directory record (EOCD) */ int writeEND(int entriesCount, long offset, long length) throws IOException { written = 0; writeInt(PK0506); // "PK\005\006" writeShort(0); // number of this disk writeShort(0); // central directory start disk writeShort(entriesCount); // number of directory entries on disk writeShort(entriesCount); // total number of directory entries writeInt(length); // length of central directory writeInt(offset); // offset of central directory writeShort(0); // comment length return written; } /** * Writes a 16-bit short to the output stream in little-endian byte order. */ private void writeShort(int v) throws IOException { out.write(new byte[]{ (byte) ((v >>> 0) & 0xff), (byte) ((v >>> 8) & 0xff) }); written += 2; } /** * Writes a 32-bit int to the output stream in little-endian byte order. */ private void writeInt(long v) throws IOException { out.write(new byte[]{ (byte) ((v >>> 0) & 0xff), (byte) ((v >>> 8) & 0xff), (byte) ((v >>> 16) & 0xff), (byte) ((v >>> 24) & 0xff) }); written += 4; } /** * Writes a 64-bit int to the output stream in little-endian byte order. */ private void writeLong(long v) throws IOException { out.write(new byte[]{ (byte) ((v >>> 0) & 0xff), (byte) ((v >>> 8) & 0xff), (byte) ((v >>> 16) & 0xff), (byte) ((v >>> 24) & 0xff), (byte) ((v >>> 32) & 0xff), (byte) ((v >>> 40) & 0xff), (byte) ((v >>> 48) & 0xff), (byte) ((v >>> 56) & 0xff) }); written += 8; } }