blob: 706004912da791119235842251f5c2e4d380ab38 [file] [log] [blame]
/* ====================================================================
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;
int compressedSize;
int 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, int offset, int 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 {
OutputStream out = this.out;
out.write((v >>> 0) & 0xff);
out.write((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 {
OutputStream out = this.out;
out.write((int) ((v >>> 0) & 0xff));
out.write((int) ((v >>> 8) & 0xff));
out.write((int) ((v >>> 16) & 0xff));
out.write((int) ((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 {
OutputStream out = this.out;
out.write((int) ((v >>> 0) & 0xff));
out.write((int) ((v >>> 8) & 0xff));
out.write((int) ((v >>> 16) & 0xff));
out.write((int) ((v >>> 24) & 0xff));
out.write((int) ((v >>> 32) & 0xff));
out.write((int) ((v >>> 40) & 0xff));
out.write((int) ((v >>> 48) & 0xff));
out.write((int) ((v >>> 56) & 0xff));
written += 8;
}
}