git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1861196 13f79535-47bb-0310-9956-ffa450edef68pull/155/head
@@ -0,0 +1,141 @@ | |||
/* ==================================================================== | |||
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 org.apache.poi.xssf.streaming.Zip64Impl.Entry; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import java.util.zip.*; | |||
/** | |||
* ZIP64 OutputStream implementation compatible with MS Excel. | |||
* Drop in replacement for `java.util.ZipOutputStream`. | |||
* | |||
* For more information see https://github.com/rzymek/opczip | |||
* | |||
* @author Krzysztof Rzymkowski | |||
*/ | |||
class OpcOutputStream extends DeflaterOutputStream { | |||
private final Zip64Impl spec; | |||
private final List<Entry> entries = new ArrayList<>(); | |||
private final CRC32 crc = new CRC32(); | |||
private Entry current; | |||
private int written = 0; | |||
private boolean finished = false; | |||
/** | |||
* Creates ZIP64 output stream | |||
* | |||
* @param out target stream to write compressed data to | |||
*/ | |||
public OpcOutputStream(OutputStream out) { | |||
super(out, new Deflater(Deflater.DEFAULT_COMPRESSION, true)); | |||
this.spec = new Zip64Impl(out); | |||
} | |||
/** | |||
* @see Deflater#setLevel(int) | |||
*/ | |||
public void setLevel(int level) { | |||
super.def.setLevel(level); | |||
} | |||
/** | |||
* @see ZipOutputStream#putNextEntry(ZipEntry) | |||
*/ | |||
public void putNextEntry(String name) throws IOException { | |||
if (current != null) { | |||
closeEntry(); | |||
} | |||
current = new Entry(name); | |||
current.offset = written; | |||
written += spec.writeLFH(current); | |||
entries.add(current); | |||
} | |||
/** | |||
* @see ZipOutputStream#closeEntry() | |||
*/ | |||
public void closeEntry() throws IOException { | |||
if (current == null) { | |||
throw new IllegalStateException("not current zip current"); | |||
} | |||
def.finish(); | |||
while (!def.finished()) { | |||
deflate(); | |||
} | |||
current.size = def.getBytesRead(); | |||
current.compressedSize = (int) def.getBytesWritten(); | |||
current.crc = crc.getValue(); | |||
written += current.compressedSize; | |||
written += spec.writeDAT(current); | |||
current = null; | |||
def.reset(); | |||
crc.reset(); | |||
} | |||
/** | |||
* @see ZipOutputStream#finish() | |||
*/ | |||
@Override | |||
public void finish() throws IOException { | |||
if(finished){ | |||
return; | |||
} | |||
if(current != null) { | |||
closeEntry(); | |||
} | |||
int offset = written; | |||
for (Entry entry : entries) { | |||
written += spec.writeCEN(entry); | |||
} | |||
written += spec.writeEND(entries.size(), offset, written - offset); | |||
finished = true; | |||
} | |||
/** | |||
* @see ZipOutputStream#write(byte[], int, int) | |||
*/ | |||
@Override | |||
public synchronized void write(byte[] b, int off, int len) throws IOException { | |||
if (off < 0 || len < 0 || off > b.length - len) { | |||
throw new IndexOutOfBoundsException(); | |||
} else if (len == 0) { | |||
return; | |||
} | |||
super.write(b, off, len); | |||
crc.update(b, off, len); | |||
} | |||
/** | |||
* @see ZipOutputStream#close() | |||
*/ | |||
@Override | |||
public void close() throws IOException { | |||
finish(); | |||
out.close(); | |||
} | |||
} | |||
@@ -0,0 +1,81 @@ | |||
/* ==================================================================== | |||
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 org.apache.commons.compress.archivers.ArchiveEntry; | |||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; | |||
import java.io.IOException; | |||
import java.io.OutputStream; | |||
class OpcZipArchiveOutputStream extends ZipArchiveOutputStream { | |||
private final OpcOutputStream out; | |||
OpcZipArchiveOutputStream(OutputStream out) { | |||
super(out); | |||
this.out = new OpcOutputStream(out); | |||
} | |||
@Override | |||
public void setLevel(int level) { | |||
out.setLevel(level); | |||
} | |||
@Override | |||
public void putArchiveEntry(ArchiveEntry archiveEntry) throws IOException { | |||
out.putNextEntry(archiveEntry.getName()); | |||
} | |||
@Override | |||
public void closeArchiveEntry() throws IOException { | |||
out.closeEntry(); | |||
} | |||
@Override | |||
public void finish() throws IOException { | |||
out.finish(); | |||
} | |||
@Override | |||
public void write(byte[] b, int off, int len) throws IOException { | |||
out.write(b, off, len); | |||
} | |||
@Override | |||
public void close() throws IOException { | |||
out.close(); | |||
} | |||
@Override | |||
public void write(int b) throws IOException { | |||
out.write(b); | |||
} | |||
@Override | |||
public void flush() throws IOException { | |||
out.flush(); | |||
} | |||
@Override | |||
public void write(byte[] b) throws IOException { | |||
out.write(b); | |||
} | |||
} | |||
@@ -32,6 +32,7 @@ import java.util.List; | |||
import java.util.Map; | |||
import java.util.NoSuchElementException; | |||
import org.apache.commons.compress.archivers.ArchiveOutputStream; | |||
import org.apache.commons.compress.archivers.zip.Zip64Mode; | |||
import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; | |||
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; | |||
@@ -385,8 +386,7 @@ public class SXSSFWorkbook implements Workbook { | |||
} | |||
protected void injectData(ZipEntrySource zipEntrySource, OutputStream out) throws IOException { | |||
ZipArchiveOutputStream zos = new ZipArchiveOutputStream(out); | |||
zos.setUseZip64(zip64Mode); | |||
ArchiveOutputStream zos = createArchiveOutputStream(out); | |||
try { | |||
Enumeration<? extends ZipArchiveEntry> en = zipEntrySource.getEntries(); | |||
while (en.hasMoreElements()) { | |||
@@ -421,6 +421,16 @@ public class SXSSFWorkbook implements Workbook { | |||
} | |||
} | |||
protected ZipArchiveOutputStream createArchiveOutputStream(OutputStream out) { | |||
if (Zip64Mode.Always.equals(zip64Mode)) { | |||
return new OpcZipArchiveOutputStream(out); | |||
} else { | |||
ZipArchiveOutputStream zos = new ZipArchiveOutputStream(out); | |||
zos.setUseZip64(zip64Mode); | |||
return zos; | |||
} | |||
} | |||
private static void copyStreamAndInjectWorksheet(InputStream in, OutputStream out, InputStream worksheetData) throws IOException { | |||
InputStreamReader inReader = new InputStreamReader(in, StandardCharsets.UTF_8); | |||
OutputStreamWriter outWriter = new OutputStreamWriter(out, StandardCharsets.UTF_8); |
@@ -0,0 +1,185 @@ | |||
/* ==================================================================== | |||
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 | |||
* | |||
* @author Krzysztof Rzymkowski | |||
*/ | |||
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; | |||
} | |||
} | |||