#59893 - Forbid calls to InputStream.available

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1830400 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2018-04-27 21:38:19 +00:00
parent 2574fd2d3d
commit e0f7014503
22 changed files with 646 additions and 580 deletions

View File

@ -17,15 +17,17 @@
package org.apache.poi.poifs.poibrowser;
import java.io.*;
import org.apache.poi.poifs.filesystem.*;
import java.io.IOException;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
import org.apache.poi.util.IOUtils;
/**
* <p>Describes the most important (whatever that is) features of a
* {@link POIFSDocumentPath}.</p>
*/
public class DocumentDescriptor
class DocumentDescriptor
{
//arbitrarily selected; may need to increase
@ -54,26 +56,20 @@ public class DocumentDescriptor
public DocumentDescriptor(final String name,
final POIFSDocumentPath path,
final DocumentInputStream stream,
final int nrOfBytes)
{
final int nrOfBytes) {
this.name = name;
this.path = path;
this.stream = stream;
try
{
size = stream.available();
if (stream.markSupported())
{
try {
if (stream.markSupported()) {
stream.mark(nrOfBytes);
final byte[] b = IOUtils.safelyAllocate(nrOfBytes, MAX_RECORD_LENGTH);
final int read = stream.read(b, 0, Math.min(size, b.length));
bytes = new byte[read];
System.arraycopy(b, 0, bytes, 0, read);
bytes = IOUtils.toByteArray(stream, nrOfBytes, MAX_RECORD_LENGTH);
stream.reset();
} else {
bytes = new byte[0];
}
}
catch (IOException ex)
{
size = bytes.length + stream.available();
} catch (IOException ex) {
System.out.println(ex);
}
}

View File

@ -175,10 +175,12 @@ public class VariantSupport extends Variant {
try {
typedPropertyValue.readValue(lei);
} catch ( UnsupportedOperationException exc ) {
int propLength = Math.min( length, lei.available() );
final byte[] v = IOUtils.safelyAllocate(propLength, MAX_RECORD_LENGTH);
lei.readFully(v, 0, propLength);
try {
final byte[] v = IOUtils.toByteArray(lei, length, MAX_RECORD_LENGTH);
throw new ReadingNotSupportedException( type, v );
} catch (IOException e) {
throw new RuntimeException(e);
}
}
switch ( (int) type ) {

View File

@ -17,14 +17,13 @@
package org.apache.poi.hssf.record;
import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayInputStream;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
import org.apache.poi.util.LittleEndianInputStream;
import org.apache.poi.util.RecordFormatException;
/**
@ -85,8 +84,7 @@ public final class ObjRecord extends Record implements Cloneable {
*/
subrecords = new ArrayList<>();
ByteArrayInputStream bais = new ByteArrayInputStream(subRecordData);
LittleEndianInputStream subRecStream = new LittleEndianInputStream(bais);
LittleEndianByteArrayInputStream subRecStream = new LittleEndianByteArrayInputStream(subRecordData);
CommonObjectDataSubRecord cmo = (CommonObjectDataSubRecord)SubRecord.createSubRecord(subRecStream, 0);
subrecords.add(cmo);
while (true) {
@ -96,7 +94,7 @@ public final class ObjRecord extends Record implements Cloneable {
break;
}
}
int nRemainingBytes = bais.available();
final int nRemainingBytes = subRecordData.length-subRecStream.getReadIndex();
if (nRemainingBytes > 0) {
// At present (Oct-2008), most unit test samples have (subRecordData.length % 2 == 0)
_isPaddedToQuadByteMultiple = subRecordData.length % MAX_PAD_ALIGNMENT == 0;

View File

@ -32,6 +32,7 @@ import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.RecordFormatException;
import org.apache.poi.util.SuppressForbidden;
public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndianInput {
@ -39,7 +40,6 @@ public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndia
//arbitrarily selected; may need to increase
private static final int MAX_RECORD_LENGTH = 100_000;
private final EncryptionInfo info;
private ChunkedCipherInputStream ccis;
private final byte buffer[] = new byte[LittleEndianConsts.LONG_SIZE];
private boolean shouldSkipEncryptionOnCurrentRecord;
@ -55,8 +55,7 @@ public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndia
((PushbackInputStream)stream).unread(initialBuf);
}
this.info = info;
Decryptor dec = this.info.getDecryptor();
Decryptor dec = info.getDecryptor();
dec.setChunkSize(RC4_REKEYING_INTERVAL);
ccis = (ChunkedCipherInputStream)dec.getDataStream(stream, Integer.MAX_VALUE, 0);
@ -69,6 +68,7 @@ public final class Biff8DecryptingStream implements BiffHeaderInput, LittleEndia
}
@Override
@SuppressForbidden("just delegating")
public int available() {
return ccis.available();
}

View File

@ -97,7 +97,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
private int read(byte[] b, int off, int len, boolean readPlain) throws IOException {
int total = 0;
if (available() <= 0) {
if (remainingBytes() <= 0) {
return -1;
}
@ -112,7 +112,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
}
}
int count = (int)(chunk.length - (pos & chunkMask));
int avail = available();
int avail = remainingBytes();
if (avail == 0) {
return total;
}
@ -133,7 +133,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
}
@Override
public long skip(final long n) throws IOException {
public long skip(final long n) {
long start = pos;
long skip = Math.min(remainingBytes(), n);
@ -169,7 +169,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
}
@Override
public synchronized void reset() throws IOException {
public synchronized void reset() {
throw new UnsupportedOperationException();
}
@ -193,7 +193,7 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
}
final int todo = (int)Math.min(size, chunk.length);
int readBytes = 0, totalBytes = 0;
int readBytes, totalBytes = 0;
do {
readBytes = super.read(plain, totalBytes, todo-totalBytes);
totalBytes += Math.max(0, readBytes);
@ -211,10 +211,6 @@ public abstract class ChunkedCipherInputStream extends LittleEndianInputStream {
/**
* Helper function for overriding the cipher invocation, i.e. XOR doesn't use a cipher
* and uses it's own implementation
*
* @throws BadPaddingException
* @throws IllegalBlockSizeException
* @throws ShortBufferException
*/
protected int invokeCipher(int totalBytes, boolean doFinal) throws GeneralSecurityException {
if (doFinal) {

View File

@ -273,11 +273,9 @@ public class POIFSReader
while (listeners.hasNext())
{
POIFSReaderListener listener = listeners.next();
listener.processPOIFSReaderEvent(
new POIFSReaderEvent(
new DocumentInputStream(document), path,
name));
try (DocumentInputStream dis = new DocumentInputStream(document)) {
listener.processPOIFSReaderEvent(new POIFSReaderEvent(dis, path, name));
}
}
}
else

View File

@ -138,7 +138,7 @@ public final class ODocumentInputStream extends DocumentInputStream {
if (atEOD()) {
return EOF;
}
int limit = Math.min(available(), len);
int limit = Math.min(_document_size - _current_offset, len);
readFully(b, off, limit);
return limit;
}

View File

@ -28,6 +28,7 @@ import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
@ -39,6 +40,7 @@ import javax.imageio.ImageTypeSpecifier;
import javax.imageio.stream.ImageInputStream;
import javax.imageio.stream.MemoryCacheImageInputStream;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@ -69,20 +71,24 @@ public class BitmapImageRenderer implements ImageRenderer {
* @return the bufferedImage or null, if there was no image reader for this content type
* @throws IOException thrown if there was an error while processing the image
*/
private static BufferedImage readImage(InputStream data, String contentType) throws IOException {
private static BufferedImage readImage(final InputStream data, final String contentType) throws IOException {
IOException lastException = null;
BufferedImage img = null;
if (data.markSupported()) {
data.mark(data.available());
final ByteArrayInputStream bis;
if (data instanceof ByteArrayInputStream) {
bis = (ByteArrayInputStream)data;
} else {
ByteArrayOutputStream bos = new ByteArrayOutputStream(0x3FFFF);
IOUtils.copy(data, bos);
bis = new ByteArrayInputStream(bos.toByteArray());
}
// currently don't use FileCacheImageInputStream,
// because of the risk of filling the file handles (see #59166)
ImageInputStream iis = new MemoryCacheImageInputStream(data);
ImageInputStream iis = new MemoryCacheImageInputStream(bis);
try {
iis = new MemoryCacheImageInputStream(data);
iis.mark();
Iterator<ImageReader> iter = ImageIO.getImageReaders(iis);
while (img==null && iter.hasNext()) {
ImageReader reader = iter.next();
@ -90,21 +96,11 @@ public class BitmapImageRenderer implements ImageRenderer {
// 0:default mode, 1:fallback mode
for (int mode=0; img==null && mode<3; mode++) {
lastException = null;
try {
iis.reset();
} catch (IOException e) {
if (data.markSupported()) {
data.reset();
data.mark(data.available());
if (mode > 0) {
bis.reset();
iis.close();
iis = new MemoryCacheImageInputStream(data);
} else {
// can't restore the input stream, so we need to stop processing here
lastException = e;
break;
iis = new MemoryCacheImageInputStream(bis);
}
}
iis.mark();
try {

View File

@ -116,21 +116,57 @@ public final class IOUtils {
* @return A byte array with the read bytes.
* @throws IOException If reading data fails or EOF is encountered too early for the given length.
*/
public static byte[] toByteArray(InputStream stream, int length) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream(length == Integer.MAX_VALUE ? 4096 : length);
public static byte[] toByteArray(InputStream stream, final int length) throws IOException {
return toByteArray(stream, length, Integer.MAX_VALUE);
}
/**
* Reads up to {@code length} bytes from the input stream, and returns the bytes read.
*
* @param stream The byte stream of data to read.
* @param length The maximum length to read, use {@link Integer#MAX_VALUE} to read the stream
* until EOF
* @param maxLength if the input is equal to/longer than {@code maxLength} bytes,
* then throw an {@link IOException} complaining about the length.
* use {@link Integer#MAX_VALUE} to disable the check
* @return A byte array with the read bytes.
* @throws IOException If reading data fails or EOF is encountered too early for the given length.
*/
public static byte[] toByteArray(InputStream stream, final long length, final int maxLength) throws IOException {
if (length < 0L || maxLength < 0L) {
throw new RecordFormatException("Can't allocate an array of length < 0");
}
if (length > (long)Integer.MAX_VALUE) {
throw new RecordFormatException("Can't allocate an array > "+Integer.MAX_VALUE);
}
if (BYTE_ARRAY_MAX_OVERRIDE > 0) {
if (length > BYTE_ARRAY_MAX_OVERRIDE) {
throwRFE(length, BYTE_ARRAY_MAX_OVERRIDE);
}
} else if (length > maxLength) {
throwRFE(length, maxLength);
}
final int len = Math.min((int)length, maxLength);
ByteArrayOutputStream baos = new ByteArrayOutputStream(len == Integer.MAX_VALUE ? 4096 : len);
byte[] buffer = new byte[4096];
int totalBytes = 0, readBytes;
do {
readBytes = stream.read(buffer, 0, Math.min(buffer.length, length-totalBytes));
readBytes = stream.read(buffer, 0, Math.min(buffer.length, len-totalBytes));
totalBytes += Math.max(readBytes,0);
if (readBytes > 0) {
baos.write(buffer, 0, readBytes);
}
} while (totalBytes < length && readBytes > -1);
} while (totalBytes < len && readBytes > -1);
if (length != Integer.MAX_VALUE && totalBytes < length) {
throw new IOException("unexpected EOF");
if (maxLength != Integer.MAX_VALUE && totalBytes == maxLength) {
throw new IOException("MaxLength ("+maxLength+") reached - stream seems to be invalid.");
}
if (len != Integer.MAX_VALUE && totalBytes < len) {
throw new EOFException("unexpected EOF");
}
return baos.toByteArray();
@ -350,19 +386,19 @@ public final class IOUtils {
*
* @param inp The {@link InputStream} which provides the data
* @param out The {@link OutputStream} to write the data to
* @return the amount of bytes copied
*
* @throws IOException If copying the data fails.
*/
public static void copy(InputStream inp, OutputStream out) throws IOException {
byte[] buff = new byte[4096];
int count;
while ((count = inp.read(buff)) != -1) {
if (count < -1) {
throw new RecordFormatException("Can't have read < -1 bytes");
}
public static long copy(InputStream inp, OutputStream out) throws IOException {
final byte[] buff = new byte[4096];
long totalCount = 0;
for (int count; (count = inp.read(buff)) != -1; totalCount += count) {
if (count > 0) {
out.write(buff, 0, count);
}
}
return totalCount;
}
/**
@ -370,16 +406,18 @@ public final class IOUtils {
*
* @param srcStream The {@link InputStream} which provides the data
* @param destFile The file where the data should be stored
* @return the amount of bytes copied
*
* @throws IOException If the target directory does not exist and cannot be created
* or if copying the data fails.
*/
public static void copy(InputStream srcStream, File destFile) throws IOException {
public static long copy(InputStream srcStream, File destFile) throws IOException {
File destDirectory = destFile.getParentFile();
if (!(destDirectory.exists() || destDirectory.mkdirs())) {
throw new RuntimeException("Can't create destination directory: "+destDirectory);
}
try (OutputStream destStream = new FileOutputStream(destFile)) {
IOUtils.copy(srcStream, destStream);
return IOUtils.copy(srcStream, destStream);
}
}

View File

@ -30,6 +30,7 @@ import java.util.Date;
import org.apache.poi.POIXMLProperties.CoreProperties;
import org.apache.poi.openxml4j.util.Nullable;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LocaleUtil;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
@ -249,20 +250,18 @@ public final class TestPOIXMLProperties {
// Adding / changing
ByteArrayInputStream imageData = new ByteArrayInputStream(new byte[1]);
assertEquals(1, imageData.available());
noThumbProps.setThumbnail("Testing.png", imageData);
assertNotNull(noThumbProps.getThumbnailPart());
assertEquals("/Testing.png", noThumbProps.getThumbnailFilename());
assertNotNull(noThumbProps.getThumbnailImage());
assertEquals(1, noThumbProps.getThumbnailImage().available());
assertEquals(1, IOUtils.toByteArray(noThumbProps.getThumbnailImage()).length);
imageData = new ByteArrayInputStream(new byte[2]);
assertEquals(2, imageData.available());
noThumbProps.setThumbnail("Testing2.png", imageData);
assertNotNull(noThumbProps.getThumbnailPart());
assertEquals("/Testing.png", noThumbProps.getThumbnailFilename());
assertNotNull(noThumbProps.getThumbnailImage());
assertEquals(2, noThumbProps.getThumbnailImage().available());
assertEquals(2, IOUtils.toByteArray(noThumbProps.getThumbnailImage()).length);
}
private static String zeroPad(long i) {

View File

@ -16,6 +16,7 @@
==================================================================== */
package org.apache.poi.openxml4j.util;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.junit.Test;
@ -39,8 +40,9 @@ public class TestZipSecureFile {
while (entries.hasMoreElements()) {
ZipEntry entry = entries.nextElement();
InputStream inputStream = secureFile.getInputStream(entry);
assertTrue(inputStream.available() > 0);
try (InputStream inputStream = secureFile.getInputStream(entry)) {
assertTrue(IOUtils.toByteArray(inputStream).length > 0);
}
}
}
}

View File

@ -24,7 +24,6 @@ import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
@ -39,107 +38,92 @@ import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.XSSFTestDataSamples;
import org.junit.Assume;
import org.junit.Test;
public class TestDecryptor {
private static final POIDataSamples samples = POIDataSamples.getPOIFSInstance();
@Test
public void passwordVerification() throws IOException, GeneralSecurityException {
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
try (InputStream is = samples.openResourceAsStream("protect.xlsx");
POIFSFileSystem fs = new POIFSFileSystem(is)) {
EncryptionInfo info = new EncryptionInfo(fs);
Decryptor d = Decryptor.getInstance(info);
assertTrue(d.verifyPassword(Decryptor.DEFAULT_PASSWORD));
fs.close();
}
}
@Test
public void decrypt() throws IOException, GeneralSecurityException {
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protect.xlsx"));
try (InputStream is = samples.openResourceAsStream("protect.xlsx");
POIFSFileSystem fs = new POIFSFileSystem(is)) {
EncryptionInfo info = new EncryptionInfo(fs);
Decryptor d = Decryptor.getInstance(info);
d.verifyPassword(Decryptor.DEFAULT_PASSWORD);
zipOk(fs.getRoot(), d);
fs.close();
}
}
@Test
public void agile() throws IOException, GeneralSecurityException {
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protected_agile.docx"));
try (InputStream is = samples.openResourceAsStream("protected_agile.docx");
POIFSFileSystem fs = new POIFSFileSystem(is)) {
EncryptionInfo info = new EncryptionInfo(fs);
assertTrue(info.getVersionMajor() == 4 && info.getVersionMinor() == 4);
Decryptor d = Decryptor.getInstance(info);
assertTrue(d.verifyPassword(Decryptor.DEFAULT_PASSWORD));
zipOk(fs.getRoot(), d);
fs.close();
}
}
private void zipOk(DirectoryNode root, Decryptor d) throws IOException, GeneralSecurityException {
ZipInputStream zin = new ZipInputStream(d.getDataStream(root));
try (ZipInputStream zin = new ZipInputStream(d.getDataStream(root))) {
while (true) {
ZipEntry entry = zin.getNextEntry();
if (entry==null) {
if (entry == null) {
break;
}
// crc32 is checked within zip-stream
if (entry.isDirectory()) {
continue;
}
zin.skip(entry.getSize());
assertEquals(entry.getSize() - 1, zin.skip(entry.getSize() - 1));
byte buf[] = new byte[10];
int readBytes = zin.read(buf);
// zin.available() doesn't work for entries
assertEquals("size failed for "+entry.getName(), -1, readBytes);
assertEquals("size failed for " + entry.getName(), 1, readBytes);
}
}
zin.close();
}
@Test
public void dataLength() throws Exception {
POIFSFileSystem fs = new POIFSFileSystem(POIDataSamples.getPOIFSInstance().openResourceAsStream("protected_agile.docx"));
try (InputStream fsIs = samples.openResourceAsStream("protected_agile.docx");
POIFSFileSystem fs = new POIFSFileSystem(fsIs)) {
EncryptionInfo info = new EncryptionInfo(fs);
Decryptor d = Decryptor.getInstance(info);
d.verifyPassword(Decryptor.DEFAULT_PASSWORD);
InputStream is = d.getDataStream(fs);
try (InputStream is = d.getDataStream(fs)) {
long len = d.getLength();
assertEquals(12810, len);
byte[] buf = new byte[(int)len];
is.read(buf);
byte[] buf = new byte[(int) len];
assertEquals(12810, is.read(buf));
ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(buf));
while (true) {
ZipEntry entry = zin.getNextEntry();
if (entry==null) {
if (entry == null) {
break;
}
while (zin.available()>0) {
zin.skip(zin.available());
IOUtils.toByteArray(zin);
}
}
}
}
@ -148,7 +132,7 @@ public class TestDecryptor {
public void bug57080() throws Exception {
// the test file contains a wrong ole entry size, produced by extenxls
// the fix limits the available size and tries to read all entries
File f = POIDataSamples.getPOIFSInstance().getFile("extenxls_pwd123.xlsx");
File f = samples.getFile("extenxls_pwd123.xlsx");
try (NPOIFSFileSystem fs = new NPOIFSFileSystem(f, true)) {
EncryptionInfo info = new EncryptionInfo(fs);
@ -174,14 +158,12 @@ public class TestDecryptor {
@Test
public void test58616() throws IOException, GeneralSecurityException {
FileInputStream fis = new FileInputStream(XSSFTestDataSamples.getSampleFile("58616.xlsx"));
POIFSFileSystem pfs = new POIFSFileSystem(fis);
try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("58616.xlsx");
POIFSFileSystem pfs = new POIFSFileSystem(is)) {
EncryptionInfo info = new EncryptionInfo(pfs);
Decryptor dec = Decryptor.getInstance(info);
//dec.verifyPassword(null);
dec.getDataStream(pfs);
pfs.close();
fis.close();
dec.getDataStream(pfs).close();
}
}
@Test
@ -189,19 +171,12 @@ public class TestDecryptor {
int maxKeyLen = Cipher.getMaxAllowedKeyLength("AES");
Assume.assumeTrue("Please install JCE Unlimited Strength Jurisdiction Policy files for AES 256", maxKeyLen == 2147483647);
InputStream is = POIDataSamples.getPOIFSInstance().openResourceAsStream("60320-protected.xlsx");
POIFSFileSystem fs = new POIFSFileSystem(is);
is.close();
try (InputStream is = samples.openResourceAsStream("60320-protected.xlsx");
POIFSFileSystem fs = new POIFSFileSystem(is)) {
EncryptionInfo info = new EncryptionInfo(fs);
Decryptor d = Decryptor.getInstance(info);
boolean b = d.verifyPassword("Test001!!");
assertTrue(b);
assertTrue(d.verifyPassword("Test001!!"));
zipOk(fs.getRoot(), d);
fs.close();
}
}
}

View File

@ -36,18 +36,20 @@ import javax.crypto.Cipher;
import org.apache.poi.POIDataSamples;
import org.apache.poi.openxml4j.opc.ContentTypes;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.openxml4j.opc.PackageAccess;
import org.apache.poi.poifs.crypt.agile.AgileDecryptor;
import org.apache.poi.poifs.crypt.agile.AgileEncryptionHeader;
import org.apache.poi.poifs.crypt.agile.AgileEncryptionVerifier;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentNode;
import org.apache.poi.poifs.filesystem.Entry;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.BoundedInputStream;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.TempFile;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.junit.Assume;
import org.junit.Ignore;
import org.junit.Test;
@ -59,35 +61,37 @@ public class TestEncryptor {
// ... at least the output can be opened in Excel Viewer
String password = "pass";
InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleMultiCell.xlsx");
ByteArrayOutputStream payloadExpected = new ByteArrayOutputStream();
IOUtils.copy(is, payloadExpected);
is.close();
final byte[] payloadExpected;
try (InputStream is = POIDataSamples.getSpreadSheetInstance().openResourceAsStream("SimpleMultiCell.xlsx")) {
payloadExpected = IOUtils.toByteArray(is);
}
POIFSFileSystem fs = new POIFSFileSystem();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (POIFSFileSystem fs = new POIFSFileSystem()) {
EncryptionInfo ei = new EncryptionInfo(EncryptionMode.binaryRC4);
Encryptor enc = ei.getEncryptor();
enc.confirmPassword(password);
OutputStream os = enc.getDataStream(fs.getRoot());
payloadExpected.writeTo(os);
os.close();
try (OutputStream os = enc.getDataStream(fs.getRoot())) {
os.write(payloadExpected);
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
fs.writeFilesystem(bos);
}
fs = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
ei = new EncryptionInfo(fs);
final byte[] payloadActual;
try (POIFSFileSystem fs = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()))) {
EncryptionInfo ei = new EncryptionInfo(fs);
Decryptor dec = ei.getDecryptor();
boolean b = dec.verifyPassword(password);
assertTrue(b);
ByteArrayOutputStream payloadActual = new ByteArrayOutputStream();
is = dec.getDataStream(fs.getRoot());
IOUtils.copy(is,payloadActual);
is.close();
try (InputStream is = dec.getDataStream(fs.getRoot())) {
payloadActual = IOUtils.toByteArray(is);
}
}
assertArrayEquals(payloadExpected.toByteArray(), payloadActual.toByteArray());
assertArrayEquals(payloadExpected, payloadActual);
}
@Test
@ -97,43 +101,47 @@ public class TestEncryptor {
File file = POIDataSamples.getDocumentInstance().getFile("bug53475-password-is-pass.docx");
String pass = "pass";
NPOIFSFileSystem nfs = new NPOIFSFileSystem(file);
final byte[] payloadExpected, encPackExpected;
final long decPackLenExpected;
final EncryptionInfo infoExpected;
final Decryptor decExpected;
try (NPOIFSFileSystem nfs = new NPOIFSFileSystem(file, true)) {
// Check the encryption details
EncryptionInfo infoExpected = new EncryptionInfo(nfs);
Decryptor decExpected = Decryptor.getInstance(infoExpected);
infoExpected = new EncryptionInfo(nfs);
decExpected = Decryptor.getInstance(infoExpected);
boolean passed = decExpected.verifyPassword(pass);
assertTrue("Unable to process: document is encrypted", passed);
// extract the payload
InputStream is = decExpected.getDataStream(nfs);
byte payloadExpected[] = IOUtils.toByteArray(is);
is.close();
try (InputStream is = decExpected.getDataStream(nfs)) {
payloadExpected = IOUtils.toByteArray(is);
}
long decPackLenExpected = decExpected.getLength();
decPackLenExpected = decExpected.getLength();
assertEquals(decPackLenExpected, payloadExpected.length);
is = nfs.getRoot().createDocumentInputStream(Decryptor.DEFAULT_POIFS_ENTRY);
is = new BoundedInputStream(is, is.available()-16); // ignore padding block
byte encPackExpected[] = IOUtils.toByteArray(is);
is.close();
// listDir(nfs.getRoot(), "orig", "");
nfs.close();
final DirectoryNode root = nfs.getRoot();
final DocumentEntry entry = (DocumentEntry)root.getEntry(Decryptor.DEFAULT_POIFS_ENTRY);
try (InputStream is = root.createDocumentInputStream(entry)) {
// ignore padding block
encPackExpected = IOUtils.toByteArray(is, entry.getSize()-16);
}
}
// check that same verifier/salt lead to same hashes
byte verifierSaltExpected[] = infoExpected.getVerifier().getSalt();
byte verifierExpected[] = decExpected.getVerifier();
byte keySalt[] = infoExpected.getHeader().getKeySalt();
byte keySpec[] = decExpected.getSecretKey().getEncoded();
byte integritySalt[] = decExpected.getIntegrityHmacKey();
final byte verifierSaltExpected[] = infoExpected.getVerifier().getSalt();
final byte verifierExpected[] = decExpected.getVerifier();
final byte keySalt[] = infoExpected.getHeader().getKeySalt();
final byte keySpec[] = decExpected.getSecretKey().getEncoded();
final byte integritySalt[] = decExpected.getIntegrityHmacKey();
// the hmacs of the file always differ, as we use PKCS5-padding to pad the bytes
// whereas office just uses random bytes
// byte integrityHash[] = d.getIntegrityHmacValue();
POIFSFileSystem fs = new POIFSFileSystem();
EncryptionInfo infoActual = new EncryptionInfo(
final EncryptionInfo infoActual = new EncryptionInfo(
EncryptionMode.agile
, infoExpected.getVerifier().getCipherAlgorithm()
, infoExpected.getVerifier().getHashAlgorithm()
@ -145,35 +153,37 @@ public class TestEncryptor {
Encryptor e = Encryptor.getInstance(infoActual);
e.confirmPassword(pass, keySpec, keySalt, verifierExpected, verifierSaltExpected, integritySalt);
OutputStream os = e.getDataStream(fs);
IOUtils.copy(new ByteArrayInputStream(payloadExpected), os);
os.close();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
try (POIFSFileSystem fs = new POIFSFileSystem()) {
try (OutputStream os = e.getDataStream(fs)) {
os.write(payloadExpected);
}
fs.writeFilesystem(bos);
fs.close();
}
nfs = new NPOIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
infoActual = new EncryptionInfo(nfs.getRoot());
Decryptor decActual = Decryptor.getInstance(infoActual);
passed = decActual.verifyPassword(pass);
final EncryptionInfo infoActual2;
final byte[] payloadActual, encPackActual;
final long decPackLenActual;
try (NPOIFSFileSystem nfs = new NPOIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()))) {
infoActual2 = new EncryptionInfo(nfs.getRoot());
Decryptor decActual = Decryptor.getInstance(infoActual2);
boolean passed = decActual.verifyPassword(pass);
assertTrue("Unable to process: document is encrypted", passed);
// extract the payload
is = decActual.getDataStream(nfs);
byte payloadActual[] = IOUtils.toByteArray(is);
is.close();
try (InputStream is = decActual.getDataStream(nfs)) {
payloadActual = IOUtils.toByteArray(is);
}
long decPackLenActual = decActual.getLength();
decPackLenActual = decActual.getLength();
is = nfs.getRoot().createDocumentInputStream(Decryptor.DEFAULT_POIFS_ENTRY);
is = new BoundedInputStream(is, is.available()-16); // ignore padding block
byte encPackActual[] = IOUtils.toByteArray(is);
is.close();
// listDir(nfs.getRoot(), "copy", "");
nfs.close();
final DirectoryNode root = nfs.getRoot();
final DocumentEntry entry = (DocumentEntry)root.getEntry(Decryptor.DEFAULT_POIFS_ENTRY);
try (InputStream is = root.createDocumentInputStream(entry)) {
// ignore padding block
encPackActual = IOUtils.toByteArray(is, entry.getSize()-16);
}
}
AgileEncryptionHeader aehExpected = (AgileEncryptionHeader)infoExpected.getHeader();
AgileEncryptionHeader aehActual = (AgileEncryptionHeader)infoActual.getHeader();
@ -186,32 +196,33 @@ public class TestEncryptor {
@Test
public void standardEncryption() throws Exception {
File file = POIDataSamples.getDocumentInstance().getFile("bug53475-password-is-solrcell.docx");
String pass = "solrcell";
final String pass = "solrcell";
NPOIFSFileSystem nfs = new NPOIFSFileSystem(file);
final byte[] payloadExpected;
final EncryptionInfo infoExpected;
final Decryptor d;
try (NPOIFSFileSystem nfs = new NPOIFSFileSystem(file, true)) {
// Check the encryption details
EncryptionInfo infoExpected = new EncryptionInfo(nfs);
Decryptor d = Decryptor.getInstance(infoExpected);
infoExpected = new EncryptionInfo(nfs);
d = Decryptor.getInstance(infoExpected);
boolean passed = d.verifyPassword(pass);
assertTrue("Unable to process: document is encrypted", passed);
// extract the payload
ByteArrayOutputStream bos = new ByteArrayOutputStream();
InputStream is = d.getDataStream(nfs);
IOUtils.copy(is, bos);
is.close();
nfs.close();
byte payloadExpected[] = bos.toByteArray();
try (InputStream is = d.getDataStream(nfs)) {
payloadExpected = IOUtils.toByteArray(is);
}
}
// check that same verifier/salt lead to same hashes
byte verifierSaltExpected[] = infoExpected.getVerifier().getSalt();
byte verifierExpected[] = d.getVerifier();
byte keySpec[] = d.getSecretKey().getEncoded();
byte keySalt[] = infoExpected.getHeader().getKeySalt();
final byte verifierSaltExpected[] = infoExpected.getVerifier().getSalt();
final byte verifierExpected[] = d.getVerifier();
final byte keySpec[] = d.getSecretKey().getEncoded();
final byte keySalt[] = infoExpected.getHeader().getKeySalt();
EncryptionInfo infoActual = new EncryptionInfo(
final EncryptionInfo infoActual = new EncryptionInfo(
EncryptionMode.standard
, infoExpected.getVerifier().getCipherAlgorithm()
, infoExpected.getVerifier().getHashAlgorithm()
@ -220,7 +231,7 @@ public class TestEncryptor {
, infoExpected.getVerifier().getChainingMode()
);
Encryptor e = Encryptor.getInstance(infoActual);
final Encryptor e = Encryptor.getInstance(infoActual);
e.confirmPassword(pass, keySpec, keySalt, verifierExpected, verifierSaltExpected, null);
assertArrayEquals(infoExpected.getVerifier().getEncryptedVerifier(), infoActual.getVerifier().getEncryptedVerifier());
@ -229,7 +240,10 @@ public class TestEncryptor {
// now we use a newly generated salt/verifier and check
// if the file content is still the same
infoActual = new EncryptionInfo(
final byte[] encBytes;
try (POIFSFileSystem fs = new POIFSFileSystem()) {
final EncryptionInfo infoActual2 = new EncryptionInfo(
EncryptionMode.standard
, infoExpected.getVerifier().getCipherAlgorithm()
, infoExpected.getVerifier().getHashAlgorithm()
@ -238,36 +252,28 @@ public class TestEncryptor {
, infoExpected.getVerifier().getChainingMode()
);
e = Encryptor.getInstance(infoActual);
e.confirmPassword(pass);
final Encryptor e2 = Encryptor.getInstance(infoActual2);
e2.confirmPassword(pass);
POIFSFileSystem fs = new POIFSFileSystem();
OutputStream os = e.getDataStream(fs);
IOUtils.copy(new ByteArrayInputStream(payloadExpected), os);
os.close();
try (OutputStream os = e2.getDataStream(fs)) {
os.write(payloadExpected);
}
bos.reset();
final ByteArrayOutputStream bos = new ByteArrayOutputStream(50000);
fs.writeFilesystem(bos);
encBytes = bos.toByteArray();
}
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
final byte[] payloadActual;
try (NPOIFSFileSystem nfs = new NPOIFSFileSystem(new ByteArrayInputStream(encBytes))) {
final EncryptionInfo ei = new EncryptionInfo(nfs);
Decryptor d2 = Decryptor.getInstance(ei);
assertTrue("Unable to process: document is encrypted", d2.verifyPassword(pass));
// FileOutputStream fos = new FileOutputStream("encrypted.docx");
// IOUtils.copy(bis, fos);
// fos.close();
// bis.reset();
nfs = new NPOIFSFileSystem(bis);
infoExpected = new EncryptionInfo(nfs);
d = Decryptor.getInstance(infoExpected);
passed = d.verifyPassword(pass);
assertTrue("Unable to process: document is encrypted", passed);
bos.reset();
is = d.getDataStream(nfs);
IOUtils.copy(is, bos);
is.close();
nfs.close();
byte payloadActual[] = bos.toByteArray();
try (InputStream is = d2.getDataStream(nfs)) {
payloadActual = IOUtils.toByteArray(is);
}
}
assertArrayEquals(payloadExpected, payloadActual);
}
@ -281,8 +287,9 @@ public class TestEncryptor {
@Test
public void encryptPackageWithoutCoreProperties() throws Exception {
// Open our file without core properties
File inp = POIDataSamples.getOpenXML4JInstance().getFile("OPCCompliance_NoCoreProperties.xlsx");
OPCPackage pkg = OPCPackage.open(inp.getPath());
final byte[] encBytes;
try (InputStream is = POIDataSamples.getOpenXML4JInstance().openResourceAsStream("OPCCompliance_NoCoreProperties.xlsx");
OPCPackage pkg = OPCPackage.open(is)) {
// It doesn't have any core properties yet
assertEquals(0, pkg.getPartsByContentType(ContentTypes.CORE_PROPERTIES_PART).size());
@ -292,52 +299,51 @@ public class TestEncryptor {
// Encrypt it
EncryptionInfo info = new EncryptionInfo(EncryptionMode.agile);
NPOIFSFileSystem fs = new NPOIFSFileSystem();
Encryptor enc = info.getEncryptor();
enc.confirmPassword("password");
OutputStream os = enc.getDataStream(fs);
try (NPOIFSFileSystem fs = new NPOIFSFileSystem()) {
try (OutputStream os = enc.getDataStream(fs)) {
pkg.save(os);
os.close();
pkg.revert();
}
// Save the resulting OLE2 document, and re-open it
ByteArrayOutputStream baos = new ByteArrayOutputStream();
fs.writeFilesystem(baos);
fs.close();
encBytes = baos.toByteArray();
}
}
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
NPOIFSFileSystem inpFS = new NPOIFSFileSystem(bais);
try (NPOIFSFileSystem inpFS = new NPOIFSFileSystem(new ByteArrayInputStream(encBytes))) {
// Check we can decrypt it
info = new EncryptionInfo(inpFS);
EncryptionInfo info = new EncryptionInfo(inpFS);
Decryptor d = Decryptor.getInstance(info);
assertEquals(true, d.verifyPassword("password"));
OPCPackage inpPkg = OPCPackage.open(d.getDataStream(inpFS));
try (OPCPackage inpPkg = OPCPackage.open(d.getDataStream(inpFS))) {
// Check it now has empty core properties
assertEquals(1, inpPkg.getPartsByContentType(ContentTypes.CORE_PROPERTIES_PART).size());
assertNotNull(inpPkg.getPackageProperties());
assertNotNull(inpPkg.getPackageProperties().getLanguageProperty());
assertNull(inpPkg.getPackageProperties().getLanguageProperty().getValue());
inpPkg.close();
inpFS.close();
}
}
}
@Test
@Ignore
public void inPlaceRewrite() throws Exception {
File f = TempFile.createTempFile("protected_agile", ".docx");
// File f = new File("protected_agile.docx");
FileOutputStream fos = new FileOutputStream(f);
InputStream fis = POIDataSamples.getPOIFSInstance().openResourceAsStream("protected_agile.docx");
IOUtils.copy(fis, fos);
fis.close();
fos.close();
NPOIFSFileSystem fs = new NPOIFSFileSystem(f, false);
try (FileOutputStream fos = new FileOutputStream(f);
InputStream fis = POIDataSamples.getPOIFSInstance().openResourceAsStream("protected_agile.docx")) {
IOUtils.copy(fis, fos);
}
try (NPOIFSFileSystem fs = new NPOIFSFileSystem(f, false)) {
// decrypt the protected file - in this case it was encrypted with the default password
EncryptionInfo encInfo = new EncryptionInfo(fs);
@ -345,21 +351,23 @@ public class TestEncryptor {
boolean b = d.verifyPassword(Decryptor.DEFAULT_PASSWORD);
assertTrue(b);
try (InputStream docIS = d.getDataStream(fs);
XWPFDocument docx = new XWPFDocument(docIS)) {
// do some strange things with it ;)
InputStream docIS = d.getDataStream(fs);
XWPFDocument docx = new XWPFDocument(docIS);
docx.getParagraphArray(0).insertNewRun(0).setText("POI was here! All your base are belong to us!");
docx.getParagraphArray(0).insertNewRun(1).addBreak();
XWPFParagraph p = docx.getParagraphArray(0);
p.insertNewRun(0).setText("POI was here! All your base are belong to us!");
p.insertNewRun(1).addBreak();
// and encrypt it again
Encryptor e = encInfo.getEncryptor();
e.confirmPassword("AYBABTU");
docx.write(e.getDataStream(fs));
docx.close();
docIS.close();
docx.close();
fs.close();
try (OutputStream os = e.getDataStream(fs)) {
docx.write(os);
}
}
}
}
@ -437,21 +445,23 @@ public class TestEncryptor {
// existing = getCipher(skey, header.getCipherAlgorithm(), header.getChainingMode(), header.getKeySalt(), encryptionMode, padding);
// }
InputStream is = POIDataSamples.getPOIFSInstance().openResourceAsStream("60320-protected.xlsx");
POIFSFileSystem fsOrig = new POIFSFileSystem(is);
is.close();
EncryptionInfo infoOrig = new EncryptionInfo(fsOrig);
final EncryptionInfo infoOrig;
final byte[] zipInput, epOrigBytes;
try (InputStream is = POIDataSamples.getPOIFSInstance().openResourceAsStream("60320-protected.xlsx");
POIFSFileSystem fsOrig = new POIFSFileSystem(is)) {
infoOrig = new EncryptionInfo(fsOrig);
Decryptor decOrig = infoOrig.getDecryptor();
boolean b = decOrig.verifyPassword("Test001!!");
assertTrue(b);
InputStream decIn = decOrig.getDataStream(fsOrig);
byte[] zipInput = IOUtils.toByteArray(decIn);
decIn.close();
try (InputStream decIn = decOrig.getDataStream(fsOrig)) {
zipInput = IOUtils.toByteArray(decIn);
}
InputStream epOrig = fsOrig.getRoot().createDocumentInputStream("EncryptedPackage");
try (InputStream epOrig = fsOrig.getRoot().createDocumentInputStream("EncryptedPackage")) {
// ignore the 16 padding bytes
byte[] epOrigBytes = IOUtils.toByteArray(epOrig, 9400);
epOrig.close();
epOrigBytes = IOUtils.toByteArray(epOrig, 9400);
}
}
EncryptionInfo eiNew = new EncryptionInfo(EncryptionMode.agile);
AgileEncryptionHeader aehHeader = (AgileEncryptionHeader)eiNew.getHeader();
@ -472,26 +482,29 @@ public class TestEncryptor {
infoOrig.getVerifier().getSalt(),
infoOrig.getDecryptor().getIntegrityHmacKey()
);
NPOIFSFileSystem fsNew = new NPOIFSFileSystem();
OutputStream os = enc.getDataStream(fsNew);
final byte[] epNewBytes;
final EncryptionInfo infoReload;
try (NPOIFSFileSystem fsNew = new NPOIFSFileSystem()) {
try (OutputStream os = enc.getDataStream(fsNew)) {
os.write(zipInput);
os.close();
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
fsNew.writeFilesystem(bos);
fsNew.close();
NPOIFSFileSystem fsReload = new NPOIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
InputStream epReload = fsReload.getRoot().createDocumentInputStream("EncryptedPackage");
byte[] epNewBytes = IOUtils.toByteArray(epReload, 9400);
epReload.close();
try (NPOIFSFileSystem fsReload = new NPOIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()))) {
infoReload = new EncryptionInfo(fsReload);
try (InputStream epReload = fsReload.getRoot().createDocumentInputStream("EncryptedPackage")) {
epNewBytes = IOUtils.toByteArray(epReload, 9400);
}
}
}
assertArrayEquals(epOrigBytes, epNewBytes);
EncryptionInfo infoReload = new EncryptionInfo(fsOrig);
Decryptor decReload = infoReload.getDecryptor();
b = decReload.verifyPassword("Test001!!");
assertTrue(b);
assertTrue(decReload.verifyPassword("Test001!!"));
AgileEncryptionHeader aehOrig = (AgileEncryptionHeader)infoOrig.getHeader();
AgileEncryptionHeader aehReload = (AgileEncryptionHeader)infoReload.getHeader();
@ -529,7 +542,5 @@ public class TestEncryptor {
// assertArrayEquals(adOrig.getIntegrityHmacValue(), adReload.getIntegrityHmacValue());
assertArrayEquals(adOrig.getSecretKey().getEncoded(), adReload.getSecretKey().getEncoded());
assertArrayEquals(adOrig.getVerifier(), adReload.getVerifier());
fsReload.close();
}
}

View File

@ -119,6 +119,9 @@ java.lang.Object#notifyAll()
@defaultMessage Don't interrupt threads use FutureUtils#cancel(Future<T>) instead
java.util.concurrent.Future#cancel(boolean)
@defaultMessage Don't use ...InputStream.available() as it gives wrong result for certain streams - use IOUtils.toByteArray to read the stream fully and then count the available bytes
java.io.InputStream#available()
@defaultMessage Unnecessary, inefficient, and confusing conversion of String.toString
java.lang.String#toString()

View File

@ -171,7 +171,7 @@ public final class TextSpecInfoAtom extends RecordAtom {
public TextSpecInfoRun[] getTextSpecInfoRuns(){
LittleEndianByteArrayInputStream bis = new LittleEndianByteArrayInputStream(_data); // NOSONAR
List<TextSpecInfoRun> lst = new ArrayList<>();
while (bis.available() > 0) {
while (bis.getReadIndex() < _data.length) {
lst.add(new TextSpecInfoRun(bis));
}
return lst.toArray(new TextSpecInfoRun[lst.size()]);

View File

@ -50,16 +50,16 @@ public class HwmfPicture {
final HwmfHeader header;
public HwmfPicture(InputStream inputStream) throws IOException {
BufferedInputStream bis = new BufferedInputStream(inputStream, 10000);
LittleEndianInputStream leis = new LittleEndianInputStream(bis);
try (BufferedInputStream bis = new BufferedInputStream(inputStream, 10000);
LittleEndianInputStream leis = new LittleEndianInputStream(bis)) {
placeableHeader = HwmfPlaceableHeader.readHeader(leis);
header = new HwmfHeader(leis);
for (;;) {
if (leis.available() < 6) {
logger.log(POILogger.ERROR, "unexpected eof - wmf file was truncated");
break;
}
long recordSize;
int recordFunction;
try {
// recordSize in DWORDs
long recordSizeLong = leis.readUInt()*2;
if (recordSizeLong > Integer.MAX_VALUE) {
@ -67,15 +67,21 @@ public class HwmfPicture {
} else if (recordSizeLong < 0L) {
throw new RecordFormatException("record size can't be < 0");
}
int recordSize = (int)recordSizeLong;
int recordFunction = leis.readShort();
recordSize = (int)recordSizeLong;
recordFunction = leis.readShort();
} catch (Exception e) {
logger.log(POILogger.ERROR, "unexpected eof - wmf file was truncated");
break;
}
// 4 bytes (recordSize) + 2 bytes (recordFunction)
int consumedSize = 6;
HwmfRecordType wrt = HwmfRecordType.getById(recordFunction);
if (wrt == null) {
throw new IOException("unexpected record type: "+recordFunction);
}
if (wrt == HwmfRecordType.eof) break;
if (wrt == HwmfRecordType.eof) {
break;
}
if (wrt.clazz == null) {
throw new IOException("unsupported record type: "+recordFunction);
}
@ -89,7 +95,7 @@ public class HwmfPicture {
}
consumedSize += wr.init(leis, recordSize, recordFunction);
int remainingSize = recordSize - consumedSize;
int remainingSize = (int)(recordSize - consumedSize);
if (remainingSize < 0) {
throw new RecordFormatException("read too many bytes. record size: "+recordSize + "; comsumed size: "+consumedSize);
} else if(remainingSize > 0) {
@ -100,6 +106,7 @@ public class HwmfPicture {
}
}
}
}
public List<HwmfRecord> getRecords() {
return Collections.unmodifiableList(records);

View File

@ -32,6 +32,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
@ -57,32 +58,30 @@ import org.junit.Ignore;
import org.junit.Test;
public class TestHwmfParsing {
private static final POIDataSamples samples = POIDataSamples.getSlideShowInstance();
@Test
public void parse() throws IOException {
File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf");
FileInputStream fis = new FileInputStream(f);
try (InputStream fis = samples.openResourceAsStream("santa.wmf")) {
HwmfPicture wmf = new HwmfPicture(fis);
fis.close();
List<HwmfRecord> records = wmf.getRecords();
assertEquals(581, records.size());
}
}
@Test(expected = RecordFormatException.class)
public void testInfiniteLoop() throws Exception {
File f = POIDataSamples.getSlideShowInstance().getFile("61338.wmf");
FileInputStream fis = null;
try {
fis = new FileInputStream(f);
HwmfPicture wmf = new HwmfPicture(fis);
} finally {
fis.close();
try (InputStream is = samples.openResourceAsStream("61338.wmf")) {
new HwmfPicture(is);
}
}
@Test
@Ignore("This is work-in-progress and not a real unit test ...")
public void paint() throws IOException {
File f = POIDataSamples.getSlideShowInstance().getFile("santa.wmf");
File f = samples.getFile("santa.wmf");
// File f = new File("bla.wmf");
FileInputStream fis = new FileInputStream(f);
HwmfPicture wmf = new HwmfPicture(fis);

View File

@ -29,6 +29,7 @@ import java.util.Map;
import org.apache.poi.poifs.eventfilesystem.POIFSReader;
import org.apache.poi.poifs.eventfilesystem.POIFSReaderEvent;
import org.apache.poi.poifs.eventfilesystem.POIFSReaderListener;
import org.apache.poi.util.IOUtils;
/**
* Test (Proof of concept) program that employs the
@ -110,16 +111,15 @@ public class ReaderWriter
@Override
public void processPOIFSReaderEvent(final POIFSReaderEvent event)
{
@SuppressWarnings("resource")
DocumentInputStream istream = event.getStream();
POIFSDocumentPath path = event.getPath();
String name = event.getName();
try
{
int size = istream.available();
byte[] data = new byte[ istream.available() ];
try {
byte[] data = IOUtils.toByteArray(istream);
int size = data.length;
istream.read(data);
DocumentDescriptor descriptor = new DocumentDescriptor(path,
name);

View File

@ -17,22 +17,28 @@
package org.apache.poi.poifs.filesystem;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import junit.framework.TestCase;
import org.apache.poi.POIDataSamples;
import org.apache.poi.poifs.property.DirectoryProperty;
import org.apache.poi.poifs.storage.RawDataBlock;
import org.apache.poi.util.SuppressForbidden;
import org.junit.Before;
import org.junit.Test;
/**
* Class to test DocumentInputStream functionality
*/
public final class TestDocumentInputStream extends TestCase {
public final class TestDocumentInputStream {
private DocumentNode _workbook_n;
private DocumentNode _workbook_o;
private byte[] _workbook_data;
@ -42,8 +48,8 @@ public final class TestDocumentInputStream extends TestCase {
// any block size
private static final int _buffer_size = 6;
@Override
protected void setUp() throws Exception {
@Before
public void setUp() throws Exception {
int blocks = (_workbook_size + 511) / 512;
_workbook_data = new byte[ 512 * blocks ];
@ -92,6 +98,7 @@ public final class TestDocumentInputStream extends TestCase {
/**
* test constructor
*/
@Test
public void testConstructor() throws IOException {
DocumentInputStream ostream = new ODocumentInputStream(_workbook_o);
DocumentInputStream nstream = new NDocumentInputStream(_workbook_n);
@ -99,8 +106,8 @@ public final class TestDocumentInputStream extends TestCase {
assertEquals(_workbook_size, _workbook_o.getSize());
assertEquals(_workbook_size, _workbook_n.getSize());
assertEquals(_workbook_size, ostream.available());
assertEquals(_workbook_size, nstream.available());
assertEquals(_workbook_size, available(ostream));
assertEquals(_workbook_size, available(nstream));
ostream.close();
nstream.close();
@ -109,23 +116,24 @@ public final class TestDocumentInputStream extends TestCase {
/**
* test available() behavior
*/
@Test
public void testAvailable() throws IOException {
DocumentInputStream ostream = new DocumentInputStream(_workbook_o);
DocumentInputStream nstream = new NDocumentInputStream(_workbook_n);
assertEquals(_workbook_size, ostream.available());
assertEquals(_workbook_size, nstream.available());
assertEquals(_workbook_size, available(ostream));
assertEquals(_workbook_size, available(nstream));
ostream.close();
nstream.close();
try {
ostream.available();
available(ostream);
fail("Should have caught IOException");
} catch (IllegalStateException ignored) {
// as expected
}
try {
nstream.available();
available(nstream);
fail("Should have caught IOException");
} catch (IllegalStateException ignored) {
// as expected
@ -135,6 +143,7 @@ public final class TestDocumentInputStream extends TestCase {
/**
* test mark/reset/markSupported.
*/
@Test
public void testMarkFunctions() throws IOException {
byte[] buffer = new byte[ _workbook_size / 5 ];
byte[] small_buffer = new byte[212];
@ -152,12 +161,12 @@ public final class TestDocumentInputStream extends TestCase {
_workbook_data[ j ], buffer[ j ]
);
}
assertEquals(_workbook_size - buffer.length, stream.available());
assertEquals(_workbook_size - buffer.length, available(stream));
// Reset, and check the available goes back to being the
// whole of the stream
stream.reset();
assertEquals(_workbook_size, stream.available());
assertEquals(_workbook_size, available(stream));
// Read part of a block
@ -168,7 +177,7 @@ public final class TestDocumentInputStream extends TestCase {
_workbook_data[ j ], small_buffer[ j ]
);
}
assertEquals(_workbook_size - small_buffer.length, stream.available());
assertEquals(_workbook_size - small_buffer.length, available(stream));
stream.mark(0);
// Read the next part
@ -179,11 +188,11 @@ public final class TestDocumentInputStream extends TestCase {
_workbook_data[ j+small_buffer.length ], small_buffer[ j ]
);
}
assertEquals(_workbook_size - 2*small_buffer.length, stream.available());
assertEquals(_workbook_size - 2*small_buffer.length, available(stream));
// Reset, check it goes back to where it was
stream.reset();
assertEquals(_workbook_size - small_buffer.length, stream.available());
assertEquals(_workbook_size - small_buffer.length, available(stream));
// Read
stream.read(small_buffer);
@ -193,7 +202,7 @@ public final class TestDocumentInputStream extends TestCase {
_workbook_data[ j+small_buffer.length ], small_buffer[ j ]
);
}
assertEquals(_workbook_size - 2*small_buffer.length, stream.available());
assertEquals(_workbook_size - 2*small_buffer.length, available(stream));
// Now read at various points
@ -236,11 +245,11 @@ public final class TestDocumentInputStream extends TestCase {
_workbook_data[ j ], buffer[ j ]
);
}
assertEquals(_workbook_size - buffer.length, stream.available());
assertEquals(_workbook_size - buffer.length, available(stream));
// Read all of it again, check it began at the start again
stream.reset();
assertEquals(_workbook_size, stream.available());
assertEquals(_workbook_size, available(stream));
stream.read(buffer);
for (int j = 0; j < buffer.length; j++) {
@ -254,7 +263,7 @@ public final class TestDocumentInputStream extends TestCase {
stream.mark(12);
stream.read(buffer);
assertEquals(_workbook_size - (2 * buffer.length),
stream.available());
available(stream));
for (int j = buffer.length; j < (2 * buffer.length); j++)
{
assertEquals("checking byte " + j, _workbook_data[ j ],
@ -263,12 +272,12 @@ public final class TestDocumentInputStream extends TestCase {
// Reset, should go back to only one buffer full read
stream.reset();
assertEquals(_workbook_size - buffer.length, stream.available());
assertEquals(_workbook_size - buffer.length, available(stream));
// Read the buffer again
stream.read(buffer);
assertEquals(_workbook_size - (2 * buffer.length),
stream.available());
available(stream));
for (int j = buffer.length; j < (2 * buffer.length); j++)
{
assertEquals("checking byte " + j, _workbook_data[ j ],
@ -281,6 +290,7 @@ public final class TestDocumentInputStream extends TestCase {
/**
* test simple read method
*/
@Test
public void testReadSingleByte() throws IOException {
DocumentInputStream[] streams = new DocumentInputStream[] {
new DocumentInputStream(_workbook_o),
@ -297,7 +307,7 @@ public final class TestDocumentInputStream extends TestCase {
( byte ) b);
remaining--;
assertEquals("checking remaining after reading byte " + j,
remaining, stream.available());
remaining, available(stream));
}
// Ensure we fell off the end
@ -317,6 +327,7 @@ public final class TestDocumentInputStream extends TestCase {
/**
* Test buffered read
*/
@Test
public void testBufferRead() throws IOException {
DocumentInputStream[] streams = new DocumentInputStream[] {
new DocumentInputStream(_workbook_o),
@ -333,22 +344,22 @@ public final class TestDocumentInputStream extends TestCase {
// test reading zero length buffer
assertEquals(0, stream.read(new byte[ 0 ]));
assertEquals(_workbook_size, stream.available());
assertEquals(_workbook_size, available(stream));
byte[] buffer = new byte[ _buffer_size ];
int offset = 0;
while (stream.available() >= buffer.length)
while (available(stream) >= buffer.length)
{
assertEquals(_buffer_size, stream.read(buffer));
for (byte data : buffer) {
for (byte element : buffer) {
assertEquals("in main loop, byte " + offset,
_workbook_data[ offset ], data);
_workbook_data[ offset ], element);
offset++;
}
assertEquals("offset " + offset, _workbook_size - offset,
stream.available());
available(stream));
}
assertEquals(_workbook_size % _buffer_size, stream.available());
assertEquals(_workbook_size % _buffer_size, available(stream));
Arrays.fill(buffer, ( byte ) 0);
int count = stream.read(buffer);
@ -378,6 +389,7 @@ public final class TestDocumentInputStream extends TestCase {
/**
* Test complex buffered read
*/
@Test
public void testComplexBufferRead() throws IOException {
DocumentInputStream[] streams = new DocumentInputStream[] {
new DocumentInputStream(_workbook_o),
@ -413,11 +425,11 @@ public final class TestDocumentInputStream extends TestCase {
// test reading zero
assertEquals(0, stream.read(new byte[ 5 ], 0, 0));
assertEquals(_workbook_size, stream.available());
assertEquals(_workbook_size, available(stream));
byte[] buffer = new byte[ _workbook_size ];
int offset = 0;
while (stream.available() >= _buffer_size)
while (available(stream) >= _buffer_size)
{
Arrays.fill(buffer, ( byte ) 0);
assertEquals(_buffer_size,
@ -437,9 +449,9 @@ public final class TestDocumentInputStream extends TestCase {
}
offset += _buffer_size;
assertEquals("offset " + offset, _workbook_size - offset,
stream.available());
available(stream));
}
assertEquals(_workbook_size % _buffer_size, stream.available());
assertEquals(_workbook_size % _buffer_size, available(stream));
Arrays.fill(buffer, ( byte ) 0);
int count = stream.read(buffer, offset,
_workbook_size % _buffer_size);
@ -474,38 +486,40 @@ public final class TestDocumentInputStream extends TestCase {
/**
* Tests that we can skip within the stream
*/
@Test
public void testSkip() throws IOException {
DocumentInputStream[] streams = new DocumentInputStream[] {
new DocumentInputStream(_workbook_o),
new NDocumentInputStream(_workbook_n)
};
for(DocumentInputStream stream : streams) {
assertEquals(_workbook_size, stream.available());
int count = stream.available();
assertEquals(_workbook_size, available(stream));
int count = available(stream);
while (stream.available() >= _buffer_size) {
while (available(stream) >= _buffer_size) {
assertEquals(_buffer_size, stream.skip(_buffer_size));
count -= _buffer_size;
assertEquals(count, stream.available());
assertEquals(count, available(stream));
}
assertEquals(_workbook_size % _buffer_size,
stream.skip(_buffer_size));
assertEquals(0, stream.available());
assertEquals(0, available(stream));
stream.reset();
assertEquals(_workbook_size, stream.available());
assertEquals(_workbook_size, available(stream));
assertEquals(_workbook_size, stream.skip(_workbook_size * 2));
assertEquals(0, stream.available());
assertEquals(0, available(stream));
stream.reset();
assertEquals(_workbook_size, stream.available());
assertEquals(_workbook_size, available(stream));
assertEquals(_workbook_size,
stream.skip(2 + ( long ) Integer.MAX_VALUE));
assertEquals(0, stream.available());
assertEquals(0, available(stream));
}
}
/**
* Test that we can read files at multiple levels down the tree
*/
@Test
public void testReadMultipleTreeLevels() throws Exception {
final POIDataSamples _samples = POIDataSamples.getPublisherInstance();
File sample = _samples.getFile("Sample.pub");
@ -551,4 +565,9 @@ public final class TestDocumentInputStream extends TestCase {
npoifs.close();
}
}
@SuppressForbidden("just for testing")
private static int available(InputStream is) throws IOException {
return is.available();
}
}

View File

@ -33,6 +33,7 @@ import org.apache.poi.poifs.storage.BATBlock;
import org.apache.poi.poifs.storage.BlockAllocationTableReader;
import org.apache.poi.poifs.storage.HeaderBlock;
import org.apache.poi.poifs.storage.RawDataBlockList;
import org.apache.poi.util.IOUtils;
/**
* Tests for the older OPOIFS-based POIFSFileSystem
@ -285,13 +286,8 @@ public final class TestPOIFSFileSystem extends TestCase {
checkAllDirectoryContents((DirectoryEntry)entry);
} else {
DocumentNode doc = (DocumentNode) entry;
DocumentInputStream dis = new DocumentInputStream(doc);
try {
int numBytes = dis.available();
byte[] data = new byte [numBytes];
dis.read(data);
} finally {
dis.close();
try (DocumentInputStream dis = new DocumentInputStream(doc)) {
IOUtils.toByteArray(dis);
}
}
}

View File

@ -172,6 +172,29 @@ public final class TestIOUtils {
}
}
@Test(expected = RecordFormatException.class)
public void testMaxLengthTooLong() throws IOException {
try (InputStream is = new FileInputStream(TMP)) {
IOUtils.toByteArray(is, Integer.MAX_VALUE, 100);
}
}
@Test
public void testMaxLengthIgnored() throws IOException {
try (InputStream is = new FileInputStream(TMP)) {
IOUtils.toByteArray(is, 90, Integer.MAX_VALUE);
IOUtils.toByteArray(is, 90, 100);
IOUtils.toByteArray(is, Integer.MAX_VALUE, Integer.MAX_VALUE);
}
}
@Test(expected = RecordFormatException.class)
public void testMaxLengthInvalid() throws IOException {
try (InputStream is = new FileInputStream(TMP)) {
IOUtils.toByteArray(is, 90, 80);
}
}
@Test
public void testWonkyInputStream() throws IOException {
long skipped = IOUtils.skipFully(new WonkyInputStream(), 10000);

View File

@ -258,10 +258,14 @@ public final class TestLittleEndian {
InputStream stream = new ByteArrayInputStream(_good_array);
int count = 0;
while (stream.available() > 0) {
while (true) {
try {
short value = LittleEndian.readShort(stream);
assertEquals(value, expected_value);
count++;
} catch (BufferUnderrunException e) {
break;
}
}
assertEquals(count,
_good_array.length / LittleEndianConsts.SHORT_SIZE);
@ -283,10 +287,14 @@ public final class TestLittleEndian {
InputStream stream = new ByteArrayInputStream(_good_array);
int count = 0;
while (stream.available() > 0) {
while (true) {
try {
int value = LittleEndian.readInt(stream);
assertEquals(value, expected_value);
count++;
} catch (BufferUnderrunException e) {
break;
}
}
assertEquals(count, _good_array.length / LittleEndianConsts.INT_SIZE);
stream = new ByteArrayInputStream(_bad_array);
@ -308,10 +316,14 @@ public final class TestLittleEndian {
InputStream stream = new ByteArrayInputStream(_good_array);
int count = 0;
while (stream.available() > 0) {
while (true) {
try {
long value = LittleEndian.readLong(stream);
assertEquals(value, expected_value);
count++;
} catch (BufferUnderrunException e) {
break;
}
}
assertEquals(count,
_good_array.length / LittleEndianConsts.LONG_SIZE);
@ -324,21 +336,17 @@ public final class TestLittleEndian {
}
}
// public void testReadFromStream() throws IOException {
// int actual;
// actual = LittleEndian.readUShort(new ByteArrayInputStream(new byte[] { 5, -128, }));
// assertEquals(32773, actual);
//
// actual = LittleEndian.readUShort(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4, }));
// assertEquals(513, actual);
//
// try {
// LittleEndian.readInt(new ByteArrayInputStream(new byte[] { 1, 2, 3, }));
// fail("Should have caught BufferUnderrunException");
// } catch (BufferUnderrunException ignored) {
// // as expected
// }
// }
@Test(expected = BufferUnderrunException.class)
public void testReadFromStream() throws IOException {
int actual;
actual = LittleEndian.readUShort(new ByteArrayInputStream(new byte[] { 5, -128, }));
assertEquals(32773, actual);
actual = LittleEndian.readUShort(new ByteArrayInputStream(new byte[] { 1, 2, 3, 4, }));
assertEquals(513, actual);
LittleEndian.readInt(new ByteArrayInputStream(new byte[] { 1, 2, 3, }));
}
@Test
public void testUnsignedByteToInt() {