import java.io.FileOutputStream;\r
import java.io.IOException;\r
import java.io.InputStream;\r
-import java.io.OutputStreamWriter;\r
-import java.io.Writer;\r
+import java.io.OutputStream;\r
import java.util.zip.GZIPInputStream;\r
import java.util.zip.GZIPOutputStream;\r
\r
return TempFile.createTempFile("poi-sxssf-sheet-xml", ".gz");\r
}\r
\r
- /**\r
- * @return a wrapped instance of GZIPOutputStream\r
- */\r
@Override\r
- public Writer createWriter(File fd)throws IOException {\r
- return new OutputStreamWriter(new GZIPOutputStream(new FileOutputStream(fd)), "UTF-8");\r
+ protected InputStream decorateInputStream(FileInputStream fis) throws IOException {\r
+ return new GZIPInputStream(fis);\r
}\r
\r
-\r
- /**\r
- * @return a GZIPInputStream stream to read the compressed temp file\r
- */\r
@Override\r
- public InputStream getWorksheetXMLInputStream() throws IOException {\r
- File fd = getTempFile();\r
- return new GZIPInputStream(new FileInputStream(fd));\r
+ protected OutputStream decorateOutputStream(FileOutputStream fos) throws IOException {\r
+ return new GZIPOutputStream(fos);\r
}\r
\r
}\r
import java.util.zip.ZipOutputStream;
import org.apache.poi.openxml4j.opc.OPCPackage;
+import org.apache.poi.openxml4j.util.ZipEntrySource;
+import org.apache.poi.openxml4j.util.ZipFileZipEntrySource;
import org.apache.poi.ss.SpreadsheetVersion;
import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.util.Internal;
import org.apache.poi.util.NotImplemented;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
_randomAccessWindowSize = rowAccessWindowSize;
}
+ /**
+ * Get whether temp files should be compressed.
+ *
+ * @return whether to compress temp files
+ */
+ public boolean isCompressTempFiles() {
+ return _compressTmpFiles;
+ }
/**
* Set whether temp files should be compressed.
* <p>
* </p>
* @param compress whether to compress temp files
*/
- public void setCompressTempFiles(boolean compress){
+ public void setCompressTempFiles(boolean compress) {
_compressTmpFiles = compress;
}
+
+ @Internal
+ protected SharedStringsTable getSharedStringSource() {
+ return _sharedStringSource;
+ }
- SheetDataWriter createSheetDataWriter() throws IOException {
+ protected SheetDataWriter createSheetDataWriter() throws IOException {
if(_compressTmpFiles) {
return new GZIPSheetDataWriter(_sharedStringSource);
}
return null;
}
- private void injectData(File zipfile, OutputStream out) throws IOException
+ protected void injectData(ZipEntrySource zipEntrySource, OutputStream out) throws IOException
{
- // don't use ZipHelper.openZipFile here - see #59743
- ZipFile zip = new ZipFile(zipfile);
try
{
ZipOutputStream zos = new ZipOutputStream(out);
try
{
- Enumeration<? extends ZipEntry> en = zip.entries();
+ Enumeration<? extends ZipEntry> en = zipEntrySource.getEntries();
while (en.hasMoreElements())
{
ZipEntry ze = en.nextElement();
zos.putNextEntry(new ZipEntry(ze.getName()));
- InputStream is = zip.getInputStream(ze);
+ InputStream is = zipEntrySource.getInputStream(ze);
XSSFSheet xSheet=getSheetFromZipEntryName(ze.getName());
if(xSheet!=null)
{
}
finally
{
- zip.close();
+ zipEntrySource.close();
}
}
private static void copyStream(InputStream in, OutputStream out) throws IOException {
}
//Substitute the template entries with the generated sheet data files
- injectData(tmplFile, stream);
+ final ZipEntrySource source = new ZipFileZipEntrySource(new ZipFile(tmplFile));
+ injectData(source, stream);
}
finally
{
import java.io.FileOutputStream;\r
import java.io.IOException;\r
import java.io.InputStream;\r
+import java.io.OutputStream;\r
import java.io.OutputStreamWriter;\r
import java.io.Writer;\r
import java.util.Iterator;\r
_out = createWriter(_fd);\r
}\r
\r
- public SheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException{\r
+ public SheetDataWriter(SharedStringsTable sharedStringsTable) throws IOException {\r
this();\r
this._sharedStringSource = sharedStringsTable;\r
}\r
* \r
* @param fd the file to write to\r
*/\r
- public Writer createWriter(File fd)throws IOException {\r
- return new BufferedWriter(new OutputStreamWriter(new FileOutputStream(fd), "UTF-8"));\r
+ public Writer createWriter(File fd) throws IOException {\r
+ final OutputStream decorated = decorateOutputStream(new FileOutputStream(fd));\r
+ return new BufferedWriter(new OutputStreamWriter(decorated, "UTF-8"));\r
+ }\r
+ \r
+ /**\r
+ * Override this to translate (such as encrypt or compress) the file output stream\r
+ * as it is being written to disk.\r
+ * The default behavior is to to pass the stream through unmodified.\r
+ *\r
+ * @param fos the stream to decorate\r
+ * @return a decorated stream\r
+ * @throws IOException\r
+ * @see #decorateInputStream(FileInputStream)\r
+ */\r
+ protected OutputStream decorateOutputStream(FileOutputStream fos) throws IOException {\r
+ return fos;\r
}\r
\r
/**\r
*/\r
public InputStream getWorksheetXMLInputStream() throws IOException {\r
File fd = getTempFile();\r
- return new FileInputStream(fd);\r
+ return decorateInputStream(new FileInputStream(fd));\r
+ }\r
+ \r
+ /**\r
+ * Override this to translate (such as decrypt or expand) the file input stream\r
+ * as it is being read from disk.\r
+ * The default behavior is to to pass the stream through unmodified.\r
+ *\r
+ * @param fis the stream to decorate\r
+ * @return a decorated stream\r
+ * @throws IOException\r
+ * @see #decorateOutputStream(FileOutputStream)\r
+ */\r
+ protected InputStream decorateInputStream(FileInputStream fis) throws IOException {\r
+ return fis;\r
}\r
\r
public int getNumberOfFlushedRows() {\r
--- /dev/null
+/* ====================================================================
+ 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.poifs.crypt;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+import java.util.Enumeration;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+import java.util.zip.ZipOutputStream;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.poi.openxml4j.util.ZipEntrySource;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.TempFile;
+
+public class AesZipFileZipEntrySource implements ZipEntrySource {
+ final File tmpFile;
+ final ZipFile zipFile;
+ final Cipher ci;
+ boolean closed;
+
+ public AesZipFileZipEntrySource(File tmpFile, Cipher ci) throws IOException {
+ this.tmpFile = tmpFile;
+ this.zipFile = new ZipFile(tmpFile);
+ this.ci = ci;
+ this.closed = false;
+ }
+
+ /**
+ * Note: the file sizes are rounded up to the next cipher block size,
+ * so don't rely on file sizes of these custom encrypted zip file entries!
+ */
+ public Enumeration<? extends ZipEntry> getEntries() {
+ return zipFile.entries();
+ }
+
+ @SuppressWarnings("resource")
+ public InputStream getInputStream(ZipEntry entry) throws IOException {
+ InputStream is = zipFile.getInputStream(entry);
+ return new CipherInputStream(is, ci);
+ }
+
+ @Override
+ public void close() throws IOException {
+ zipFile.close();
+ tmpFile.delete();
+ closed = true;
+ }
+
+ @Override
+ public boolean isClosed() {
+ return closed;
+ }
+
+ public static ZipEntrySource createZipEntrySource(InputStream is) throws IOException, GeneralSecurityException {
+ // generate session key
+ SecureRandom sr = new SecureRandom();
+ byte[] ivBytes = new byte[16], keyBytes = new byte[16];
+ sr.nextBytes(ivBytes);
+ sr.nextBytes(keyBytes);
+ final File tmpFile = TempFile.createTempFile("protectedXlsx", ".zip");
+ copyToFile(is, tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
+ IOUtils.closeQuietly(is);
+ return fileToSource(tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
+ }
+
+ private static void copyToFile(InputStream is, File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException {
+ SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
+ Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding");
+
+ ZipInputStream zis = new ZipInputStream(is);
+ FileOutputStream fos = new FileOutputStream(tmpFile);
+ ZipOutputStream zos = new ZipOutputStream(fos);
+
+ ZipEntry ze;
+ while ((ze = zis.getNextEntry()) != null) {
+ // the cipher output stream pads the data, therefore we can't reuse the ZipEntry with set sizes
+ // as those will be validated upon close()
+ ZipEntry zeNew = new ZipEntry(ze.getName());
+ zeNew.setComment(ze.getComment());
+ zeNew.setExtra(ze.getExtra());
+ zeNew.setTime(ze.getTime());
+ // zeNew.setMethod(ze.getMethod());
+ zos.putNextEntry(zeNew);
+ FilterOutputStream fos2 = new FilterOutputStream(zos){
+ // don't close underlying ZipOutputStream
+ public void close() {}
+ };
+ CipherOutputStream cos = new CipherOutputStream(fos2, ciEnc);
+ IOUtils.copy(zis, cos);
+ cos.close();
+ fos2.close();
+ zos.closeEntry();
+ zis.closeEntry();
+ }
+ zos.close();
+ fos.close();
+ zis.close();
+ }
+
+ private static ZipEntrySource fileToSource(File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException {
+ SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
+ Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding");
+ return new AesZipFileZipEntrySource(tmpFile, ciDec);
+ }
+
+}
+
import java.io.File;
import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
-import java.security.SecureRandom;
-import java.util.Enumeration;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipException;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
-import java.util.zip.ZipOutputStream;
-
-import javax.crypto.Cipher;
-import javax.crypto.CipherInputStream;
-import javax.crypto.CipherOutputStream;
-import javax.crypto.spec.SecretKeySpec;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.util.ZipEntrySource;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
-import org.apache.poi.util.IOUtils;
-import org.apache.poi.util.TempFile;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
*/
@Test
public void protectedTempZip() throws IOException, GeneralSecurityException, XmlException, OpenXML4JException {
- final File tmpFile = TempFile.createTempFile("protectedXlsx", ".zip");
File tikaProt = XSSFTestDataSamples.getSampleFile("protected_passtika.xlsx");
FileInputStream fis = new FileInputStream(tikaProt);
POIFSFileSystem poifs = new POIFSFileSystem(fis);
boolean passOk = dec.verifyPassword("tika");
assertTrue(passOk);
- // generate session key
- SecureRandom sr = new SecureRandom();
- byte[] ivBytes = new byte[16], keyBytes = new byte[16];
- sr.nextBytes(ivBytes);
- sr.nextBytes(keyBytes);
-
// extract encrypted ooxml file and write to custom encrypted zip file
InputStream is = dec.getDataStream(poifs);
- copyToFile(is, tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
- is.close();
// provide ZipEntrySource to poi which decrypts on the fly
- ZipEntrySource source = fileToSource(tmpFile, CipherAlgorithm.aes128, keyBytes, ivBytes);
+ ZipEntrySource source = AesZipFileZipEntrySource.createZipEntrySource(is);
// test the source
OPCPackage opc = OPCPackage.open(source);
source.close();
poifs.close();
fis.close();
- tmpFile.delete();
- }
-
- private void copyToFile(InputStream is, File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws IOException, GeneralSecurityException {
- SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
- Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding");
-
- ZipInputStream zis = new ZipInputStream(is);
- FileOutputStream fos = new FileOutputStream(tmpFile);
- ZipOutputStream zos = new ZipOutputStream(fos);
-
- ZipEntry ze;
- while ((ze = zis.getNextEntry()) != null) {
- // the cipher output stream pads the data, therefore we can't reuse the ZipEntry with set sizes
- // as those will be validated upon close()
- ZipEntry zeNew = new ZipEntry(ze.getName());
- zeNew.setComment(ze.getComment());
- zeNew.setExtra(ze.getExtra());
- zeNew.setTime(ze.getTime());
- // zeNew.setMethod(ze.getMethod());
- zos.putNextEntry(zeNew);
- FilterOutputStream fos2 = new FilterOutputStream(zos){
- // don't close underlying ZipOutputStream
- public void close() {}
- };
- CipherOutputStream cos = new CipherOutputStream(fos2, ciEnc);
- IOUtils.copy(zis, cos);
- cos.close();
- fos2.close();
- zos.closeEntry();
- zis.closeEntry();
- }
- zos.close();
- fos.close();
- zis.close();
- }
-
- private ZipEntrySource fileToSource(File tmpFile, CipherAlgorithm cipherAlgorithm, byte keyBytes[], byte ivBytes[]) throws ZipException, IOException {
- SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
- Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding");
- ZipFile zf = new ZipFile(tmpFile);
- return new AesZipFileZipEntrySource(zf, ciDec);
- }
-
- static class AesZipFileZipEntrySource implements ZipEntrySource {
- final ZipFile zipFile;
- final Cipher ci;
- boolean closed;
-
- AesZipFileZipEntrySource(ZipFile zipFile, Cipher ci) {
- this.zipFile = zipFile;
- this.ci = ci;
- this.closed = false;
- }
-
- /**
- * Note: the file sizes are rounded up to the next cipher block size,
- * so don't rely on file sizes of these custom encrypted zip file entries!
- */
- public Enumeration<? extends ZipEntry> getEntries() {
- return zipFile.entries();
- }
-
- @SuppressWarnings("resource")
- public InputStream getInputStream(ZipEntry entry) throws IOException {
- InputStream is = zipFile.getInputStream(entry);
- return new CipherInputStream(is, ci);
- }
-
- @Override
- public void close() throws IOException {
- zipFile.close();
- closed = true;
- }
-
- @Override
- public boolean isClosed() {
- return closed;
- }
}
}
--- /dev/null
+/*
+ * ====================================================================
+ * 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 static org.junit.Assert.assertEquals;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.SecureRandom;
+
+import javax.crypto.Cipher;
+import javax.crypto.CipherInputStream;
+import javax.crypto.CipherOutputStream;
+import javax.crypto.spec.SecretKeySpec;
+
+import org.apache.poi.openxml4j.util.ZipEntrySource;
+import org.apache.poi.poifs.crypt.AesZipFileZipEntrySource;
+import org.apache.poi.poifs.crypt.ChainingMode;
+import org.apache.poi.poifs.crypt.CipherAlgorithm;
+import org.apache.poi.poifs.crypt.CryptoFunctions;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+import org.apache.poi.xssf.usermodel.XSSFCell;
+import org.apache.poi.xssf.usermodel.XSSFRow;
+import org.apache.poi.xssf.usermodel.XSSFSheet;
+import org.apache.poi.xssf.usermodel.XSSFWorkbook;
+import org.junit.Test;
+
+/**
+ * This class tests that an SXSSFWorkbook can be written and read where all temporary disk I/O
+ * is encrypted, but the final saved workbook is not encrypted
+ */
+public final class TestSXSSFWorkbookWithCustomZipEntrySource {
+
+ @Test
+ public void customZipEntrySource() throws IOException, GeneralSecurityException {
+ final String sheetName = "TestSheet1";
+ final String cellValue = "customZipEntrySource";
+ SXSSFWorkbookWithCustomZipEntrySource workbook = new SXSSFWorkbookWithCustomZipEntrySource();
+ SXSSFSheet sheet1 = workbook.createSheet(sheetName);
+ SXSSFRow row1 = sheet1.createRow(1);
+ SXSSFCell cell1 = row1.createCell(1);
+ cell1.setCellValue(cellValue);
+ ByteArrayOutputStream os = new ByteArrayOutputStream(8192);
+ workbook.write(os);
+ workbook.close();
+ workbook.dispose();
+ XSSFWorkbook xwb = new XSSFWorkbook(new ByteArrayInputStream(os.toByteArray()));
+ XSSFSheet xs1 = xwb.getSheetAt(0);
+ assertEquals(sheetName, xs1.getSheetName());
+ XSSFRow xr1 = xs1.getRow(1);
+ XSSFCell xc1 = xr1.getCell(1);
+ assertEquals(cellValue, xc1.getStringCellValue());
+ xwb.close();
+ }
+
+ static class SXSSFWorkbookWithCustomZipEntrySource extends SXSSFWorkbook {
+
+ private static final POILogger logger = POILogFactory.getLogger(SXSSFWorkbookWithCustomZipEntrySource.class);
+
+ @Override
+ public void write(OutputStream stream) throws IOException {
+ flushSheets();
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ getXSSFWorkbook().write(os);
+ ZipEntrySource source = null;
+ try {
+ // provide ZipEntrySource to poi which decrypts on the fly
+ source = AesZipFileZipEntrySource.createZipEntrySource(new ByteArrayInputStream(os.toByteArray()));
+ injectData(source, stream);
+ } catch (GeneralSecurityException e) {
+ throw new IOException(e);
+ } finally {
+ source.close();
+ }
+ }
+
+ @Override
+ protected SheetDataWriter createSheetDataWriter() throws IOException {
+ //log values to ensure these values are accessible to subclasses
+ logger.log(POILogger.INFO, "isCompressTempFiles: " + isCompressTempFiles());
+ logger.log(POILogger.INFO, "SharedStringSource: " + getSharedStringSource());
+ return new SheetDataWriterWithDecorator();
+ }
+ }
+
+ static class SheetDataWriterWithDecorator extends SheetDataWriter {
+ final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
+ SecretKeySpec skeySpec;
+ byte[] ivBytes;
+
+ public SheetDataWriterWithDecorator() throws IOException {
+ super();
+ }
+
+ void init() {
+ if(skeySpec == null) {
+ SecureRandom sr = new SecureRandom();
+ ivBytes = new byte[16];
+ byte[] keyBytes = new byte[16];
+ sr.nextBytes(ivBytes);
+ sr.nextBytes(keyBytes);
+ skeySpec = new SecretKeySpec(keyBytes, cipherAlgorithm.jceId);
+ }
+ }
+
+ @Override
+ protected OutputStream decorateOutputStream(FileOutputStream fos) {
+ init();
+ Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, "PKCS5Padding");
+ return new CipherOutputStream(fos, ciEnc);
+ }
+
+ @Override
+ protected InputStream decorateInputStream(FileInputStream fis) {
+ Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, "PKCS5Padding");
+ return new CipherInputStream(fis, ciDec);
+ }
+
+ }
+}