#59841 - OOXML: enable custom zip streams via OPCPackage.open(ZipEntrySource)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1753003 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
af2a99b910
commit
11fe2eda86
@ -53,6 +53,7 @@ import org.apache.poi.openxml4j.opc.internal.marshallers.ZipPackagePropertiesMar
|
|||||||
import org.apache.poi.openxml4j.opc.internal.unmarshallers.PackagePropertiesUnmarshaller;
|
import org.apache.poi.openxml4j.opc.internal.unmarshallers.PackagePropertiesUnmarshaller;
|
||||||
import org.apache.poi.openxml4j.opc.internal.unmarshallers.UnmarshallContext;
|
import org.apache.poi.openxml4j.opc.internal.unmarshallers.UnmarshallContext;
|
||||||
import org.apache.poi.openxml4j.util.Nullable;
|
import org.apache.poi.openxml4j.util.Nullable;
|
||||||
|
import org.apache.poi.openxml4j.util.ZipEntrySource;
|
||||||
import org.apache.poi.util.NotImplemented;
|
import org.apache.poi.util.NotImplemented;
|
||||||
import org.apache.poi.util.POILogFactory;
|
import org.apache.poi.util.POILogFactory;
|
||||||
import org.apache.poi.util.POILogger;
|
import org.apache.poi.util.POILogger;
|
||||||
@ -200,6 +201,42 @@ public abstract class OPCPackage implements RelationshipSource, Closeable {
|
|||||||
return open(file, defaultPackageAccess);
|
return open(file, defaultPackageAccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open an user provided {@link ZipEntrySource} with read-only permission.
|
||||||
|
* This method can be used to stream data into POI.
|
||||||
|
* Opposed to other open variants, the data is read as-is, e.g. there aren't
|
||||||
|
* any zip-bomb protection put in place.
|
||||||
|
*
|
||||||
|
* @param zipEntry the custom source
|
||||||
|
* @return A Package object
|
||||||
|
* @throws InvalidFormatException if a parsing error occur.
|
||||||
|
*/
|
||||||
|
public static OPCPackage open(ZipEntrySource zipEntry)
|
||||||
|
throws InvalidFormatException {
|
||||||
|
OPCPackage pack = new ZipPackage(zipEntry, PackageAccess.READ);
|
||||||
|
try {
|
||||||
|
if (pack.partList == null) {
|
||||||
|
pack.getParts();
|
||||||
|
}
|
||||||
|
// pack.originalPackagePath = file.getAbsolutePath();
|
||||||
|
return pack;
|
||||||
|
} catch (InvalidFormatException e) {
|
||||||
|
try {
|
||||||
|
pack.close();
|
||||||
|
} catch (IOException e1) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
try {
|
||||||
|
pack.close();
|
||||||
|
} catch (IOException e1) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a package.
|
* Open a package.
|
||||||
*
|
*
|
||||||
|
@ -20,6 +20,7 @@ import static org.apache.poi.xssf.usermodel.XSSFRelation.NS_SPREADSHEETML;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.PushbackInputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -140,8 +141,12 @@ public class ReadOnlySharedStringsTable extends DefaultHandler {
|
|||||||
* @throws ParserConfigurationException
|
* @throws ParserConfigurationException
|
||||||
*/
|
*/
|
||||||
public void readFrom(InputStream is) throws IOException, SAXException {
|
public void readFrom(InputStream is) throws IOException, SAXException {
|
||||||
if (is.available() > 0) {
|
// test if the file is empty, otherwise parse it
|
||||||
InputSource sheetSource = new InputSource(is);
|
PushbackInputStream pis = new PushbackInputStream(is, 1);
|
||||||
|
int emptyTest = pis.read();
|
||||||
|
if (emptyTest > -1) {
|
||||||
|
pis.unread(emptyTest);
|
||||||
|
InputSource sheetSource = new InputSource(pis);
|
||||||
try {
|
try {
|
||||||
XMLReader sheetParser = SAXHelper.newXMLReader();
|
XMLReader sheetParser = SAXHelper.newXMLReader();
|
||||||
sheetParser.setContentHandler(this);
|
sheetParser.setContentHandler(this);
|
||||||
|
@ -0,0 +1,175 @@
|
|||||||
|
/* ====================================================================
|
||||||
|
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 static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
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.xssf.XSSFTestDataSamples;
|
||||||
|
import org.apache.poi.xssf.extractor.XSSFEventBasedExcelExtractor;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.apache.xmlbeans.XmlException;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class TestSecureTempZip {
|
||||||
|
/**
|
||||||
|
* Test case for #59841 - this is an example on how to use encrypted temp files,
|
||||||
|
* which are streamed into POI opposed to having everything in memory
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void protectedTempZip() throws IOException, GeneralSecurityException, XmlException, OpenXML4JException {
|
||||||
|
final File tmpFile = new File("build/tmp", "protectedXlsx.zip");
|
||||||
|
File tikaProt = XSSFTestDataSamples.getSampleFile("protected_passtika.xlsx");
|
||||||
|
FileInputStream fis = new FileInputStream(tikaProt);
|
||||||
|
POIFSFileSystem poifs = new POIFSFileSystem(fis);
|
||||||
|
EncryptionInfo ei = new EncryptionInfo(poifs);
|
||||||
|
Decryptor dec = ei.getDecryptor();
|
||||||
|
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);
|
||||||
|
|
||||||
|
// test the source
|
||||||
|
OPCPackage opc = OPCPackage.open(source);
|
||||||
|
String expected = "This is an Encrypted Excel spreadsheet.";
|
||||||
|
|
||||||
|
XSSFEventBasedExcelExtractor extractor = new XSSFEventBasedExcelExtractor(opc);
|
||||||
|
extractor.setIncludeSheetNames(false);
|
||||||
|
String txt = extractor.getText();
|
||||||
|
assertEquals(expected, txt.trim());
|
||||||
|
|
||||||
|
XSSFWorkbook wb = new XSSFWorkbook(opc);
|
||||||
|
txt = wb.getSheetAt(0).getRow(0).getCell(0).getStringCellValue();
|
||||||
|
assertEquals(expected, txt);
|
||||||
|
|
||||||
|
extractor.close();
|
||||||
|
|
||||||
|
wb.close();
|
||||||
|
opc.close();
|
||||||
|
source.close();
|
||||||
|
poifs.close();
|
||||||
|
fis.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
AesZipFileZipEntrySource(ZipFile zipFile, Cipher ci) {
|
||||||
|
this.zipFile = zipFile;
|
||||||
|
this.ci = ci;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user