TestPOIFSStream.java
/* ====================================================================
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.filesystem;
import static org.apache.poi.POIDataSamples.writeOutAndReadBack;
import static org.hamcrest.CoreMatchers.hasItem;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.function.Function;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.poi.POIDataSamples;
import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.NoPropertySetStreamException;
import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.poifs.common.POIFSConstants;
import org.apache.poi.poifs.property.DirectoryProperty;
import org.apache.poi.poifs.property.Property;
import org.apache.poi.poifs.property.PropertyTable;
import org.apache.poi.poifs.property.RootProperty;
import org.apache.poi.poifs.storage.BATBlock;
import org.apache.poi.poifs.storage.HeaderBlock;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.TempFile;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
/**
* Tests {@link POIFSStream}
*/
final class TestPOIFSStream {
private static final POIDataSamples _inst = POIDataSamples.getPOIFSInstance();
/**
* Read a single block stream
*/
@Test
void testReadTinyStream() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.getFile("BlockSize512.zvi"))) {
// 98 is actually the last block in a two block stream...
POIFSStream stream = new POIFSStream(fs, 98);
Iterator<ByteBuffer> i = stream.getBlockIterator();
assertTrue(i.hasNext());
ByteBuffer b = i.next();
assertFalse(i.hasNext());
// Check the contents
assertEquals((byte) 0x81, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x82, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
}
}
/**
* Read a stream with only two blocks in it
*/
@Test
void testReadShortStream() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.getFile("BlockSize512.zvi"))) {
// 97 -> 98 -> end
POIFSStream stream = new POIFSStream(fs, 97);
Iterator<ByteBuffer> i = stream.getBlockIterator();
assertTrue(i.hasNext());
ByteBuffer b97 = i.next();
assertTrue(i.hasNext());
ByteBuffer b98 = i.next();
assertFalse(i.hasNext());
// Check the contents of the 1st block
assertEquals((byte) 0x01, b97.get());
assertEquals((byte) 0x00, b97.get());
assertEquals((byte) 0x00, b97.get());
assertEquals((byte) 0x00, b97.get());
assertEquals((byte) 0x02, b97.get());
assertEquals((byte) 0x00, b97.get());
assertEquals((byte) 0x00, b97.get());
assertEquals((byte) 0x00, b97.get());
// Check the contents of the 2nd block
assertEquals((byte) 0x81, b98.get());
assertEquals((byte) 0x00, b98.get());
assertEquals((byte) 0x00, b98.get());
assertEquals((byte) 0x00, b98.get());
assertEquals((byte) 0x82, b98.get());
assertEquals((byte) 0x00, b98.get());
assertEquals((byte) 0x00, b98.get());
assertEquals((byte) 0x00, b98.get());
}
}
/**
* Read a stream with many blocks
*/
@Test
void testReadLongerStream() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.getFile("BlockSize512.zvi"))) {
ByteBuffer b0 = null;
ByteBuffer b1 = null;
ByteBuffer b22 = null;
// The stream at 0 has 23 blocks in it
POIFSStream stream = new POIFSStream(fs, 0);
Iterator<ByteBuffer> i = stream.getBlockIterator();
int count = 0;
while (i.hasNext()) {
ByteBuffer b = i.next();
if (count == 0) {
b0 = b;
}
if (count == 1) {
b1 = b;
}
if (count == 22) {
b22 = b;
}
count++;
}
assertEquals(23, count);
// Check the contents
// 1st block is at 0
assertNotNull(b0);
assertEquals((byte) 0x9e, b0.get());
assertEquals((byte) 0x75, b0.get());
assertEquals((byte) 0x97, b0.get());
assertEquals((byte) 0xf6, b0.get());
// 2nd block is at 1
assertNotNull(b1);
assertEquals((byte) 0x86, b1.get());
assertEquals((byte) 0x09, b1.get());
assertEquals((byte) 0x22, b1.get());
assertEquals((byte) 0xfb, b1.get());
// last block is at 89
assertNotNull(b22);
assertEquals((byte) 0xfe, b22.get());
assertEquals((byte) 0xff, b22.get());
assertEquals((byte) 0x00, b22.get());
assertEquals((byte) 0x00, b22.get());
assertEquals((byte) 0x05, b22.get());
assertEquals((byte) 0x01, b22.get());
assertEquals((byte) 0x02, b22.get());
assertEquals((byte) 0x00, b22.get());
}
}
/**
* Read a stream with several blocks in a 4096 byte block file
*/
@Test
void testReadStream4096() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.getFile("BlockSize4096.zvi"))) {
// 0 -> 1 -> 2 -> end
POIFSStream stream = new POIFSStream(fs, 0);
Iterator<ByteBuffer> i = stream.getBlockIterator();
assertTrue(i.hasNext());
ByteBuffer b0 = i.next();
assertTrue(i.hasNext());
ByteBuffer b1 = i.next();
assertTrue(i.hasNext());
ByteBuffer b2 = i.next();
assertFalse(i.hasNext());
// Check the contents of the 1st block
assertEquals((byte) 0x9E, b0.get());
assertEquals((byte) 0x75, b0.get());
assertEquals((byte) 0x97, b0.get());
assertEquals((byte) 0xF6, b0.get());
assertEquals((byte) 0xFF, b0.get());
assertEquals((byte) 0x21, b0.get());
assertEquals((byte) 0xD2, b0.get());
assertEquals((byte) 0x11, b0.get());
// Check the contents of the 2nd block
assertEquals((byte) 0x00, b1.get());
assertEquals((byte) 0x00, b1.get());
assertEquals((byte) 0x03, b1.get());
assertEquals((byte) 0x00, b1.get());
assertEquals((byte) 0x00, b1.get());
assertEquals((byte) 0x00, b1.get());
assertEquals((byte) 0x00, b1.get());
assertEquals((byte) 0x00, b1.get());
// Check the contents of the 3rd block
assertEquals((byte) 0x6D, b2.get());
assertEquals((byte) 0x00, b2.get());
assertEquals((byte) 0x00, b2.get());
assertEquals((byte) 0x00, b2.get());
assertEquals((byte) 0x03, b2.get());
assertEquals((byte) 0x00, b2.get());
assertEquals((byte) 0x46, b2.get());
assertEquals((byte) 0x00, b2.get());
}
}
/**
* Craft a nasty file with a loop, and ensure we don't get stuck
*/
@Test
void testReadFailsOnLoop() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.getFile("BlockSize512.zvi"))) {
// Hack the FAT so that it goes 0->1->2->0
fs.setNextBlock(0, 1);
fs.setNextBlock(1, 2);
fs.setNextBlock(2, 0);
// Now try to read
POIFSStream stream = new POIFSStream(fs, 0);
Iterator<ByteBuffer> i = stream.getBlockIterator();
assertTrue(i.hasNext());
// 1st read works
i.next();
assertTrue(i.hasNext());
// 2nd read works
i.next();
assertTrue(i.hasNext());
// 3rd read works
i.next();
assertTrue(i.hasNext());
// 4th read blows up as it loops back to 0
assertThrows(RuntimeException.class, i::next, "Loop should have been detected but wasn't!");
assertTrue(i.hasNext());
}
}
/**
* Tests that we can load some streams that are
* stored in the mini stream.
*/
@Test
void testReadMiniStreams() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.openResourceAsStream("BlockSize512.zvi"))) {
POIFSMiniStore ministore = fs.getMiniStore();
// 178 -> 179 -> 180 -> end
POIFSStream stream = new POIFSStream(ministore, 178);
Iterator<ByteBuffer> i = stream.getBlockIterator();
assertTrue(i.hasNext());
ByteBuffer b178 = i.next();
assertTrue(i.hasNext());
ByteBuffer b179 = i.next();
assertTrue(i.hasNext());
ByteBuffer b180 = i.next();
assertFalse(i.hasNext());
// Check the contents of the 1st block
assertEquals((byte) 0xfe, b178.get());
assertEquals((byte) 0xff, b178.get());
assertEquals((byte) 0x00, b178.get());
assertEquals((byte) 0x00, b178.get());
assertEquals((byte) 0x05, b178.get());
assertEquals((byte) 0x01, b178.get());
assertEquals((byte) 0x02, b178.get());
assertEquals((byte) 0x00, b178.get());
// And the 2nd
assertEquals((byte) 0x6c, b179.get());
assertEquals((byte) 0x00, b179.get());
assertEquals((byte) 0x00, b179.get());
assertEquals((byte) 0x00, b179.get());
assertEquals((byte) 0x28, b179.get());
assertEquals((byte) 0x00, b179.get());
assertEquals((byte) 0x00, b179.get());
assertEquals((byte) 0x00, b179.get());
// And the 3rd
assertEquals((byte) 0x30, b180.get());
assertEquals((byte) 0x00, b180.get());
assertEquals((byte) 0x00, b180.get());
assertEquals((byte) 0x00, b180.get());
assertEquals((byte) 0x00, b180.get());
assertEquals((byte) 0x00, b180.get());
assertEquals((byte) 0x00, b180.get());
assertEquals((byte) 0x80, b180.get());
}
}
/**
* Writing the same amount of data as before
*/
@Test
void testReplaceStream() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.openResourceAsStream("BlockSize512.zvi"))) {
byte[] data = new byte[512];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 256);
}
// 98 is actually the last block in a two block stream...
POIFSStream stream = new POIFSStream(fs, 98);
stream.updateContents(data);
// Check the reading of blocks
Iterator<ByteBuffer> it = stream.getBlockIterator();
assertTrue(it.hasNext());
ByteBuffer b = it.next();
assertFalse(it.hasNext());
// Now check the contents
data = new byte[512];
b.get(data);
for (int i = 0; i < data.length; i++) {
byte exp = (byte) (i % 256);
assertEquals(exp, data[i]);
}
}
}
/**
* Writes less data than before, some blocks will need
* to be freed
*/
@Test
void testReplaceStreamWithLess() throws Exception {
try (InputStream is = _inst.openResourceAsStream("BlockSize512.zvi");
POIFSFileSystem fs = new POIFSFileSystem(is)) {
byte[] data = new byte[512];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 256);
}
// 97 -> 98 -> end
assertEquals(98, fs.getNextBlock(97));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(98));
// Create a 2 block stream, will become a 1 block one
POIFSStream stream = new POIFSStream(fs, 97);
stream.updateContents(data);
// 97 should now be the end, and 98 free
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(97));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(98));
// Check the reading of blocks
Iterator<ByteBuffer> it = stream.getBlockIterator();
assertTrue(it.hasNext());
ByteBuffer b = it.next();
assertFalse(it.hasNext());
// Now check the contents
data = new byte[512];
b.get(data);
for (int i = 0; i < data.length; i++) {
byte exp = (byte) (i % 256);
assertEquals(exp, data[i]);
}
}
}
/**
* Writes more data than before, new blocks will be needed
*/
@Test
void testReplaceStreamWithMore() throws Exception {
try (InputStream is = _inst.openResourceAsStream("BlockSize512.zvi");
POIFSFileSystem fs = new POIFSFileSystem(is)) {
byte[] data = new byte[512 * 3];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 256);
}
// 97 -> 98 -> end
assertEquals(98, fs.getNextBlock(97));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(98));
// 100 is our first free one
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(99));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(100));
// Create a 2 block stream, will become a 3 block one
POIFSStream stream = new POIFSStream(fs, 97);
stream.updateContents(data);
// 97 -> 98 -> 100 -> end
assertEquals(98, fs.getNextBlock(97));
assertEquals(100, fs.getNextBlock(98));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(100));
// Check the reading of blocks
Iterator<ByteBuffer> it = stream.getBlockIterator();
int count = 0;
while (it.hasNext()) {
ByteBuffer b = it.next();
data = new byte[512];
b.get(data);
for (int i = 0; i < data.length; i++) {
byte exp = (byte) (i % 256);
assertEquals(exp, data[i]);
}
count++;
}
assertEquals(3, count);
}
}
/**
* Writes to a new stream in the file
*/
@Test
void testWriteNewStream() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.openResourceAsStream("BlockSize512.zvi"))) {
// 100 is our first free one
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(99));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(100));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(101));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(102));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(103));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(104));
// Add a single block one
byte[] data = new byte[512];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 256);
}
POIFSStream stream = new POIFSStream(fs);
stream.updateContents(data);
// Check it was allocated properly
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(99));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(100));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(101));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(102));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(103));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(104));
// And check the contents
Iterator<ByteBuffer> it = stream.getBlockIterator();
int count = 0;
while (it.hasNext()) {
ByteBuffer b = it.next();
data = new byte[512];
b.get(data);
for (int i = 0; i < data.length; i++) {
byte exp = (byte) (i % 256);
assertEquals(exp, data[i]);
}
count++;
}
assertEquals(1, count);
// And a multi block one
data = new byte[512 * 3];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 256);
}
stream = new POIFSStream(fs);
stream.updateContents(data);
// Check it was allocated properly
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(99));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(100));
assertEquals(102, fs.getNextBlock(101));
assertEquals(103, fs.getNextBlock(102));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(103));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(104));
// And check the contents
it = stream.getBlockIterator();
count = 0;
while (it.hasNext()) {
ByteBuffer b = it.next();
data = new byte[512];
b.get(data);
for (int i = 0; i < data.length; i++) {
byte exp = (byte) (i % 256);
assertEquals(exp, data[i]);
}
count++;
}
assertEquals(3, count);
// Free it
stream.free();
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(99));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(100));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(101));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(102));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(103));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(104));
}
}
/**
* Writes to a new stream in the file, where we've not enough
* free blocks so new FAT segments will need to be allocated
* to support this
*/
@Test
void testWriteNewStreamExtraFATs() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.openResourceAsStream("BlockSize512.zvi"))) {
// Allocate almost all the blocks
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(99));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(100));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(127));
for (int i = 100; i < 127; i++) {
fs.setNextBlock(i, POIFSConstants.END_OF_CHAIN);
}
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(127));
assertTrue(fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
// Write a 3 block stream
byte[] data = new byte[512 * 3];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 256);
}
POIFSStream stream = new POIFSStream(fs);
stream.updateContents(data);
// Check we got another BAT
assertFalse(fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
assertTrue(fs.getBATBlockAndIndex(128).getBlock().hasFreeSectors());
// the BAT will be in the first spot of the new block
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(126));
assertEquals(129, fs.getNextBlock(127));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(128));
assertEquals(130, fs.getNextBlock(129));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(130));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(131));
}
}
/**
* Replaces data in an existing stream, with a bit
* more data than before, in a 4096 byte block file
*/
@Test
void testWriteStream4096() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.openResourceAsStream("BlockSize4096.zvi"))) {
// 0 -> 1 -> 2 -> end
assertEquals(1, fs.getNextBlock(0));
assertEquals(2, fs.getNextBlock(1));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(2));
assertEquals(4, fs.getNextBlock(3));
// First free one is at 15
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(14));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(15));
// Write a 5 block file
byte[] data = new byte[4096 * 5];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i % 256);
}
POIFSStream stream = new POIFSStream(fs, 0);
stream.updateContents(data);
// Check it
assertEquals(1, fs.getNextBlock(0));
assertEquals(2, fs.getNextBlock(1));
assertEquals(15, fs.getNextBlock(2)); // Jumps
assertEquals(4, fs.getNextBlock(3)); // Next stream
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(14));
assertEquals(16, fs.getNextBlock(15)); // Continues
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(16)); // Ends
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(17)); // Free
// Check the contents too
Iterator<ByteBuffer> it = stream.getBlockIterator();
int count = 0;
while (it.hasNext()) {
ByteBuffer b = it.next();
data = new byte[512];
b.get(data);
for (int i = 0; i < data.length; i++) {
byte exp = (byte) (i % 256);
assertEquals(exp, data[i]);
}
count++;
}
assertEquals(5, count);
}
}
/**
* Tests that we can write into the mini stream
*/
@Test
void testWriteMiniStreams() throws Exception {
try (InputStream is = _inst.openResourceAsStream("BlockSize512.zvi");
POIFSFileSystem fs = new POIFSFileSystem(is)) {
POIFSMiniStore ministore = fs.getMiniStore();
// 178 -> 179 -> 180 -> end
assertEquals(179, ministore.getNextBlock(178));
assertEquals(180, ministore.getNextBlock(179));
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(180));
// Try writing 3 full blocks worth
byte[] data = new byte[64 * 3];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) i;
}
POIFSStream stream = new POIFSStream(ministore, 178);
stream.updateContents(data);
// Check
assertEquals(179, ministore.getNextBlock(178));
assertEquals(180, ministore.getNextBlock(179));
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(180));
stream = new POIFSStream(ministore, 178);
Iterator<ByteBuffer> it = stream.getBlockIterator();
ByteBuffer b178 = it.next();
ByteBuffer b179 = it.next();
ByteBuffer b180 = it.next();
assertFalse(it.hasNext());
assertEquals((byte) 0x00, b178.get());
assertEquals((byte) 0x01, b178.get());
assertEquals((byte) 0x40, b179.get());
assertEquals((byte) 0x41, b179.get());
assertEquals((byte) 0x80, b180.get());
assertEquals((byte) 0x81, b180.get());
// Try writing just into 3 blocks worth
data = new byte[64 * 2 + 12];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i + 4);
}
stream = new POIFSStream(ministore, 178);
stream.updateContents(data);
// Check
assertEquals(179, ministore.getNextBlock(178));
assertEquals(180, ministore.getNextBlock(179));
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(180));
stream = new POIFSStream(ministore, 178);
it = stream.getBlockIterator();
b178 = it.next();
b179 = it.next();
b180 = it.next();
assertFalse(it.hasNext());
assertEquals((byte) 0x04, b178.get(0));
assertEquals((byte) 0x05, b178.get(1));
assertEquals((byte) 0x44, b179.get(0));
assertEquals((byte) 0x45, b179.get(1));
assertEquals((byte) 0x84, b180.get(0));
assertEquals((byte) 0x85, b180.get(1));
// Try writing 1, should truncate
data = new byte[12];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i + 9);
}
stream = new POIFSStream(ministore, 178);
stream.updateContents(data);
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(178));
assertEquals(POIFSConstants.UNUSED_BLOCK, ministore.getNextBlock(179));
assertEquals(POIFSConstants.UNUSED_BLOCK, ministore.getNextBlock(180));
stream = new POIFSStream(ministore, 178);
it = stream.getBlockIterator();
b178 = it.next();
assertFalse(it.hasNext());
assertEquals((byte) 0x09, b178.get(0));
assertEquals((byte) 0x0a, b178.get(1));
// Try writing 5, should extend
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(178));
assertEquals(POIFSConstants.UNUSED_BLOCK, ministore.getNextBlock(179));
assertEquals(POIFSConstants.UNUSED_BLOCK, ministore.getNextBlock(180));
assertEquals(POIFSConstants.UNUSED_BLOCK, ministore.getNextBlock(181));
assertEquals(POIFSConstants.UNUSED_BLOCK, ministore.getNextBlock(182));
assertEquals(POIFSConstants.UNUSED_BLOCK, ministore.getNextBlock(183));
data = new byte[64 * 4 + 12];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i + 3);
}
stream = new POIFSStream(ministore, 178);
stream.updateContents(data);
assertEquals(179, ministore.getNextBlock(178));
assertEquals(180, ministore.getNextBlock(179));
assertEquals(181, ministore.getNextBlock(180));
assertEquals(182, ministore.getNextBlock(181));
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(182));
stream = new POIFSStream(ministore, 178);
it = stream.getBlockIterator();
b178 = it.next();
b179 = it.next();
b180 = it.next();
ByteBuffer b181 = it.next();
ByteBuffer b182 = it.next();
assertFalse(it.hasNext());
assertEquals((byte) 0x03, b178.get(0));
assertEquals((byte) 0x04, b178.get(1));
assertEquals((byte) 0x43, b179.get(0));
assertEquals((byte) 0x44, b179.get(1));
assertEquals((byte) 0x83, b180.get(0));
assertEquals((byte) 0x84, b180.get(1));
assertEquals((byte) 0xc3, b181.get(0));
assertEquals((byte) 0xc4, b181.get(1));
assertEquals((byte) 0x03, b182.get(0));
assertEquals((byte) 0x04, b182.get(1));
// Write lots, so it needs another big block
ministore.getBlockAt(183);
assertThrows(NoSuchElementException.class, () -> ministore.getBlockAt(184), "Block 184 should be off the end of the list");
data = new byte[64 * 6 + 12];
for (int i = 0; i < data.length; i++) {
data[i] = (byte) (i + 1);
}
stream = new POIFSStream(ministore, 178);
stream.updateContents(data);
// Should have added 2 more blocks to the chain
assertEquals(179, ministore.getNextBlock(178));
assertEquals(180, ministore.getNextBlock(179));
assertEquals(181, ministore.getNextBlock(180));
assertEquals(182, ministore.getNextBlock(181));
assertEquals(183, ministore.getNextBlock(182));
assertEquals(184, ministore.getNextBlock(183));
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(184));
assertEquals(POIFSConstants.UNUSED_BLOCK, ministore.getNextBlock(185));
// Block 184 should exist
ministore.getBlockAt(183);
ministore.getBlockAt(184);
ministore.getBlockAt(185);
// Check contents
stream = new POIFSStream(ministore, 178);
it = stream.getBlockIterator();
b178 = it.next();
b179 = it.next();
b180 = it.next();
b181 = it.next();
b182 = it.next();
ByteBuffer b183 = it.next();
ByteBuffer b184 = it.next();
assertFalse(it.hasNext());
assertEquals((byte) 0x01, b178.get(0));
assertEquals((byte) 0x02, b178.get(1));
assertEquals((byte) 0x41, b179.get(0));
assertEquals((byte) 0x42, b179.get(1));
assertEquals((byte) 0x81, b180.get(0));
assertEquals((byte) 0x82, b180.get(1));
assertEquals((byte) 0xc1, b181.get(0));
assertEquals((byte) 0xc2, b181.get(1));
assertEquals((byte) 0x01, b182.get(0));
assertEquals((byte) 0x02, b182.get(1));
assertEquals((byte) 0x41, b183.get(0));
assertEquals((byte) 0x42, b183.get(1));
assertEquals((byte) 0x81, b184.get(0));
assertEquals((byte) 0x82, b184.get(1));
}
}
/**
* Craft a nasty file with a loop, and ensure we don't get stuck
*/
@Test
void testWriteFailsOnLoop() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.getFile("BlockSize512.zvi"))) {
// Hack the FAT so that it goes 0->1->2->0
fs.setNextBlock(0, 1);
fs.setNextBlock(1, 2);
fs.setNextBlock(2, 0);
// Try to write a large amount, should fail on the write
POIFSStream stream1 = new POIFSStream(fs, 0);
assertThrows(IllegalStateException.class,
() -> stream1.updateContents(new byte[512 * 4]), "Loop should have been detected but wasn't!");
// Now reset, and try on a small bit
// Should fail during the freeing set
fs.setNextBlock(0, 1);
fs.setNextBlock(1, 2);
fs.setNextBlock(2, 0);
POIFSStream stream2 = new POIFSStream(fs, 0);
assertThrows(IllegalStateException.class,
() -> stream2.updateContents(new byte[512]), "Loop should have been detected but wasn't!");
}
}
/**
* Tests adding a new stream, writing and reading it.
*/
@Test
void testReadWriteNewStream() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem()) {
POIFSStream stream = new POIFSStream(fs);
// Check our filesystem has Properties then BAT
assertEquals(2, fs.getFreeBlock());
BATBlock bat = fs.getBATBlockAndIndex(0).getBlock();
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(2));
// Check the stream as-is
assertEquals(POIFSConstants.END_OF_CHAIN, stream.getStartBlock());
assertThrows(IllegalStateException.class, stream::getBlockIterator,
"Shouldn't be able to get an iterator before writing");
// Write in two blocks
byte[] data = new byte[512 + 20];
for (int i = 0; i < 512; i++) {
data[i] = (byte) (i % 256);
}
for (int i = 512; i < data.length; i++) {
data[i] = (byte) (i % 256 + 100);
}
stream.updateContents(data);
// Check now
assertEquals(4, fs.getFreeBlock());
bat = fs.getBATBlockAndIndex(0).getBlock();
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(3, bat.getValueAt(2));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(3));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(4));
Iterator<ByteBuffer> it = stream.getBlockIterator();
assertTrue(it.hasNext());
ByteBuffer b = it.next();
byte[] read = new byte[512];
b.get(read);
for (int i = 0; i < read.length; i++) {
assertEquals(data[i], read[i], "Wrong value at " + i);
}
assertTrue(it.hasNext());
b = it.next();
read = new byte[512];
b.get(read);
for (int i = 0; i < 20; i++) {
assertEquals(data[i + 512], read[i]);
}
for (int i = 20; i < read.length; i++) {
assertEquals(0, read[i]);
}
assertFalse(it.hasNext());
}
}
/**
* Writes a stream, then replaces it
*/
@Test
void testWriteThenReplace() throws Exception {
try (POIFSFileSystem fs1 = new POIFSFileSystem()) {
// Starts empty, other that Properties and BAT
BATBlock bat = fs1.getBATBlockAndIndex(0).getBlock();
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(2));
// Write something that uses a main stream
byte[] main4106 = new byte[4106];
main4106[0] = -10;
main4106[4105] = -11;
fs1.getRoot().createDocument("Normal", new ByteArrayInputStream(main4106));
// Should have used 9 blocks
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(3, bat.getValueAt(2));
assertEquals(4, bat.getValueAt(3));
assertEquals(5, bat.getValueAt(4));
assertEquals(6, bat.getValueAt(5));
assertEquals(7, bat.getValueAt(6));
assertEquals(8, bat.getValueAt(7));
assertEquals(9, bat.getValueAt(8));
assertEquals(10, bat.getValueAt(9));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(10));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(11));
DocumentEntry normal = (DocumentEntry) fs1.getRoot().getEntryCaseInsensitive("Normal");
assertEquals(4106, normal.getSize());
assertEquals(4106, ((DocumentNode) normal).getProperty().getSize());
// Replace with one still big enough for a main stream, but one block smaller
byte[] main4096 = new byte[4096];
main4096[0] = -10;
main4096[4095] = -11;
try (DocumentOutputStream nout = new DocumentOutputStream(normal)) {
nout.write(main4096);
}
// Will have dropped to 8
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(3, bat.getValueAt(2));
assertEquals(4, bat.getValueAt(3));
assertEquals(5, bat.getValueAt(4));
assertEquals(6, bat.getValueAt(5));
assertEquals(7, bat.getValueAt(6));
assertEquals(8, bat.getValueAt(7));
assertEquals(9, bat.getValueAt(8));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(9));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(10));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(11));
normal = (DocumentEntry) fs1.getRoot().getEntryCaseInsensitive("Normal");
assertEquals(4096, normal.getSize());
assertEquals(4096, ((DocumentNode) normal).getProperty().getSize());
// Write and check
try (POIFSFileSystem fs2 = writeOutAndReadBack(fs1)) {
bat = fs2.getBATBlockAndIndex(0).getBlock();
// No change after write
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0)); // Properties
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(3, bat.getValueAt(2));
assertEquals(4, bat.getValueAt(3));
assertEquals(5, bat.getValueAt(4));
assertEquals(6, bat.getValueAt(5));
assertEquals(7, bat.getValueAt(6));
assertEquals(8, bat.getValueAt(7));
assertEquals(9, bat.getValueAt(8));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(9)); // End of Normal
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(10));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(11));
normal = (DocumentEntry) fs2.getRoot().getEntryCaseInsensitive("Normal");
assertEquals(4096, normal.getSize());
assertEquals(4096, ((DocumentNode) normal).getProperty().getSize());
// Make longer, take 1 block at the end
normal = (DocumentEntry) fs2.getRoot().getEntryCaseInsensitive("Normal");
try (DocumentOutputStream nout = new DocumentOutputStream(normal)) {
nout.write(main4106);
}
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(3, bat.getValueAt(2));
assertEquals(4, bat.getValueAt(3));
assertEquals(5, bat.getValueAt(4));
assertEquals(6, bat.getValueAt(5));
assertEquals(7, bat.getValueAt(6));
assertEquals(8, bat.getValueAt(7));
assertEquals(9, bat.getValueAt(8));
assertEquals(10, bat.getValueAt(9));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(10)); // Normal
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(11));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(12));
normal = (DocumentEntry) fs2.getRoot().getEntryCaseInsensitive("Normal");
assertEquals(4106, normal.getSize());
assertEquals(4106, ((DocumentNode) normal).getProperty().getSize());
// Make it small, will trigger the SBAT stream and free lots up
byte[] mini = new byte[]{42, 0, 1, 2, 3, 4, 42};
normal = (DocumentEntry) fs2.getRoot().getEntryCaseInsensitive("Normal");
try (DocumentOutputStream nout = new DocumentOutputStream(normal)) {
nout.write(mini);
}
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(2)); // SBAT
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(3)); // Mini Stream
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(4));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(5));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(6));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(7));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(8));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(9));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(10));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(11));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(12));
normal = (DocumentEntry) fs2.getRoot().getEntryCaseInsensitive("Normal");
assertEquals(7, normal.getSize());
assertEquals(7, ((DocumentNode) normal).getProperty().getSize());
// Finally back to big again
try (DocumentOutputStream nout = new DocumentOutputStream(normal)) {
nout.write(main4096);
}
// Will keep the mini stream, now empty
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(2)); // SBAT
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(3)); // Mini Stream
assertEquals(5, bat.getValueAt(4));
assertEquals(6, bat.getValueAt(5));
assertEquals(7, bat.getValueAt(6));
assertEquals(8, bat.getValueAt(7));
assertEquals(9, bat.getValueAt(8));
assertEquals(10, bat.getValueAt(9));
assertEquals(11, bat.getValueAt(10));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(11));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(12));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(13));
normal = (DocumentEntry) fs2.getRoot().getEntryCaseInsensitive("Normal");
assertEquals(4096, normal.getSize());
assertEquals(4096, ((DocumentNode) normal).getProperty().getSize());
// Save, re-load, re-check
try (POIFSFileSystem fs3 = writeOutAndReadBack(fs2)) {
bat = fs3.getBATBlockAndIndex(0).getBlock();
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, bat.getValueAt(1));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(2)); // SBAT
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(3)); // Mini Stream
assertEquals(5, bat.getValueAt(4));
assertEquals(6, bat.getValueAt(5));
assertEquals(7, bat.getValueAt(6));
assertEquals(8, bat.getValueAt(7));
assertEquals(9, bat.getValueAt(8));
assertEquals(10, bat.getValueAt(9));
assertEquals(11, bat.getValueAt(10));
assertEquals(POIFSConstants.END_OF_CHAIN, bat.getValueAt(11));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(12));
assertEquals(POIFSConstants.UNUSED_BLOCK, bat.getValueAt(13));
normal = (DocumentEntry) fs3.getRoot().getEntryCaseInsensitive("Normal");
assertEquals(4096, normal.getSize());
assertEquals(4096, ((DocumentNode) normal).getProperty().getSize());
}
}
}
}
/**
* Returns test files with 512 byte and 4k block sizes, loaded
* both from InputStreams and Files
*/
public static Collection<Arguments> get512and4kFileAndInput() {
return CollectionUtils.union(get512FileAndInput(), get4kFileAndInput());
}
public static List<Arguments> get512FileAndInput() {
return Arrays.asList(
Arguments.of("BlockSize512.zvi", (Function<String,POIFSFileSystem>)TestPOIFSStream::openAsFile),
Arguments.of("BlockSize512.zvi", (Function<String,POIFSFileSystem>)TestPOIFSStream::openAsStream)
);
}
public static List<Arguments> get4kFileAndInput() {
return Arrays.asList(
Arguments.of("BlockSize4096.zvi", (Function<String,POIFSFileSystem>)TestPOIFSStream::openAsFile),
Arguments.of("BlockSize4096.zvi", (Function<String,POIFSFileSystem>)TestPOIFSStream::openAsStream)
);
}
private static POIFSFileSystem openAsFile(String fileName) {
try {
return new POIFSFileSystem(_inst.getFile(fileName));
} catch (IOException e) {
fail(e);
return null;
}
}
private static POIFSFileSystem openAsStream(String fileName) {
try {
return new POIFSFileSystem(_inst.openResourceAsStream(fileName));
} catch (IOException e) {
fail(e);
return null;
}
}
private static void assertBATCount(POIFSFileSystem fs, int expectedBAT, int expectedXBAT) throws IOException {
int foundBAT = 0;
int foundXBAT = 0;
int sz = (int) (fs.size() / fs.getBigBlockSize());
for (int i = 0; i < sz; i++) {
if (fs.getNextBlock(i) == POIFSConstants.FAT_SECTOR_BLOCK) {
foundBAT++;
}
if (fs.getNextBlock(i) == POIFSConstants.DIFAT_SECTOR_BLOCK) {
foundXBAT++;
}
}
assertEquals(expectedBAT, foundBAT, "Wrong number of BATs");
assertEquals(expectedXBAT, foundXBAT, "Wrong number of XBATs with " + expectedBAT + " BATs");
}
private void assertContentsMatches(byte[] expected, DocumentEntry doc) throws IOException {
DocumentInputStream inp = new DocumentInputStream(doc);
byte[] contents = new byte[doc.getSize()];
assertEquals(doc.getSize(), inp.read(contents));
inp.close();
if (expected != null) {
assertThat(expected, equalTo(contents));
}
}
private static HeaderBlock writeOutAndReadHeader(POIFSFileSystem fs) throws IOException {
UnsynchronizedByteArrayOutputStream baos = UnsynchronizedByteArrayOutputStream.builder().get();
fs.writeFilesystem(baos);
return new HeaderBlock(baos.toInputStream());
}
private static POIFSFileSystem writeOutFileAndReadBack(POIFSFileSystem original) throws IOException {
final File file = TempFile.createTempFile("TestPOIFS", ".ole2");
try (OutputStream fout = new FileOutputStream(file)) {
original.writeFilesystem(fout);
}
return new POIFSFileSystem(file, false);
}
@ParameterizedTest()
@MethodSource("get512FileAndInput")
void basicOpen512(String file, Function<String,POIFSFileSystem> opener) throws IOException {
// With a simple 512 block file
try (POIFSFileSystem fs = opener.apply(file)) {
assertEquals(512, fs.getBigBlockSize());
}
}
@ParameterizedTest()
@MethodSource("get4kFileAndInput")
void basicOpen4k(String file, Function<String,POIFSFileSystem> opener) throws IOException {
// Now with a simple 4096 block file
try (POIFSFileSystem fs = opener.apply(file)) {
assertEquals(4096, fs.getBigBlockSize());
}
}
@ParameterizedTest()
@MethodSource("get512FileAndInput")
void propertiesAndFatOnRead512(String file, Function<String,POIFSFileSystem> opener) throws IOException {
// With a simple 512 block file
try (POIFSFileSystem fs = opener.apply(file)) {
// Check the FAT was properly processed:
// Verify we only got one block
fs.getBATBlockAndIndex(0);
fs.getBATBlockAndIndex(1);
assertThrows(IndexOutOfBoundsException.class, () -> fs.getBATBlockAndIndex(140),
"Should only be one BAT, but a 2nd was found");
// Verify a few next offsets
// 97 -> 98 -> END
assertEquals(98, fs.getNextBlock(97));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(98));
// Check the properties
PropertyTable props = fs._get_property_table();
assertEquals(90, props.getStartBlock());
assertEquals(7, props.countBlocks());
// Root property tells us about the Mini Stream
RootProperty root = props.getRoot();
assertEquals("Root Entry", root.getName());
assertEquals(11564, root.getSize());
assertEquals(0, root.getStartBlock());
// Check its children too
Property prop;
Iterator<Property> pi = root.getChildren();
prop = pi.next();
assertEquals("Thumbnail", prop.getName());
prop = pi.next();
assertEquals("\u0005DocumentSummaryInformation", prop.getName());
prop = pi.next();
assertEquals("\u0005SummaryInformation", prop.getName());
prop = pi.next();
assertEquals("Image", prop.getName());
prop = pi.next();
assertEquals("Tags", prop.getName());
assertFalse(pi.hasNext());
// Check the SBAT (Small Blocks FAT) was properly processed
POIFSMiniStore ministore = fs.getMiniStore();
// Verify we only got two SBAT blocks
ministore.getBATBlockAndIndex(0);
ministore.getBATBlockAndIndex(128);
assertThrows(IndexOutOfBoundsException.class, () -> ministore.getBATBlockAndIndex(256),
"Should only be two SBATs, but a 3rd was found");
// Verify a few offsets: 0->50 is a stream
for (int i = 0; i < 50; i++) {
assertEquals(i + 1, ministore.getNextBlock(i));
}
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(50));
}
}
@ParameterizedTest()
@MethodSource("get4kFileAndInput")
void propertiesAndFatOnRead4k(String file, Function<String,POIFSFileSystem> opener) throws IOException {
// Now with a simple 4096 block file
try (POIFSFileSystem fs = opener.apply(file)) {
// Check the FAT was properly processed
// Verify we only got one block
fs.getBATBlockAndIndex(0);
fs.getBATBlockAndIndex(1);
assertThrows(IndexOutOfBoundsException.class, () -> fs.getBATBlockAndIndex(1040),
"Should only be one BAT, but a 2nd was found");
// Verify a few next offsets
// 0 -> 1 -> 2 -> END
assertEquals(1, fs.getNextBlock(0));
assertEquals(2, fs.getNextBlock(1));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(2));
// Check the properties
PropertyTable props = fs._get_property_table();
assertEquals(12, props.getStartBlock());
assertEquals(1, props.countBlocks());
// Root property tells us about the Mini Stream
RootProperty root = props.getRoot();
assertEquals("Root Entry", root.getName());
assertEquals(11564, root.getSize());
assertEquals(0, root.getStartBlock());
// Check its children too
Property prop;
Iterator<Property> pi = root.getChildren();
prop = pi.next();
assertEquals("Thumbnail", prop.getName());
prop = pi.next();
assertEquals("\u0005DocumentSummaryInformation", prop.getName());
prop = pi.next();
assertEquals("\u0005SummaryInformation", prop.getName());
prop = pi.next();
assertEquals("Image", prop.getName());
prop = pi.next();
assertEquals("Tags", prop.getName());
assertFalse(pi.hasNext());
// Check the SBAT (Small Blocks FAT) was properly processed
POIFSMiniStore ministore = fs.getMiniStore();
// Verify we only got one SBAT block
ministore.getBATBlockAndIndex(0);
ministore.getBATBlockAndIndex(128);
ministore.getBATBlockAndIndex(1023);
assertThrows(IndexOutOfBoundsException.class, () -> ministore.getBATBlockAndIndex(1024),
"Should only be one SBAT, but a 2nd was found");
// Verify a few offsets: 0->50 is a stream
for (int i = 0; i < 50; i++) {
assertEquals(i + 1, ministore.getNextBlock(i));
}
assertEquals(POIFSConstants.END_OF_CHAIN, ministore.getNextBlock(50));
}
}
/**
* Check that for a given block, we can correctly figure
* out what the next one is
*/
@ParameterizedTest()
@MethodSource("get512FileAndInput")
void nextBlock512(String file, Function<String,POIFSFileSystem> opener) throws IOException {
try (POIFSFileSystem fs = opener.apply(file)) {
// 0 -> 21 are simple
for (int i = 0; i < 21; i++) {
assertEquals(i + 1, fs.getNextBlock(i));
}
// 21 jumps to 89, then ends
assertEquals(89, fs.getNextBlock(21));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(89));
// 22 -> 88 simple sequential stream
for (int i = 22; i < 88; i++) {
assertEquals(i + 1, fs.getNextBlock(i));
}
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(88));
// 90 -> 96 is another stream
for (int i = 90; i < 96; i++) {
assertEquals(i + 1, fs.getNextBlock(i));
}
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(96));
// 97+98 is another
assertEquals(98, fs.getNextBlock(97));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(98));
// 99 is our FAT block
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs.getNextBlock(99));
// 100 onwards is free
for (int i = 100; i < fs.getBigBlockSizeDetails().getBATEntriesPerBlock(); i++) {
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(i));
}
}
}
@ParameterizedTest()
@MethodSource("get4kFileAndInput")
void nextBlock4k(String file, Function<String,POIFSFileSystem> opener) throws IOException {
// Quick check on 4096 byte blocks too
try (POIFSFileSystem fs = opener.apply(file)) {
// 0 -> 1 -> 2 -> end
assertEquals(1, fs.getNextBlock(0));
assertEquals(2, fs.getNextBlock(1));
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(2));
// 4 -> 11 then end
for (int i = 4; i < 11; i++) {
assertEquals(i + 1, fs.getNextBlock(i));
}
assertEquals(POIFSConstants.END_OF_CHAIN, fs.getNextBlock(11));
}
}
/**
* Check we get the right data back for each block
*/
@ParameterizedTest()
@MethodSource("get512FileAndInput")
void getBlock512(String file, Function<String,POIFSFileSystem> opener) throws IOException {
try (POIFSFileSystem fs = opener.apply(file)) {
// The 0th block is the first data block
ByteBuffer b = fs.getBlockAt(0);
assertEquals((byte) 0x9e, b.get());
assertEquals((byte) 0x75, b.get());
assertEquals((byte) 0x97, b.get());
assertEquals((byte) 0xf6, b.get());
// And the next block
b = fs.getBlockAt(1);
assertEquals((byte) 0x86, b.get());
assertEquals((byte) 0x09, b.get());
assertEquals((byte) 0x22, b.get());
assertEquals((byte) 0xfb, b.get());
// Check the final block too
b = fs.getBlockAt(99);
assertEquals((byte) 0x01, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x02, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
}
}
@ParameterizedTest()
@MethodSource("get4kFileAndInput")
void getBlock4k(String file, Function<String,POIFSFileSystem> opener) throws IOException {
// Quick check on 4096 byte blocks too
try (POIFSFileSystem fs = opener.apply(file)) {
// The 0th block is the first data block
ByteBuffer b = fs.getBlockAt(0);
assertEquals((byte) 0x9e, b.get());
assertEquals((byte) 0x75, b.get());
assertEquals((byte) 0x97, b.get());
assertEquals((byte) 0xf6, b.get());
// And the next block
b = fs.getBlockAt(1);
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x03, b.get());
assertEquals((byte) 0x00, b.get());
// The 14th block is the FAT
b = fs.getBlockAt(14);
assertEquals((byte) 0x01, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x02, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
assertEquals((byte) 0x00, b.get());
}
}
/**
* Ask for free blocks where there are some already
* to be had from the FAT
*/
@Test
void getFreeBlockWithSpare() throws IOException {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.getFile("BlockSize512.zvi"))) {
// Our first BAT block has spares
assertTrue(fs.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
// First free one is 100
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(100));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(101));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(102));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs.getNextBlock(103));
// Ask, will get 100
assertEquals(100, fs.getFreeBlock());
// Ask again, will still get 100 as not written to
assertEquals(100, fs.getFreeBlock());
// Allocate it, then ask again
fs.setNextBlock(100, POIFSConstants.END_OF_CHAIN);
assertEquals(101, fs.getFreeBlock());
}
}
/**
* Ask for free blocks where no free ones exist, and so the
* file needs to be extended and another BAT/XBAT added
*/
@Test
void getFreeBlockWithNoneSpare() throws IOException {
try (POIFSFileSystem fs1 = new POIFSFileSystem(_inst.openResourceAsStream("BlockSize512.zvi"))) {
int free;
// We have one BAT at block 99
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs1.getNextBlock(99));
assertBATCount(fs1, 1, 0);
// We've spare ones from 100 to 128
for (int i = 100; i < 128; i++) {
assertEquals(POIFSConstants.UNUSED_BLOCK, fs1.getNextBlock(i));
}
// Check our BAT knows it's free
assertTrue(fs1.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
// Allocate all the spare ones
for (int i = 100; i < 128; i++) {
fs1.setNextBlock(i, POIFSConstants.END_OF_CHAIN);
}
// BAT is now full, but there's only the one
assertFalse(fs1.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
assertThrows(IndexOutOfBoundsException.class, () -> fs1.getBATBlockAndIndex(128), "Should only be one BAT");
assertBATCount(fs1, 1, 0);
// Now ask for a free one, will need to extend the file
assertEquals(129, fs1.getFreeBlock());
assertFalse(fs1.getBATBlockAndIndex(0).getBlock().hasFreeSectors());
assertTrue(fs1.getBATBlockAndIndex(128).getBlock().hasFreeSectors());
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs1.getNextBlock(128));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs1.getNextBlock(129));
// We now have 2 BATs, but no XBATs
assertBATCount(fs1, 2, 0);
// Fill up to hold 109 BAT blocks
for (int i = 0; i < 109; i++) {
fs1.getFreeBlock();
int startOffset = i * 128;
while (fs1.getBATBlockAndIndex(startOffset).getBlock().hasFreeSectors()) {
free = fs1.getFreeBlock();
fs1.setNextBlock(free, POIFSConstants.END_OF_CHAIN);
}
}
assertFalse(fs1.getBATBlockAndIndex(109 * 128 - 1).getBlock().hasFreeSectors());
assertThrows(IndexOutOfBoundsException.class, () -> fs1.getBATBlockAndIndex(109 * 128), "Should only be 109 BATs");
// We now have 109 BATs, but no XBATs
assertBATCount(fs1, 109, 0);
// Ask for it to be written out, and check the header
HeaderBlock header = writeOutAndReadHeader(fs1);
assertEquals(109, header.getBATCount());
assertEquals(0, header.getXBATCount());
// Ask for another, will get our first XBAT
free = fs1.getFreeBlock();
assertTrue(free > 0, "Had: " + free);
assertFalse(fs1.getBATBlockAndIndex(109 * 128 - 1).getBlock().hasFreeSectors());
assertTrue(fs1.getBATBlockAndIndex(110 * 128 - 1).getBlock().hasFreeSectors());
assertThrows(IndexOutOfBoundsException.class, () -> fs1.getBATBlockAndIndex(110 * 128), "Should only be 110 BATs");
assertBATCount(fs1, 110, 1);
header = writeOutAndReadHeader(fs1);
assertEquals(110, header.getBATCount());
assertEquals(1, header.getXBATCount());
// Fill the XBAT, which means filling 127 BATs
for (int i = 109; i < 109 + 127; i++) {
fs1.getFreeBlock();
int startOffset = i * 128;
while (fs1.getBATBlockAndIndex(startOffset).getBlock().hasFreeSectors()) {
free = fs1.getFreeBlock();
fs1.setNextBlock(free, POIFSConstants.END_OF_CHAIN);
}
assertBATCount(fs1, i + 1, 1);
}
// Should now have 109+127 = 236 BATs
assertFalse(fs1.getBATBlockAndIndex(236 * 128 - 1).getBlock().hasFreeSectors());
assertThrows(IndexOutOfBoundsException.class, () -> fs1.getBATBlockAndIndex(236 * 128), "Should only be 236 BATs");
assertBATCount(fs1, 236, 1);
// Ask for another, will get our 2nd XBAT
free = fs1.getFreeBlock();
assertTrue(free > 0, "Had: " + free);
assertFalse(fs1.getBATBlockAndIndex(236 * 128 - 1).getBlock().hasFreeSectors());
assertTrue(fs1.getBATBlockAndIndex(237 * 128 - 1).getBlock().hasFreeSectors());
assertThrows(IndexOutOfBoundsException.class, () -> fs1.getBATBlockAndIndex(237 * 128), "Should only be 237 BATs");
// Check the counts now
assertBATCount(fs1, 237, 2);
// Check the header
header = writeOutAndReadHeader(fs1);
assertNotNull(header);
// Now, write it out, and read it back in again fully
try (POIFSFileSystem fs2 = writeOutAndReadBack(fs1)) {
// Check that it is seen correctly
assertBATCount(fs2, 237, 2);
assertFalse(fs2.getBATBlockAndIndex(236 * 128 - 1).getBlock().hasFreeSectors());
assertTrue(fs2.getBATBlockAndIndex(237 * 128 - 1).getBlock().hasFreeSectors());
assertThrows(IndexOutOfBoundsException.class, () -> fs2.getBATBlockAndIndex(237 * 128), "Should only be 237 BATs");
}
}
}
/**
* Test that we can correctly get the list of directory
* entries, and the details on the files in them
*/
@ParameterizedTest
@MethodSource("get512and4kFileAndInput")
void listEntries(String file, Function<String,POIFSFileSystem> opener) throws IOException {
try (POIFSFileSystem fs = opener.apply(file)) {
DirectoryEntry root = fs.getRoot();
assertEquals(5, root.getEntryCount());
// Check by the names
Entry thumbnail = root.getEntryCaseInsensitive("Thumbnail");
Entry dsi = root.getEntryCaseInsensitive("\u0005DocumentSummaryInformation");
Entry si = root.getEntryCaseInsensitive("\u0005SummaryInformation");
Entry image = root.getEntryCaseInsensitive("Image");
Entry tags = root.getEntryCaseInsensitive("Tags");
assertFalse(thumbnail.isDirectoryEntry());
assertFalse(dsi.isDirectoryEntry());
assertFalse(si.isDirectoryEntry());
assertTrue(image.isDirectoryEntry());
assertFalse(tags.isDirectoryEntry());
// Check via the iterator
Iterator<Entry> it = root.getEntries();
assertEquals(thumbnail.getName(), it.next().getName());
assertEquals(dsi.getName(), it.next().getName());
assertEquals(si.getName(), it.next().getName());
assertEquals(image.getName(), it.next().getName());
assertEquals(tags.getName(), it.next().getName());
// Look inside another
DirectoryEntry imageD = (DirectoryEntry) image;
assertEquals(7, imageD.getEntryCount());
}
}
/**
* Tests that we can get the correct contents for
* a document in the filesystem
*/
@ParameterizedTest
@MethodSource("get512and4kFileAndInput")
void getDocumentEntry(String file, Function<String,POIFSFileSystem> opener)
throws IOException, NoPropertySetStreamException {
try (POIFSFileSystem fs = opener.apply(file)) {
DirectoryEntry root = fs.getRoot();
Entry si = root.getEntryCaseInsensitive("\u0005SummaryInformation");
assertTrue(si.isDocumentEntry());
DocumentNode doc = (DocumentNode) si;
// Check we can read it
assertContentsMatches(null, doc);
// Now try to build the property set
try (DocumentInputStream inp = new DocumentInputStream(doc)) {
PropertySet ps = PropertySetFactory.create(inp);
SummaryInformation inf = (SummaryInformation) ps;
// Check some bits in it
assertNull(inf.getApplicationName());
assertNull(inf.getAuthor());
assertNull(inf.getSubject());
assertEquals(131333, inf.getOSVersion());
}
// Try the other summary information
si = root.getEntryCaseInsensitive("\u0005DocumentSummaryInformation");
assertTrue(si.isDocumentEntry());
doc = (DocumentNode) si;
assertContentsMatches(null, doc);
try (DocumentInputStream inp = new DocumentInputStream(doc)) {
PropertySet ps = PropertySetFactory.create(inp);
DocumentSummaryInformation dinf = (DocumentSummaryInformation) ps;
assertEquals(131333, dinf.getOSVersion());
}
}
}
/**
* Read a file, write it and read it again.
* Then, alter+add some streams, write and read
*/
@ParameterizedTest
@MethodSource("get512and4kFileAndInput")
void readWriteRead(String file, Function<String,POIFSFileSystem> opener) throws IOException, NoPropertySetStreamException {
SummaryInformation sinf;
DocumentSummaryInformation dinf;
DirectoryEntry root, testDir;
try (POIFSFileSystem fs1 = opener.apply(file)) {
// Check we can find the entries we expect
root = fs1.getRoot();
assertEquals(5, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Tags"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
// Write out, re-load
try (POIFSFileSystem fs2 = writeOutAndReadBack(fs1)) {
// Check they're still there
root = fs2.getRoot();
assertEquals(5, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Tags"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
// Check the contents of them - parse the summary block and check
sinf = (SummaryInformation) PropertySetFactory.create(new DocumentInputStream(
(DocumentEntry) root.getEntryCaseInsensitive(SummaryInformation.DEFAULT_STREAM_NAME)));
assertEquals(131333, sinf.getOSVersion());
dinf = (DocumentSummaryInformation) PropertySetFactory.create(new DocumentInputStream(
(DocumentEntry) root.getEntryCaseInsensitive(DocumentSummaryInformation.DEFAULT_STREAM_NAME)));
assertEquals(131333, dinf.getOSVersion());
// Add a test mini stream
testDir = root.createDirectory("Testing 123");
testDir.createDirectory("Testing 456");
testDir.createDirectory("Testing 789");
byte[] mini = new byte[]{42, 0, 1, 2, 3, 4, 42};
testDir.createDocument("Mini", new ByteArrayInputStream(mini));
// Write out, re-load
try (POIFSFileSystem fs3 = writeOutAndReadBack(fs2)) {
root = fs3.getRoot();
testDir = (DirectoryEntry) root.getEntryCaseInsensitive("Testing 123");
assertEquals(6, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Tags"));
assertThat(root.getEntryNames(), hasItem("Testing 123"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
// Check old and new are there
sinf = (SummaryInformation) PropertySetFactory.create(new DocumentInputStream(
(DocumentEntry) root.getEntryCaseInsensitive(SummaryInformation.DEFAULT_STREAM_NAME)));
assertEquals(131333, sinf.getOSVersion());
dinf = (DocumentSummaryInformation) PropertySetFactory.create(new DocumentInputStream(
(DocumentEntry) root.getEntryCaseInsensitive(DocumentSummaryInformation.DEFAULT_STREAM_NAME)));
assertEquals(131333, dinf.getOSVersion());
assertContentsMatches(mini, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini"));
// Write out and read once more, just to be sure
try (POIFSFileSystem fs4 = writeOutAndReadBack(fs3)) {
root = fs4.getRoot();
testDir = (DirectoryEntry) root.getEntryCaseInsensitive("Testing 123");
assertEquals(6, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Tags"));
assertThat(root.getEntryNames(), hasItem("Testing 123"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
sinf = (SummaryInformation) PropertySetFactory.create(new DocumentInputStream(
(DocumentEntry) root.getEntryCaseInsensitive(SummaryInformation.DEFAULT_STREAM_NAME)));
assertEquals(131333, sinf.getOSVersion());
dinf = (DocumentSummaryInformation) PropertySetFactory.create(new DocumentInputStream(
(DocumentEntry) root.getEntryCaseInsensitive(DocumentSummaryInformation.DEFAULT_STREAM_NAME)));
assertEquals(131333, dinf.getOSVersion());
assertContentsMatches(mini, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini"));
// Add a full stream, delete a full stream
byte[] main4096 = new byte[4096];
main4096[0] = -10;
main4096[4095] = -11;
testDir.createDocument("Normal4096", new ByteArrayInputStream(main4096));
assertTrue(root.getEntryCaseInsensitive("Tags").delete());
// Write out, re-load
try (POIFSFileSystem fs5 = writeOutAndReadBack(fs4)) {
// Check it's all there
root = fs5.getRoot();
testDir = (DirectoryEntry) root.getEntryCaseInsensitive("Testing 123");
assertEquals(5, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Testing 123"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
// Check old and new are there
sinf = (SummaryInformation) PropertySetFactory.create(new DocumentInputStream(
(DocumentEntry) root.getEntryCaseInsensitive(SummaryInformation.DEFAULT_STREAM_NAME)));
assertEquals(131333, sinf.getOSVersion());
dinf = (DocumentSummaryInformation) PropertySetFactory.create(new DocumentInputStream(
(DocumentEntry) root.getEntryCaseInsensitive(DocumentSummaryInformation.DEFAULT_STREAM_NAME)));
assertEquals(131333, dinf.getOSVersion());
assertContentsMatches(mini, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini"));
assertContentsMatches(main4096, (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4096"));
// Delete a directory, and add one more
assertTrue(testDir.getEntryCaseInsensitive("Testing 456").delete());
testDir.createDirectory("Testing ABC");
// Save
try (POIFSFileSystem fs6 = writeOutAndReadBack(fs5)) {
// Check
root = fs6.getRoot();
testDir = (DirectoryEntry) root.getEntryCaseInsensitive("Testing 123");
assertEquals(5, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Testing 123"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
assertEquals(4, testDir.getEntryCount());
assertThat(testDir.getEntryNames(), hasItem("Mini"));
assertThat(testDir.getEntryNames(), hasItem("Normal4096"));
assertThat(testDir.getEntryNames(), hasItem("Testing 789"));
assertThat(testDir.getEntryNames(), hasItem("Testing ABC"));
// Add another mini stream
byte[] mini2 = new byte[]{-42, 0, -1, -2, -3, -4, -42};
testDir.createDocument("Mini2", new ByteArrayInputStream(mini2));
// Save, load, check
try (POIFSFileSystem fs7 = writeOutAndReadBack(fs6)) {
root = fs7.getRoot();
testDir = (DirectoryEntry) root.getEntryCaseInsensitive("Testing 123");
assertEquals(5, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Testing 123"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
assertEquals(5, testDir.getEntryCount());
assertThat(testDir.getEntryNames(), hasItem("Mini"));
assertThat(testDir.getEntryNames(), hasItem("Mini2"));
assertThat(testDir.getEntryNames(), hasItem("Normal4096"));
assertThat(testDir.getEntryNames(), hasItem("Testing 789"));
assertThat(testDir.getEntryNames(), hasItem("Testing ABC"));
assertContentsMatches(mini, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini"));
assertContentsMatches(mini2, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini2"));
assertContentsMatches(main4096, (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4096"));
// Delete a mini stream, add one more
assertTrue(testDir.getEntryCaseInsensitive("Mini").delete());
byte[] mini3 = new byte[]{42, 0, 42, 0, 42, 0, 42};
testDir.createDocument("Mini3", new ByteArrayInputStream(mini3));
// Save, load, check
try (POIFSFileSystem fs8 = writeOutAndReadBack(fs7)) {
root = fs8.getRoot();
testDir = (DirectoryEntry) root.getEntryCaseInsensitive("Testing 123");
assertEquals(5, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Testing 123"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
assertEquals(5, testDir.getEntryCount());
assertThat(testDir.getEntryNames(), hasItem("Mini2"));
assertThat(testDir.getEntryNames(), hasItem("Mini3"));
assertThat(testDir.getEntryNames(), hasItem("Normal4096"));
assertThat(testDir.getEntryNames(), hasItem("Testing 789"));
assertThat(testDir.getEntryNames(), hasItem("Testing ABC"));
assertContentsMatches(mini2, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini2"));
assertContentsMatches(mini3, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini3"));
assertContentsMatches(main4096, (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4096"));
// Change some existing streams
POIFSDocument mini2Doc = new POIFSDocument((DocumentNode) testDir.getEntryCaseInsensitive("Mini2"));
mini2Doc.replaceContents(new ByteArrayInputStream(mini));
byte[] main4106 = new byte[4106];
main4106[0] = 41;
main4106[4105] = 42;
POIFSDocument mainDoc = new POIFSDocument((DocumentNode) testDir.getEntryCaseInsensitive("Normal4096"));
mainDoc.replaceContents(new ByteArrayInputStream(main4106));
// Re-check
try (POIFSFileSystem fs9 = writeOutAndReadBack(fs8)) {
root = fs9.getRoot();
testDir = (DirectoryEntry) root.getEntryCaseInsensitive("Testing 123");
assertEquals(5, root.getEntryCount());
assertThat(root.getEntryNames(), hasItem("Thumbnail"));
assertThat(root.getEntryNames(), hasItem("Image"));
assertThat(root.getEntryNames(), hasItem("Testing 123"));
assertThat(root.getEntryNames(), hasItem("\u0005DocumentSummaryInformation"));
assertThat(root.getEntryNames(), hasItem("\u0005SummaryInformation"));
assertEquals(5, testDir.getEntryCount());
assertThat(testDir.getEntryNames(), hasItem("Mini2"));
assertThat(testDir.getEntryNames(), hasItem("Mini3"));
assertThat(testDir.getEntryNames(), hasItem("Normal4096"));
assertThat(testDir.getEntryNames(), hasItem("Testing 789"));
assertThat(testDir.getEntryNames(), hasItem("Testing ABC"));
assertContentsMatches(mini, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini2"));
assertContentsMatches(mini3, (DocumentEntry) testDir.getEntryCaseInsensitive("Mini3"));
assertContentsMatches(main4106, (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4096"));
}
}
}
}
}
}
}
}
}
}
/**
* Create a new file, write it and read it again
* Then, add some streams, write and read
*/
@Test
void createWriteRead() throws IOException {
try (POIFSFileSystem fs1 = new POIFSFileSystem()) {
// Initially has Properties + BAT but not SBAT
assertEquals(POIFSConstants.END_OF_CHAIN, fs1.getNextBlock(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs1.getNextBlock(1));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs1.getNextBlock(2));
// Check that the SBAT is empty
assertEquals(POIFSConstants.END_OF_CHAIN, fs1.getRoot().getProperty().getStartBlock());
// Check that properties table was given block 0
assertEquals(0, fs1._get_property_table().getStartBlock());
// Write and read it
try (POIFSFileSystem fs2 = writeOutAndReadBack(fs1)) {
// No change, SBAT remains empty
assertEquals(POIFSConstants.END_OF_CHAIN, fs2.getNextBlock(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs2.getNextBlock(1));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs2.getNextBlock(2));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs2.getNextBlock(3));
assertEquals(POIFSConstants.END_OF_CHAIN, fs2.getRoot().getProperty().getStartBlock());
assertEquals(0, fs2._get_property_table().getStartBlock());
}
}
// Check the same but with saving to a file
try (POIFSFileSystem fs3 = new POIFSFileSystem();
POIFSFileSystem fs4 = writeOutFileAndReadBack(fs3)) {
// Same, no change, SBAT remains empty
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs4.getNextBlock(1));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs4.getNextBlock(2));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs4.getNextBlock(3));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getRoot().getProperty().getStartBlock());
assertEquals(0, fs4._get_property_table().getStartBlock());
// Put everything within a new directory
DirectoryEntry testDir = fs4.createDirectory("Test Directory");
// Add a new Normal Stream (Normal Streams minimum 4096 bytes)
byte[] main4096 = new byte[4096];
main4096[0] = -10;
main4096[4095] = -11;
testDir.createDocument("Normal4096", new ByteArrayInputStream(main4096));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs4.getNextBlock(1));
assertEquals(3, fs4.getNextBlock(2));
assertEquals(4, fs4.getNextBlock(3));
assertEquals(5, fs4.getNextBlock(4));
assertEquals(6, fs4.getNextBlock(5));
assertEquals(7, fs4.getNextBlock(6));
assertEquals(8, fs4.getNextBlock(7));
assertEquals(9, fs4.getNextBlock(8));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(9));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs4.getNextBlock(10));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs4.getNextBlock(11));
// SBAT still unused
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getRoot().getProperty().getStartBlock());
// Add a bigger Normal Stream
byte[] main5124 = new byte[5124];
main5124[0] = -22;
main5124[5123] = -33;
testDir.createDocument("Normal5124", new ByteArrayInputStream(main5124));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs4.getNextBlock(1));
assertEquals(3, fs4.getNextBlock(2));
assertEquals(4, fs4.getNextBlock(3));
assertEquals(5, fs4.getNextBlock(4));
assertEquals(6, fs4.getNextBlock(5));
assertEquals(7, fs4.getNextBlock(6));
assertEquals(8, fs4.getNextBlock(7));
assertEquals(9, fs4.getNextBlock(8));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(9));
assertEquals(11, fs4.getNextBlock(10));
assertEquals(12, fs4.getNextBlock(11));
assertEquals(13, fs4.getNextBlock(12));
assertEquals(14, fs4.getNextBlock(13));
assertEquals(15, fs4.getNextBlock(14));
assertEquals(16, fs4.getNextBlock(15));
assertEquals(17, fs4.getNextBlock(16));
assertEquals(18, fs4.getNextBlock(17));
assertEquals(19, fs4.getNextBlock(18));
assertEquals(20, fs4.getNextBlock(19));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(20));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs4.getNextBlock(21));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs4.getNextBlock(22));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getRoot().getProperty().getStartBlock());
// Now Add a mini stream
byte[] mini = new byte[]{42, 0, 1, 2, 3, 4, 42};
testDir.createDocument("Mini", new ByteArrayInputStream(mini));
// Mini stream will get one block for fat + one block for data
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs4.getNextBlock(1));
assertEquals(3, fs4.getNextBlock(2));
assertEquals(4, fs4.getNextBlock(3));
assertEquals(5, fs4.getNextBlock(4));
assertEquals(6, fs4.getNextBlock(5));
assertEquals(7, fs4.getNextBlock(6));
assertEquals(8, fs4.getNextBlock(7));
assertEquals(9, fs4.getNextBlock(8));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(9));
assertEquals(11, fs4.getNextBlock(10));
assertEquals(12, fs4.getNextBlock(11));
assertEquals(13, fs4.getNextBlock(12));
assertEquals(14, fs4.getNextBlock(13));
assertEquals(15, fs4.getNextBlock(14));
assertEquals(16, fs4.getNextBlock(15));
assertEquals(17, fs4.getNextBlock(16));
assertEquals(18, fs4.getNextBlock(17));
assertEquals(19, fs4.getNextBlock(18));
assertEquals(20, fs4.getNextBlock(19));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(20));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(21));
assertEquals(POIFSConstants.END_OF_CHAIN, fs4.getNextBlock(22));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs4.getNextBlock(23));
// Check the mini stream location was set
// (21 is mini fat, 22 is first mini stream block)
assertEquals(22, fs4.getRoot().getProperty().getStartBlock());
// Write and read back
try (POIFSFileSystem fs5 = writeOutAndReadBack(fs4)) {
HeaderBlock header = writeOutAndReadHeader(fs5);
// Check the header has the right points in it
assertEquals(1, header.getBATCount());
assertEquals(1, header.getBATArray()[0]);
assertEquals(0, header.getPropertyCount());
assertEquals(0, header.getPropertyStart());
assertEquals(1, header.getSBATCount());
assertEquals(21, header.getSBATStart());
assertEquals(22, fs5._get_property_table().getRoot().getStartBlock());
// Block use should be almost the same, except the properties
// stream will have grown out to cover 2 blocks
// Check the block use is all unchanged
assertEquals(23, fs5.getNextBlock(0)); // Properties now extends over 2 blocks
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs5.getNextBlock(1));
assertEquals(3, fs5.getNextBlock(2));
assertEquals(4, fs5.getNextBlock(3));
assertEquals(5, fs5.getNextBlock(4));
assertEquals(6, fs5.getNextBlock(5));
assertEquals(7, fs5.getNextBlock(6));
assertEquals(8, fs5.getNextBlock(7));
assertEquals(9, fs5.getNextBlock(8));
assertEquals(POIFSConstants.END_OF_CHAIN, fs5.getNextBlock(9)); // End of normal4096
assertEquals(11, fs5.getNextBlock(10));
assertEquals(12, fs5.getNextBlock(11));
assertEquals(13, fs5.getNextBlock(12));
assertEquals(14, fs5.getNextBlock(13));
assertEquals(15, fs5.getNextBlock(14));
assertEquals(16, fs5.getNextBlock(15));
assertEquals(17, fs5.getNextBlock(16));
assertEquals(18, fs5.getNextBlock(17));
assertEquals(19, fs5.getNextBlock(18));
assertEquals(20, fs5.getNextBlock(19));
assertEquals(POIFSConstants.END_OF_CHAIN, fs5.getNextBlock(20)); // End of normal5124
assertEquals(POIFSConstants.END_OF_CHAIN, fs5.getNextBlock(21)); // Mini Stream FAT
assertEquals(POIFSConstants.END_OF_CHAIN, fs5.getNextBlock(22)); // Mini Stream data
assertEquals(POIFSConstants.END_OF_CHAIN, fs5.getNextBlock(23)); // Properties #2
assertEquals(POIFSConstants.UNUSED_BLOCK, fs5.getNextBlock(24));
// Check some data
assertEquals(1, fs5.getRoot().getEntryCount());
testDir = (DirectoryEntry) fs5.getRoot().getEntryCaseInsensitive("Test Directory");
assertEquals(3, testDir.getEntryCount());
DocumentEntry miniDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Mini");
assertContentsMatches(mini, miniDoc);
DocumentEntry normDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4096");
assertContentsMatches(main4096, normDoc);
normDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Normal5124");
assertContentsMatches(main5124, normDoc);
// Delete a couple of streams
assertTrue(miniDoc.delete());
assertTrue(normDoc.delete());
// Check - will have un-used sectors now
try (POIFSFileSystem fs6 = writeOutAndReadBack(fs5)) {
assertEquals(POIFSConstants.END_OF_CHAIN, fs6.getNextBlock(0)); // Props back in 1 block
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs6.getNextBlock(1));
assertEquals(3, fs6.getNextBlock(2));
assertEquals(4, fs6.getNextBlock(3));
assertEquals(5, fs6.getNextBlock(4));
assertEquals(6, fs6.getNextBlock(5));
assertEquals(7, fs6.getNextBlock(6));
assertEquals(8, fs6.getNextBlock(7));
assertEquals(9, fs6.getNextBlock(8));
assertEquals(POIFSConstants.END_OF_CHAIN, fs6.getNextBlock(9)); // End of normal4096
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(10));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(11));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(12));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(13));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(14));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(15));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(16));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(17));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(18));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(19));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(20));
assertEquals(POIFSConstants.END_OF_CHAIN, fs6.getNextBlock(21)); // Mini Stream FAT
assertEquals(POIFSConstants.END_OF_CHAIN, fs6.getNextBlock(22)); // Mini Stream data
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(23)); // Properties gone
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(24));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs6.getNextBlock(25));
}
}
}
}
@Test
void addBeforeWrite() throws IOException {
try (POIFSFileSystem fs1 = new POIFSFileSystem()) {
// Initially has Properties + BAT but nothing else
assertEquals(POIFSConstants.END_OF_CHAIN, fs1.getNextBlock(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs1.getNextBlock(1));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs1.getNextBlock(2));
HeaderBlock hdr = writeOutAndReadHeader(fs1);
// No mini stream, and no xbats
// Will have fat then properties stream
assertEquals(1, hdr.getBATCount());
assertEquals(1, hdr.getBATArray()[0]);
assertEquals(0, hdr.getPropertyCount());
assertEquals(0, hdr.getPropertyStart());
assertEquals(POIFSConstants.END_OF_CHAIN, hdr.getSBATStart());
assertEquals(POIFSConstants.END_OF_CHAIN, hdr.getXBATIndex());
assertEquals(POIFSConstants.SMALLER_BIG_BLOCK_SIZE * 3, fs1.size());
}
// Get a clean filesystem to start with
try (POIFSFileSystem fs1 = new POIFSFileSystem()) {
// Put our test files in a non-standard place
DirectoryEntry parentDir = fs1.createDirectory("Parent Directory");
DirectoryEntry testDir = parentDir.createDirectory("Test Directory");
// Add to the mini stream
byte[] mini = new byte[]{42, 0, 1, 2, 3, 4, 42};
testDir.createDocument("Mini", new ByteArrayInputStream(mini));
// Add to the main stream
byte[] main4096 = new byte[4096];
main4096[0] = -10;
main4096[4095] = -11;
testDir.createDocument("Normal4096", new ByteArrayInputStream(main4096));
// Check the mini stream was added, then the main stream
assertEquals(POIFSConstants.END_OF_CHAIN, fs1.getNextBlock(0));
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs1.getNextBlock(1));
assertEquals(POIFSConstants.END_OF_CHAIN, fs1.getNextBlock(2)); // Mini Fat
assertEquals(POIFSConstants.END_OF_CHAIN, fs1.getNextBlock(3)); // Mini Stream
assertEquals(5, fs1.getNextBlock(4)); // Main Stream
assertEquals(6, fs1.getNextBlock(5));
assertEquals(7, fs1.getNextBlock(6));
assertEquals(8, fs1.getNextBlock(7));
assertEquals(9, fs1.getNextBlock(8));
assertEquals(10, fs1.getNextBlock(9));
assertEquals(11, fs1.getNextBlock(10));
assertEquals(POIFSConstants.END_OF_CHAIN, fs1.getNextBlock(11));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs1.getNextBlock(12));
assertEquals(POIFSConstants.SMALLER_BIG_BLOCK_SIZE * 13, fs1.size());
// Check that we can read the right data pre-write
DocumentEntry miniDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Mini");
assertContentsMatches(mini, miniDoc);
DocumentEntry normDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4096");
assertContentsMatches(main4096, normDoc);
// Write, read, check
HeaderBlock hdr = writeOutAndReadHeader(fs1);
try (POIFSFileSystem fs2 = writeOutAndReadBack(fs1)) {
// Check the header details - will have the sbat near the start,
// then the properties at the end
assertEquals(1, hdr.getBATCount());
assertEquals(1, hdr.getBATArray()[0]);
assertEquals(2, hdr.getSBATStart());
assertEquals(0, hdr.getPropertyCount());
assertEquals(0, hdr.getPropertyStart());
assertEquals(POIFSConstants.END_OF_CHAIN, hdr.getXBATIndex());
// Check the block allocation is unchanged, other than
// the properties stream going in at the end
assertEquals(12, fs2.getNextBlock(0)); // Properties
assertEquals(POIFSConstants.FAT_SECTOR_BLOCK, fs2.getNextBlock(1));
assertEquals(POIFSConstants.END_OF_CHAIN, fs2.getNextBlock(2));
assertEquals(POIFSConstants.END_OF_CHAIN, fs2.getNextBlock(3));
assertEquals(5, fs2.getNextBlock(4));
assertEquals(6, fs2.getNextBlock(5));
assertEquals(7, fs2.getNextBlock(6));
assertEquals(8, fs2.getNextBlock(7));
assertEquals(9, fs2.getNextBlock(8));
assertEquals(10, fs2.getNextBlock(9));
assertEquals(11, fs2.getNextBlock(10));
assertEquals(POIFSConstants.END_OF_CHAIN, fs2.getNextBlock(11));
assertEquals(POIFSConstants.END_OF_CHAIN, fs2.getNextBlock(12));
assertEquals(POIFSConstants.UNUSED_BLOCK, fs2.getNextBlock(13));
assertEquals(POIFSConstants.SMALLER_BIG_BLOCK_SIZE * 14, fs2.size());
// Check the data
DirectoryEntry fsRoot = fs2.getRoot();
assertEquals(1, fsRoot.getEntryCount());
parentDir = (DirectoryEntry) fsRoot.getEntryCaseInsensitive("Parent Directory");
assertEquals(1, parentDir.getEntryCount());
testDir = (DirectoryEntry) parentDir.getEntryCaseInsensitive("Test Directory");
assertEquals(2, testDir.getEntryCount());
miniDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Mini");
assertContentsMatches(mini, miniDoc);
normDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4096");
assertContentsMatches(main4096, normDoc);
// Add one more stream to each, then save and re-load
byte[] mini2 = new byte[]{-42, 0, -1, -2, -3, -4, -42};
testDir.createDocument("Mini2", new ByteArrayInputStream(mini2));
// Add to the main stream
byte[] main4106 = new byte[4106];
main4106[0] = 41;
main4106[4105] = 42;
testDir.createDocument("Normal4106", new ByteArrayInputStream(main4106));
// Recheck the data in all 4 streams
try (POIFSFileSystem fs3 = writeOutAndReadBack(fs2)) {
fsRoot = fs3.getRoot();
assertEquals(1, fsRoot.getEntryCount());
parentDir = (DirectoryEntry) fsRoot.getEntryCaseInsensitive("Parent Directory");
assertEquals(1, parentDir.getEntryCount());
testDir = (DirectoryEntry) parentDir.getEntryCaseInsensitive("Test Directory");
assertEquals(4, testDir.getEntryCount());
miniDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Mini");
assertContentsMatches(mini, miniDoc);
miniDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Mini2");
assertContentsMatches(mini2, miniDoc);
normDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4106");
assertContentsMatches(main4106, normDoc);
}
}
}
}
@Test
void readZeroLengthEntries() throws IOException {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.getFile("only-zero-byte-streams.ole2"))) {
DirectoryNode testDir = fs.getRoot();
assertEquals(3, testDir.getEntryCount());
DocumentEntry entry = (DocumentEntry) testDir.getEntryCaseInsensitive("test-zero-1");
assertNotNull(entry);
assertEquals(0, entry.getSize());
entry = (DocumentEntry) testDir.getEntryCaseInsensitive("test-zero-2");
assertNotNull(entry);
assertEquals(0, entry.getSize());
entry = (DocumentEntry) testDir.getEntryCaseInsensitive("test-zero-3");
assertNotNull(entry);
assertEquals(0, entry.getSize());
// Check properties, all have zero length, no blocks
PropertyTable props = fs._get_property_table();
assertEquals(POIFSConstants.END_OF_CHAIN, props.getRoot().getStartBlock());
for (Property prop : props.getRoot()) {
assertEquals("test-zero-", prop.getName().substring(0, 10));
assertEquals(POIFSConstants.END_OF_CHAIN, prop.getStartBlock());
}
}
}
@Test
void writeZeroLengthEntries() throws IOException {
try (POIFSFileSystem fs1 = new POIFSFileSystem()) {
DirectoryNode testDir = fs1.getRoot();
DocumentEntry miniDoc;
DocumentEntry normDoc;
DocumentEntry emptyDoc;
// Add mini and normal sized entries to start
byte[] mini2 = new byte[]{-42, 0, -1, -2, -3, -4, -42};
testDir.createDocument("Mini2", new ByteArrayInputStream(mini2));
// Add to the main stream
byte[] main4106 = new byte[4106];
main4106[0] = 41;
main4106[4105] = 42;
testDir.createDocument("Normal4106", new ByteArrayInputStream(main4106));
// Now add some empty ones
byte[] empty = new byte[0];
testDir.createDocument("empty-1", new ByteArrayInputStream(empty));
testDir.createDocument("empty-2", new ByteArrayInputStream(empty));
testDir.createDocument("empty-3", new ByteArrayInputStream(empty));
// Check
miniDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Mini2");
assertContentsMatches(mini2, miniDoc);
normDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4106");
assertContentsMatches(main4106, normDoc);
emptyDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("empty-1");
assertContentsMatches(empty, emptyDoc);
emptyDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("empty-2");
assertContentsMatches(empty, emptyDoc);
emptyDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("empty-3");
assertContentsMatches(empty, emptyDoc);
// Look at the properties entry, and check the empty ones
// have zero size and no start block
PropertyTable props = fs1._get_property_table();
Iterator<Property> propsIt = props.getRoot().getChildren();
Property prop = propsIt.next();
assertEquals("Mini2", prop.getName());
assertEquals(0, prop.getStartBlock());
assertEquals(7, prop.getSize());
prop = propsIt.next();
assertEquals("Normal4106", prop.getName());
assertEquals(4, prop.getStartBlock()); // BAT, Props, SBAT, MIni
assertEquals(4106, prop.getSize());
prop = propsIt.next();
assertEquals("empty-1", prop.getName());
assertEquals(POIFSConstants.END_OF_CHAIN, prop.getStartBlock());
assertEquals(0, prop.getSize());
prop = propsIt.next();
assertEquals("empty-2", prop.getName());
assertEquals(POIFSConstants.END_OF_CHAIN, prop.getStartBlock());
assertEquals(0, prop.getSize());
prop = propsIt.next();
assertEquals("empty-3", prop.getName());
assertEquals(POIFSConstants.END_OF_CHAIN, prop.getStartBlock());
assertEquals(0, prop.getSize());
// Save and re-check
try (POIFSFileSystem fs2 = writeOutAndReadBack(fs1)) {
testDir = fs2.getRoot();
miniDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Mini2");
assertContentsMatches(mini2, miniDoc);
normDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("Normal4106");
assertContentsMatches(main4106, normDoc);
emptyDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("empty-1");
assertContentsMatches(empty, emptyDoc);
emptyDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("empty-2");
assertContentsMatches(empty, emptyDoc);
emptyDoc = (DocumentEntry) testDir.getEntryCaseInsensitive("empty-3");
assertContentsMatches(empty, emptyDoc);
// Check that a mini-stream was assigned, with one block used
assertEquals(3, testDir.getProperty().getStartBlock());
assertEquals(64, testDir.getProperty().getSize());
}
}
}
/**
* Test that the property count is always 0 when writing files with a block size of 512 bytes.
*/
@Test
void testWritePropertyCount512() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.openResourceAsStream("BlockSize512.zvi"))) {
assertEquals(0, fs.getHeaderBlock().getPropertyCount(), "Property count");
for (int i = 1; i <= 100; i++) {
fs.getRoot().createOrUpdateDocument("Entry " + i, new ByteArrayInputStream(new byte[8192]));
}
assertEquals(0, writeOutAndReadBack(fs).getHeaderBlock().getPropertyCount(), "Property count");
}
}
/**
* Test that the property count is updated when writing files with a block size of 4096 bytes.
*/
@Test
void testWritePropertyCount4096() throws Exception {
try (POIFSFileSystem fs = new POIFSFileSystem(_inst.openResourceAsStream("BlockSize4096.zvi"))) {
assertEquals(0, fs.getHeaderBlock().getPropertyCount(), "Property count");
for (int i = 1; i <= 100; i++) {
fs.getRoot().createOrUpdateDocument("Entry " + i, new ByteArrayInputStream(new byte[8192]));
}
assertEquals(5, writeOutAndReadBack(fs).getHeaderBlock().getPropertyCount(), "Property count");
}
}
/**
* Test that we can read a file with POIFS, create a new POIFS instance,
* write it out, read it with POIFS, and see the original data
*/
@Test
void POIFSReadCopyWritePOIFSRead() throws IOException {
File testFile = POIDataSamples.getSpreadSheetInstance().getFile("Simple.xls");
try (POIFSFileSystem src = new POIFSFileSystem(testFile);
POIFSFileSystem nfs = new POIFSFileSystem()) {
byte[] wbDataExp = IOUtils.toByteArray(src.createDocumentInputStream("Workbook"));
EntryUtils.copyNodes(src.getRoot(), nfs.getRoot());
try (POIFSFileSystem pfs = writeOutFileAndReadBack(nfs)) {
byte[] wbDataAct = IOUtils.toByteArray(pfs.createDocumentInputStream("Workbook"));
assertThat(wbDataExp, equalTo(wbDataAct));
}
}
}
/**
* Ensure that you can recursively delete directories and their
* contents
*/
@Test
void RecursiveDelete() throws IOException {
File testFile = POIDataSamples.getSpreadSheetInstance().getFile("SimpleMacro.xls");
try (POIFSFileSystem src = new POIFSFileSystem(testFile)) {
// Starts out with 5 entries:
// _VBA_PROJECT_CUR
// SummaryInformation <(0x05)SummaryInformation>
// DocumentSummaryInformation <(0x05)DocumentSummaryInformation>
// Workbook
// CompObj <(0x01)CompObj>
assertEquals(5, _countChildren(src._get_property_table().getRoot()));
assertEquals(5, src.getRoot().getEntryCount());
// Grab the VBA project root
DirectoryEntry vbaProj = (DirectoryEntry) src.getRoot().getEntryCaseInsensitive("_VBA_PROJECT_CUR");
assertEquals(3, vbaProj.getEntryCount());
// Can't delete yet, has stuff
assertFalse(vbaProj.delete());
// Recursively delete
_recursiveDeletee(vbaProj);
// Entries gone
assertEquals(4, _countChildren(src._get_property_table().getRoot()));
assertEquals(4, src.getRoot().getEntryCount());
}
}
private void _recursiveDeletee(Entry entry) throws IOException {
if (entry.isDocumentEntry()) {
assertTrue(entry.delete());
return;
}
DirectoryEntry dir = (DirectoryEntry) entry;
String[] names = dir.getEntryNames().toArray(new String[dir.getEntryCount()]);
for (String name : names) {
Entry ce = dir.getEntryCaseInsensitive(name);
_recursiveDeletee(ce);
}
assertTrue(dir.delete());
}
@SuppressWarnings("unused")
private int _countChildren(DirectoryProperty p) {
int count = 0;
for (Property cp : p) {
count++;
}
return count;
}
/**
* To ensure we can create a file >2gb in size, as well as to
* extend existing files past the 2gb boundary.
* <p>
* Note that to run this test, you will require 2.5+gb of free
* space on your TMP/TEMP partition/disk
* <p>
* Note that to run this test, you need to be able to mmap 2.5+gb
* files, which may need bigger kernel.shmmax and vm.max_map_count
* settings on Linux.
* <p>
* TODO Fix this to work...
*/
@Test
@Disabled("Work in progress test for #60670")
void creationAndExtensionPast2GB() throws Exception {
File big = TempFile.createTempFile("poi-test-", ".ole2");
assumeTrue(big.getFreeSpace() > 2.5 * 1024 * 1024 * 1024,
"2.5gb of free space is required on your tmp/temp partition/disk to run large file tests");
System.out.println("Slow, memory heavy test in progress....");
int s100mb = 100 * 1024 * 1024;
int s512mb = 512 * 1024 * 1024;
long s2gb = 2L * 1024 * 1024 * 1024;
DocumentEntry entry;
// Create a just-sub 2gb file
try (POIFSFileSystem fs = POIFSFileSystem.create(big)) {
for (int i = 0; i < 19; i++) {
fs.createDocument(new DummyDataInputStream(s100mb), "Entry" + i);
}
fs.writeFilesystem();
}
// Extend it past the 2gb mark
try (POIFSFileSystem fs = new POIFSFileSystem(big, false)) {
for (int i = 0; i < 19; i++) {
entry = (DocumentEntry) fs.getRoot().getEntryCaseInsensitive("Entry" + i);
assertNotNull(entry);
assertEquals(s100mb, entry.getSize());
}
fs.createDocument(new DummyDataInputStream(s512mb), "Bigger");
fs.writeFilesystem();
}
// Check it still works
try (POIFSFileSystem fs = new POIFSFileSystem(big, false)) {
for (int i = 0; i < 19; i++) {
entry = (DocumentEntry) fs.getRoot().getEntryCaseInsensitive("Entry" + i);
assertNotNull(entry);
assertEquals(s100mb, entry.getSize());
}
entry = (DocumentEntry) fs.getRoot().getEntryCaseInsensitive("Bigger");
assertNotNull(entry);
assertEquals(s512mb, entry.getSize());
}
// Tidy
assertTrue(big.delete());
// Create a >2gb file
try (POIFSFileSystem fs = POIFSFileSystem.create(big)) {
for (int i = 0; i < 4; i++) {
fs.createDocument(new DummyDataInputStream(s512mb), "Entry" + i);
}
fs.writeFilesystem();
}
// Read it
try (POIFSFileSystem fs = new POIFSFileSystem(big, false)) {
for (int i = 0; i < 4; i++) {
entry = (DocumentEntry) fs.getRoot().getEntryCaseInsensitive("Entry" + i);
assertNotNull(entry);
assertEquals(s512mb, entry.getSize());
}
// Extend it
fs.createDocument(new DummyDataInputStream(s512mb), "Entry4");
fs.writeFilesystem();
}
// Check it worked
try (POIFSFileSystem fs = new POIFSFileSystem(big, false)) {
for (int i = 0; i < 5; i++) {
entry = (DocumentEntry) fs.getRoot().getEntryCaseInsensitive("Entry" + i);
assertNotNull(entry);
assertEquals(s512mb, entry.getSize());
}
}
// Tidy
assertTrue(big.delete());
// Create a file with a 2gb entry
try (POIFSFileSystem fs = POIFSFileSystem.create(big)) {
fs.createDocument(new DummyDataInputStream(s100mb), "Small");
// TODO Check we get a helpful error about the max size
fs.createDocument(new DummyDataInputStream(s2gb), "Big");
}
}
private static final class DummyDataInputStream extends InputStream {
private final long maxSize;
private long size;
private DummyDataInputStream(long maxSize) {
this.maxSize = maxSize;
this.size = 0;
}
@Override
public int read() {
if (size >= maxSize) return -1;
size++;
return (int) (size % 128);
}
@Override
public int read(byte[] b) {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int offset, int len) {
if (size >= maxSize) return -1;
int sz = (int) Math.min(len, maxSize - size);
for (int i = 0; i < sz; i++) {
b[i + offset] = (byte) ((size + i) % 128);
}
size += sz;
return sz;
}
}
@Test
void testDeepData() throws IOException {
try (InputStream stream = POIDataSamples.getPOIFSInstance().openResourceAsStream("deep-data.bin")) {
IOException ex = assertThrows(IOException.class,
() -> new POIFSFileSystem(stream));
assertEquals("Property tree too deep, likely a corrupt file", ex.getMessage());
}
}
@Disabled("Takes a long time to run")
@Test
void performance() throws Exception {
int iterations = 200;//1_000;
long start = System.currentTimeMillis();
for (int i = 0; i < iterations; i++) {
try (InputStream inputStream = POIDataSamples.getHSMFInstance().openResourceAsStream("lots-of-recipients.msg");
POIFSFileSystem srcFileSystem = new POIFSFileSystem(inputStream);
POIFSFileSystem destFileSystem = new POIFSFileSystem()) {
copyAllEntries(srcFileSystem.getRoot(), destFileSystem.getRoot());
File file = File.createTempFile("npoi", ".dat");
try (OutputStream outputStream = new FileOutputStream(file)) {
destFileSystem.writeFilesystem(outputStream);
}
assertTrue(file.delete());
if (i % 10 == 0) System.out.print(".");
}
}
System.out.println("NPOI took: " + (System.currentTimeMillis() - start));
}
private static void copyAllEntries(DirectoryEntry srcDirectory, DirectoryEntry destDirectory) throws IOException {
Iterator<Entry> iterator = srcDirectory.getEntries();
while (iterator.hasNext()) {
Entry entry = iterator.next();
if (entry.isDirectoryEntry()) {
DirectoryEntry childDest = destDirectory.createDirectory(entry.getName());
copyAllEntries((DirectoryEntry) entry, childDest);
} else {
DocumentEntry srcEntry = (DocumentEntry) entry;
try (InputStream inputStream = new DocumentInputStream(srcEntry)) {
destDirectory.createDocument(entry.getName(), inputStream);
}
}
}
}
}