bug 60321: add examples with encrypted temp data. Patch from PJ Fanning.

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1768744 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Javen O'Neal 2016-11-08 17:32:38 +00:00
parent cc38d66a3e
commit 7b8bc917ac
8 changed files with 703 additions and 0 deletions

View File

@ -0,0 +1,147 @@
/*
* ====================================================================
* 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.crypt.examples;
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.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.TempFile;
/**
* An example <code>ZipEntrySource</code> that has encrypted temp files to ensure that
* sensitive data is not stored in raw format on disk.
*/
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!
*/
@Override
public Enumeration<? extends ZipEntry> getEntries() {
return zipFile.entries();
}
@Override
public InputStream getInputStream(ZipEntry entry) throws IOException {
InputStream is = zipFile.getInputStream(entry);
return new CipherInputStream(is, ci);
}
@Override
public void close() throws IOException {
if(!closed) {
zipFile.close();
tmpFile.delete();
}
closed = true;
}
@Override
public boolean isClosed() {
return closed;
}
public static AesZipFileZipEntrySource 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
@Override
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 AesZipFileZipEntrySource 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);
}
}

View File

@ -0,0 +1,72 @@
/*
* ====================================================================
* 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.crypt.examples;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
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.poifs.crypt.ChainingMode;
import org.apache.poi.poifs.crypt.CipherAlgorithm;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.util.TempFile;
/**
* EncryptedTempData can be used to buffer binary data in a secure way, by using encrypted temp files.
*/
public class EncryptedTempData {
final static CipherAlgorithm cipherAlgorithm = CipherAlgorithm.aes128;
final SecretKeySpec skeySpec;
final byte[] ivBytes;
final File tempFile;
public EncryptedTempData() throws IOException {
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);
tempFile = TempFile.createTempFile("poi-temp-data", ".tmp");
}
public OutputStream getOutputStream() throws IOException {
Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, null);
return new CipherOutputStream(new FileOutputStream(tempFile), ciEnc);
}
public InputStream getInputStream() throws IOException {
Cipher ciDec = CryptoFunctions.getCipher(skeySpec, cipherAlgorithm, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, null);
return new CipherInputStream(new FileInputStream(tempFile), ciDec);
}
public void dispose() {
tempFile.delete();
}
}

View File

@ -0,0 +1,43 @@
/*
* ====================================================================
* 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.crypt.examples;
import java.io.InputStream;
import org.apache.poi.poifs.crypt.Decryptor;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.IOUtils;
public class EncryptionUtils {
public static InputStream decrypt(final InputStream inputStream, final String pwd) throws Exception {
try {
POIFSFileSystem fs = new POIFSFileSystem(inputStream);
EncryptionInfo info = new EncryptionInfo(fs);
Decryptor d = Decryptor.getInstance(info);
if (!d.verifyPassword(pwd)) {
throw new RuntimeException("incorrect password");
}
return d.getDataStream(fs);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
}

View File

@ -0,0 +1,43 @@
/*
* ====================================================================
* 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.examples.util;
import java.io.File;
import java.io.IOException;
import org.apache.poi.util.TempFile;
public class TempFileUtils {
public static void checkTempFiles() throws IOException {
String tmpDir = System.getProperty(TempFile.JAVA_IO_TMPDIR) + "/poifiles";
File tempDir = new File(tmpDir);
if(tempDir.exists()) {
String[] tempFiles = tempDir.list();
if(tempFiles.length > 0) {
System.out.println("found files in poi temp dir " + tempDir.getAbsolutePath());
for(String filename : tempDir.list()) {
System.out.println("file: " + filename);
}
}
} else {
System.out.println("unable to find poi temp dir");
}
}
}

View File

@ -0,0 +1,88 @@
/*
* ====================================================================
* 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.eventusermodel.examples;
import java.io.FileInputStream;
import java.io.InputStream;
import org.apache.poi.crypt.examples.AesZipFileZipEntrySource;
import org.apache.poi.crypt.examples.EncryptionUtils;
import org.apache.poi.examples.util.TempFileUtils;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.eventusermodel.XSSFReader.SheetIterator;
/**
* An example that loads a password protected workbook and counts the sheets.
* The example highlights how to do this in streaming way.
* <p><ul>
* <li>The example demonstrates that all temp files are removed.
* <li><code>AesZipFileZipEntrySource</code> is used to ensure that temp files are encrypted.
* </ul><p>
*/
public class LoadPasswordProtectedXlsxStreaming {
public static void main(String[] args) {
try {
if(args.length != 2) {
throw new Exception("Expected 2 params: filename and password");
}
TempFileUtils.checkTempFiles();
String filename = args[0];
String password = args[1];
FileInputStream fis = new FileInputStream(filename);
try {
InputStream unencryptedStream = EncryptionUtils.decrypt(fis, password);
try {
printSheetCount(unencryptedStream);
} finally {
IOUtils.closeQuietly(unencryptedStream);
}
} finally {
IOUtils.closeQuietly(fis);
}
TempFileUtils.checkTempFiles();
} catch(Throwable t) {
t.printStackTrace();
}
}
public static void printSheetCount(final InputStream inputStream) throws Exception {
AesZipFileZipEntrySource source = AesZipFileZipEntrySource.createZipEntrySource(inputStream);
try {
OPCPackage pkg = OPCPackage.open(source);
try {
XSSFReader reader = new XSSFReader(pkg);
SheetIterator iter = (SheetIterator)reader.getSheetsData();
int count = 0;
while(iter.hasNext()) {
iter.next();
count++;
}
System.out.println("sheet count: " + count);
} finally {
IOUtils.closeQuietly(pkg);
}
} finally {
IOUtils.closeQuietly(source);
}
}
}

View File

@ -0,0 +1,113 @@
/*
* ====================================================================
* 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.examples;
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.crypt.examples.AesZipFileZipEntrySource;
import org.apache.poi.crypt.examples.EncryptedTempData;
import org.apache.poi.openxml4j.util.ZipEntrySource;
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.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SheetDataWriter;
public class SXSSFWorkbookWithCustomZipEntrySource extends SXSSFWorkbook {
public SXSSFWorkbookWithCustomZipEntrySource() {
super(20);
setCompressTempFiles(true);
}
@Override
public void write(OutputStream stream) throws IOException {
flushSheets();
EncryptedTempData tempData = new EncryptedTempData();
ZipEntrySource source = null;
try {
OutputStream os = tempData.getOutputStream();
try {
getXSSFWorkbook().write(os);
} finally {
IOUtils.closeQuietly(os);
}
// provide ZipEntrySource to poi which decrypts on the fly
source = AesZipFileZipEntrySource.createZipEntrySource(tempData.getInputStream());
injectData(source, stream);
} catch (GeneralSecurityException e) {
throw new IOException(e);
} finally {
tempData.dispose();
IOUtils.closeQuietly(source);
}
}
@Override
protected SheetDataWriter createSheetDataWriter() throws IOException {
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);
}
}
}

View File

@ -0,0 +1,112 @@
/*
* ====================================================================
* 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.examples;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import org.apache.poi.crypt.examples.EncryptedTempData;
import org.apache.poi.examples.util.TempFileUtils;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.poifs.crypt.EncryptionInfo;
import org.apache.poi.poifs.crypt.EncryptionMode;
import org.apache.poi.poifs.crypt.Encryptor;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.streaming.SXSSFCell;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
/**
* An example that outputs a simple generated workbook that is password protected.
* The example highlights how to do this in streaming way.
* <p><ul>
* <li>The example demonstrates that all temp files are removed.
* <li><code>SXSSFWorkbookWithCustomZipEntrySource</code> extends SXSSFWorkbook to ensure temp files are encrypted.
* </ul><p>
*/
public class SavePasswordProtectedXlsx {
public static void main(String[] args) {
try {
if(args.length != 2) {
throw new Exception("Expected 2 params: filename and password");
}
TempFileUtils.checkTempFiles();
String filename = args[0];
String password = args[1];
SXSSFWorkbookWithCustomZipEntrySource wb = new SXSSFWorkbookWithCustomZipEntrySource();
try {
for(int i = 0; i < 10; i++) {
SXSSFSheet sheet = wb.createSheet("Sheet" + i);
for(int r = 0; r < 1000; r++) {
SXSSFRow row = sheet.createRow(r);
for(int c = 0; c < 100; c++) {
SXSSFCell cell = row.createCell(c);
cell.setCellValue("abcd");
}
}
}
EncryptedTempData tempData = new EncryptedTempData();
try {
wb.write(tempData.getOutputStream());
save(tempData.getInputStream(), filename, password);
System.out.println("Saved " + filename);
} finally {
tempData.dispose();
}
} finally {
wb.close();
wb.dispose();
}
TempFileUtils.checkTempFiles();
} catch(Throwable t) {
t.printStackTrace();
}
}
public static void save(final InputStream inputStream, final String filename, final String pwd)
throws InvalidFormatException, IOException, GeneralSecurityException {
try {
POIFSFileSystem fs = new POIFSFileSystem();
EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile);
Encryptor enc = Encryptor.getInstance(info);
enc.confirmPassword(pwd);
OPCPackage opc = OPCPackage.open(inputStream);
try {
FileOutputStream fos = new FileOutputStream(filename);
try {
opc.save(enc.getDataStream(fs));
fs.writeFilesystem(fos);
} finally {
IOUtils.closeQuietly(fos);
}
} finally {
IOUtils.closeQuietly(opc);
}
} finally {
IOUtils.closeQuietly(inputStream);
}
}
}

View File

@ -0,0 +1,85 @@
/*
* ====================================================================
* 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.usermodel.examples;
import java.io.FileInputStream;
import java.io.InputStream;
import org.apache.poi.crypt.examples.AesZipFileZipEntrySource;
import org.apache.poi.crypt.examples.EncryptionUtils;
import org.apache.poi.examples.util.TempFileUtils;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
/**
* An example that loads a password protected workbook and counts the sheets.
* <p><ul>
* <li>The example demonstrates that all temp files are removed.
* <li><code>AesZipFileZipEntrySource</code> is used to ensure that temp files are encrypted.
* </ul><p>
*/
public class LoadPasswordProtectedXlsx {
public static void main(String[] args) {
try {
if(args.length != 2) {
throw new Exception("Expected 2 params: filename and password");
}
TempFileUtils.checkTempFiles();
String filename = args[0];
String password = args[1];
FileInputStream fis = new FileInputStream(filename);
try {
InputStream unencryptedStream = EncryptionUtils.decrypt(fis, password);
try {
printSheetCount(unencryptedStream);
} finally {
IOUtils.closeQuietly(unencryptedStream);
}
} finally {
IOUtils.closeQuietly(fis);
}
TempFileUtils.checkTempFiles();
} catch(Throwable t) {
t.printStackTrace();
}
}
public static void printSheetCount(final InputStream inputStream) throws Exception {
AesZipFileZipEntrySource source = AesZipFileZipEntrySource.createZipEntrySource(inputStream);
try {
OPCPackage pkg = OPCPackage.open(source);
try {
XSSFWorkbook workbook = new XSSFWorkbook(pkg);
try {
System.out.println("sheet count: " + workbook.getNumberOfSheets());
} finally {
IOUtils.closeQuietly(workbook);
}
} finally {
IOUtils.closeQuietly(pkg);
}
} finally {
IOUtils.closeQuietly(source);
}
}
}