Compare commits

...

4 Commits

Author SHA1 Message Date
PJ Fanning
ac4e3c199e more HSSFEventFactory changes 2026-02-20 20:19:10 +01:00
PJ Fanning
a1f6f2786f Update RecordFactoryInputStream.java 2026-02-20 19:55:49 +01:00
PJ Fanning
b923655b43 update tests 2026-02-20 19:48:56 +01:00
PJ Fanning
6b72a2dff3 Update DocumentInputStream.java 2026-02-20 19:46:56 +01:00
5 changed files with 217 additions and 53 deletions

View File

@ -47,7 +47,7 @@ public class HSSFEventFactory {
/** /**
* Processes a file into essentially record events. * Processes a file into essentially record events.
* *
* @param req an Instance of HSSFRequest which has your registered listeners * @param req an instance of HSSFRequest which has your registered listeners
* @param fs a POIFS filesystem containing your workbook * @param fs a POIFS filesystem containing your workbook
* *
* @throws IOException if the workbook contained errors * @throws IOException if the workbook contained errors
@ -59,12 +59,42 @@ public class HSSFEventFactory {
/** /**
* Processes a file into essentially record events. * Processes a file into essentially record events.
* *
* @param req an Instance of HSSFRequest which has your registered listeners * @param req an instance of HSSFRequest which has your registered listeners
* @param fs a POIFS filesystem containing your workbook
* @param password in char array format (can be null)
*
* @throws IOException if the workbook contained errors
* @since 6.0.0
*/
public void processWorkbookEvents(HSSFRequest req, POIFSFileSystem fs,
final char[] password) throws IOException {
processWorkbookEvents(req, fs.getRoot(), password);
}
/**
* Processes a file into essentially record events.
*
* @param req an instance of HSSFRequest which has your registered listeners
* @param dir a DirectoryNode containing your workbook * @param dir a DirectoryNode containing your workbook
* *
* @throws IOException if the workbook contained errors * @throws IOException if the workbook contained errors
*/ */
public void processWorkbookEvents(HSSFRequest req, DirectoryNode dir) throws IOException { public void processWorkbookEvents(HSSFRequest req, DirectoryNode dir) throws IOException {
processWorkbookEvents(req, dir, null);
}
/**
* Processes a file into essentially record events.
*
* @param req an instance of HSSFRequest which has your registered listeners
* @param dir a DirectoryNode containing your workbook
* @param password in char array format (can be null)
*
* @throws IOException if the workbook contained errors
* @since 6.0.0
*/
public void processWorkbookEvents(HSSFRequest req, DirectoryNode dir,
final char[] password) throws IOException {
// some old documents have "WORKBOOK" or "BOOK" // some old documents have "WORKBOOK" or "BOOK"
String name = null; String name = null;
if (dir.hasEntry(WORKBOOK)) { if (dir.hasEntry(WORKBOOK)) {
@ -79,17 +109,16 @@ public class HSSFEventFactory {
} }
try (InputStream in = dir.createDocumentInputStream(name)) { try (InputStream in = dir.createDocumentInputStream(name)) {
processEvents(req, in); processEvents(req, in, password);
} }
} }
/** /**
* Processes a file into essentially record events. * Processes a file into essentially record events.
* *
* @param req an Instance of HSSFRequest which has your registered listeners * @param req an instance of HSSFRequest which has your registered listeners
* @param fs a POIFS filesystem containing your workbook * @param fs a POIFS filesystem containing your workbook
* @return numeric user-specified result code. * @return numeric user-specified result code.
*
* @throws HSSFUserException if the processing should be aborted * @throws HSSFUserException if the processing should be aborted
* @throws IOException if the workbook contained errors * @throws IOException if the workbook contained errors
*/ */
@ -101,7 +130,24 @@ public class HSSFEventFactory {
/** /**
* Processes a file into essentially record events. * Processes a file into essentially record events.
* *
* @param req an Instance of HSSFRequest which has your registered listeners * @param req an instance of HSSFRequest which has your registered listeners
* @param fs a POIFS filesystem containing your workbook
* @param password in char array format (can be null)
* @return numeric user-specified result code.
* @throws HSSFUserException if the processing should be aborted
* @throws IOException if the workbook contained errors
* @since 6.0.0
*/
public short abortableProcessWorkbookEvents(HSSFRequest req, POIFSFileSystem fs,
final char[] password)
throws IOException, HSSFUserException {
return abortableProcessWorkbookEvents(req, fs.getRoot(), password);
}
/**
* Processes a file into essentially record events.
*
* @param req an instance of HSSFRequest which has your registered listeners
* @param dir a DirectoryNode containing your workbook * @param dir a DirectoryNode containing your workbook
* @return numeric user-specified result code. * @return numeric user-specified result code.
* *
@ -115,6 +161,26 @@ public class HSSFEventFactory {
} }
} }
/**
* Processes a file into essentially record events.
*
* @param req an instance of HSSFRequest which has your registered listeners
* @param dir a DirectoryNode containing your workbook
* @param password in char array format (can be null)
* @return numeric user-specified result code.
*
* @throws HSSFUserException if the processing should be aborted
* @throws IOException if the workbook contained errors
* @since 6.0.0
*/
public short abortableProcessWorkbookEvents(HSSFRequest req, DirectoryNode dir,
final char[] password)
throws IOException, HSSFUserException {
try (InputStream in = dir.createDocumentInputStream("Workbook")) {
return abortableProcessEvents(req, in, password);
}
}
/** /**
* Processes a DocumentInputStream into essentially Record events. * Processes a DocumentInputStream into essentially Record events.
* *
@ -123,23 +189,43 @@ public class HSSFEventFactory {
* user code or <code>HSSFUserException</code> will be passed back. * user code or <code>HSSFUserException</code> will be passed back.
* *
* @see org.apache.poi.poifs.filesystem.POIFSFileSystem#createDocumentInputStream(String) * @see org.apache.poi.poifs.filesystem.POIFSFileSystem#createDocumentInputStream(String)
* @param req an Instance of HSSFRequest which has your registered listeners * @param req an instance of HSSFRequest which has your registered listeners
* @param in a DocumentInputStream obtained from POIFS's POIFSFileSystem object * @param in a DocumentInputStream obtained from POIFS's POIFSFileSystem object
*/ */
public void processEvents(HSSFRequest req, InputStream in) { public void processEvents(HSSFRequest req, InputStream in) {
try { try {
genericProcessEvents(req, in); genericProcessEvents(req, in, null);
} catch (HSSFUserException hue) { } catch (HSSFUserException hue) {
/*If an HSSFUserException user exception is thrown, ignore it.*/ /*If an HSSFUserException user exception is thrown, ignore it.*/
} }
} }
/**
* Processes a DocumentInputStream into essentially Record events.
*
* If an <code>AbortableHSSFListener</code> causes a halt to processing during this call
* the method will return just as with <code>abortableProcessEvents</code>, but no
* user code or <code>HSSFUserException</code> will be passed back.
*
* @see org.apache.poi.poifs.filesystem.POIFSFileSystem#createDocumentInputStream(String)
* @param req an instance of HSSFRequest which has your registered listeners
* @param in a DocumentInputStream obtained from POIFS's POIFSFileSystem object
* @param password in char array format (can be null)
* @since 6.0.0
*/
public void processEvents(HSSFRequest req, InputStream in, final char[] password) {
try {
genericProcessEvents(req, in, password);
} catch (HSSFUserException hue) {
/*If an HSSFUserException user exception is thrown, ignore it.*/
}
}
/** /**
* Processes a DocumentInputStream into essentially Record events. * Processes a DocumentInputStream into essentially Record events.
* *
* @see org.apache.poi.poifs.filesystem.POIFSFileSystem#createDocumentInputStream(String) * @see org.apache.poi.poifs.filesystem.POIFSFileSystem#createDocumentInputStream(String)
* @param req an Instance of HSSFRequest which has your registered listeners * @param req an instance of HSSFRequest which has your registered listeners
* @param in a DocumentInputStream obtained from POIFS's POIFSFileSystem object * @param in a DocumentInputStream obtained from POIFS's POIFSFileSystem object
* @return numeric user-specified result code. * @return numeric user-specified result code.
* *
@ -147,23 +233,41 @@ public class HSSFEventFactory {
*/ */
public short abortableProcessEvents(HSSFRequest req, InputStream in) public short abortableProcessEvents(HSSFRequest req, InputStream in)
throws HSSFUserException { throws HSSFUserException {
return genericProcessEvents(req, in); return genericProcessEvents(req, in, null);
} }
/** /**
* Processes a DocumentInputStream into essentially Record events. * Processes a DocumentInputStream into essentially Record events.
* *
* @see org.apache.poi.poifs.filesystem.POIFSFileSystem#createDocumentInputStream(String) * @see org.apache.poi.poifs.filesystem.POIFSFileSystem#createDocumentInputStream(String)
* @param req an Instance of HSSFRequest which has your registered listeners * @param req an instance of HSSFRequest which has your registered listeners
* @param in a DocumentInputStream obtained from POIFS's POIFSFileSystem object
* @param password in char array format (can be null)
* @return numeric user-specified result code.
*
* @throws HSSFUserException if the processing should be aborted
* @since 6.0.0
*/
public short abortableProcessEvents(HSSFRequest req, InputStream in, final char[] password)
throws HSSFUserException {
return genericProcessEvents(req, in, password);
}
/**
* Processes a DocumentInputStream into essentially Record events.
*
* @see org.apache.poi.poifs.filesystem.POIFSFileSystem#createDocumentInputStream(String)
* @param req an instance of HSSFRequest which has your registered listeners
* @param in a DocumentInputStream obtained from POIFS's POIFSFileSystem object * @param in a DocumentInputStream obtained from POIFS's POIFSFileSystem object
* @return numeric user-specified result code. * @return numeric user-specified result code.
*/ */
private short genericProcessEvents(HSSFRequest req, InputStream in) private short genericProcessEvents(HSSFRequest req, InputStream in, final char[] password)
throws HSSFUserException { throws HSSFUserException {
short userCode = 0; short userCode = 0;
// Create a new RecordStream and use that // Create a new RecordStream and use that
RecordFactoryInputStream recordStream = new RecordFactoryInputStream(in, false); RecordFactoryInputStream recordStream = new RecordFactoryInputStream(
in, false, password);
// Process each record as they come in // Process each record as they come in
while(true) { while(true) {

View File

@ -119,10 +119,8 @@ public final class RecordFactoryInputStream {
return createDecryptingStream(original, pwdString); return createDecryptingStream(original, pwdString);
} }
/** // keep this private because with new API methods, we want to encourage use of char array for passwords
* @since 6.0.0 private RecordInputStream createDecryptingStream(InputStream original, String password) {
*/
public RecordInputStream createDecryptingStream(InputStream original, String password) {
if (password == null) { if (password == null) {
password = Biff8EncryptionKey.getCurrentUserPassword(); password = Biff8EncryptionKey.getCurrentUserPassword();
if (password == null) { if (password == null) {

View File

@ -259,7 +259,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
return _current_offset == _document_size; return _current_offset == _document_size;
} }
private void checkAvaliable(int requestedSize) { private void checkAvailable(int requestedSize) {
if (_closed) { if (_closed) {
throw new IllegalStateException("cannot perform requested operation on a closed stream"); throw new IllegalStateException("cannot perform requested operation on a closed stream");
} }
@ -280,7 +280,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
throw new IllegalArgumentException("Can't read negative number of bytes, but had: " + len); throw new IllegalArgumentException("Can't read negative number of bytes, but had: " + len);
} }
checkAvaliable(len); checkAvailable(len);
int read = 0; int read = 0;
while(read < len) { while(read < len) {
@ -314,7 +314,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
@Override @Override
public long readLong() { public long readLong() {
checkAvaliable(LONG_SIZE); checkAvailable(LONG_SIZE);
byte[] data = new byte[LONG_SIZE]; byte[] data = new byte[LONG_SIZE];
readFully(data, 0, LONG_SIZE); readFully(data, 0, LONG_SIZE);
return LittleEndian.getLong(data, 0); return LittleEndian.getLong(data, 0);
@ -322,7 +322,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
@Override @Override
public short readShort() { public short readShort() {
checkAvaliable(SHORT_SIZE); checkAvailable(SHORT_SIZE);
byte[] data = new byte[SHORT_SIZE]; byte[] data = new byte[SHORT_SIZE];
readFully(data, 0, SHORT_SIZE); readFully(data, 0, SHORT_SIZE);
return LittleEndian.getShort(data); return LittleEndian.getShort(data);
@ -330,7 +330,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
@Override @Override
public int readInt() { public int readInt() {
checkAvaliable(INT_SIZE); checkAvailable(INT_SIZE);
byte[] data = new byte[INT_SIZE]; byte[] data = new byte[INT_SIZE];
readFully(data, 0, INT_SIZE); readFully(data, 0, INT_SIZE);
return LittleEndian.getInt(data); return LittleEndian.getInt(data);
@ -343,7 +343,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
@Override @Override
public int readUShort() { public int readUShort() {
checkAvaliable(SHORT_SIZE); checkAvailable(SHORT_SIZE);
byte[] data = new byte[SHORT_SIZE]; byte[] data = new byte[SHORT_SIZE];
readFully(data, 0, SHORT_SIZE); readFully(data, 0, SHORT_SIZE);
return LittleEndian.getUShort(data); return LittleEndian.getUShort(data);
@ -351,7 +351,7 @@ public final class DocumentInputStream extends InputStream implements LittleEndi
@Override @Override
public int readUByte() { public int readUByte() {
checkAvaliable(1); checkAvailable(1);
byte[] data = new byte[1]; byte[] data = new byte[1];
readFully(data, 0, 1); readFully(data, 0, 1);
if (data[0] >= 0) if (data[0] >= 0)

View File

@ -17,12 +17,6 @@
package org.apache.poi.hssf.eventusermodel; package org.apache.poi.hssf.eventusermodel;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayList; import java.util.ArrayList;
@ -43,14 +37,15 @@ import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/** /**
* Testing for {@link HSSFEventFactory} * Testing for {@link HSSFEventFactory}
*/ */
final class TestHSSFEventFactory { final class TestHSSFEventFactory {
private final List<org.apache.poi.hssf.record.Record> records = new ArrayList<>();
private void openSample(String sampleFileName) throws IOException { private List<org.apache.poi.hssf.record.Record> openSample(String sampleFileName) throws IOException {
records.clear(); final List<org.apache.poi.hssf.record.Record> records = new ArrayList<>();
HSSFRequest req = new HSSFRequest(); HSSFRequest req = new HSSFRequest();
req.addListenerForAllRecords(records::add); req.addListenerForAllRecords(records::add);
try (InputStream is = HSSFTestDataSamples.openSampleFileStream(sampleFileName); try (InputStream is = HSSFTestDataSamples.openSampleFileStream(sampleFileName);
@ -58,12 +53,26 @@ final class TestHSSFEventFactory {
HSSFEventFactory factory = new HSSFEventFactory(); HSSFEventFactory factory = new HSSFEventFactory();
factory.processWorkbookEvents(req, fs); factory.processWorkbookEvents(req, fs);
} }
return records;
}
private List<org.apache.poi.hssf.record.Record> openSample(
String sampleFileName, String password) throws IOException {
final List<org.apache.poi.hssf.record.Record> records = new ArrayList<>();
HSSFRequest req = new HSSFRequest();
req.addListenerForAllRecords(records::add);
try (InputStream is = HSSFTestDataSamples.openSampleFileStream(sampleFileName);
POIFSFileSystem fs = new POIFSFileSystem(is)) {
HSSFEventFactory factory = new HSSFEventFactory();
factory.processWorkbookEvents(req, fs, password.toCharArray());
}
return records;
} }
@Test @Test
void testWithMissingRecords() throws Exception { void testWithMissingRecords() throws Exception {
openSample("SimpleWithSkip.xls"); final List<org.apache.poi.hssf.record.Record> records = openSample("SimpleWithSkip.xls");
int numRec = records.size(); int numRec = records.size();
@ -82,6 +91,7 @@ final class TestHSSFEventFactory {
// Some files have crazy ordering of their continue records // Some files have crazy ordering of their continue records
// Check that we don't break on them (bug #42844) // Check that we don't break on them (bug #42844)
final List<org.apache.poi.hssf.record.Record> records =
openSample("ContinueRecordProblem.xls"); openSample("ContinueRecordProblem.xls");
int numRec = records.size(); int numRec = records.size();
@ -106,14 +116,14 @@ final class TestHSSFEventFactory {
@Test @Test
@SuppressWarnings("java:S2699") @SuppressWarnings("java:S2699")
void testUnknownContinueRecords() throws Exception { void testUnknownContinueRecords() throws Exception {
openSample("42844.xls"); assertNotNull(openSample("42844.xls"));
} }
@Test @Test
@SuppressWarnings("java:S2699") @SuppressWarnings("java:S2699")
void testWithDifferentWorkbookName() throws Exception { void testWithDifferentWorkbookName() throws Exception {
openSample("BOOK_in_capitals.xls"); assertNotNull(openSample("BOOK_in_capitals.xls"));
openSample("WORKBOOK_in_capitals.xls"); assertNotNull(openSample("WORKBOOK_in_capitals.xls"));
} }
@Test @Test
@ -125,9 +135,49 @@ final class TestHSSFEventFactory {
@Test @Test
void testWithPasswordProtectedWorkbooks() throws Exception { void testWithPasswordProtectedWorkbooks() throws Exception {
final List<org.apache.poi.hssf.record.Record> records =
openSample("xor-encryption-abc.xls", "abc");
// Check we got the sheet and the contents
assertTrue(records.size() > 50);
// Has one sheet, with values 1,2,3 in column A rows 1-3
boolean hasSheet = false, hasA1 = false, hasA2 = false, hasA3 = false;
for (org.apache.poi.hssf.record.Record r : records) {
if (r instanceof BoundSheetRecord) {
BoundSheetRecord bsr = (BoundSheetRecord) r;
assertEquals("Sheet1", bsr.getSheetname());
hasSheet = true;
}
if (r instanceof NumberRecord) {
NumberRecord nr = (NumberRecord) r;
if (nr.getColumn() == 0 && nr.getRow() == 0) {
assertEquals(1, (int) nr.getValue());
hasA1 = true;
}
if (nr.getColumn() == 0 && nr.getRow() == 1) {
assertEquals(2, (int) nr.getValue());
hasA2 = true;
}
if (nr.getColumn() == 0 && nr.getRow() == 2) {
assertEquals(3, (int) nr.getValue());
hasA3 = true;
}
}
}
assertTrue(hasSheet, "Sheet record not found");
assertTrue(hasA1, "Numeric record for A1 not found");
assertTrue(hasA2, "Numeric record for A2 not found");
assertTrue(hasA3, "Numeric record for A3 not found");
}
@Test
void testWithPasswordProtectedWorkbooksBiff8EncryptionKey() throws Exception {
// With the password, is properly processed // With the password, is properly processed
Biff8EncryptionKey.setCurrentUserPassword("abc"); Biff8EncryptionKey.setCurrentUserPassword("abc");
try { try {
final List<org.apache.poi.hssf.record.Record> records =
openSample("xor-encryption-abc.xls"); openSample("xor-encryption-abc.xls");
// Check we got the sheet and the contents // Check we got the sheet and the contents

View File

@ -73,6 +73,18 @@ class TestXorEncryption {
@Test @Test
void testUserFile() throws IOException { void testUserFile() throws IOException {
File f = getSampleFile("xor-encryption-abc.xls");
try (POIFSFileSystem fs = new POIFSFileSystem(f, true);
HSSFWorkbook hwb = new HSSFWorkbook(fs.getRoot(), true, "abc".toCharArray())) {
HSSFSheet sh = hwb.getSheetAt(0);
assertEquals(1.0, sh.getRow(0).getCell(0).getNumericCellValue(), 0.0);
assertEquals(2.0, sh.getRow(1).getCell(0).getNumericCellValue(), 0.0);
assertEquals(3.0, sh.getRow(2).getCell(0).getNumericCellValue(), 0.0);
}
}
@Test
void testUserFileBiff8EncryptionKey() throws IOException {
File f = getSampleFile("xor-encryption-abc.xls"); File f = getSampleFile("xor-encryption-abc.xls");
Biff8EncryptionKey.setCurrentUserPassword("abc"); Biff8EncryptionKey.setCurrentUserPassword("abc");
try (POIFSFileSystem fs = new POIFSFileSystem(f, true); try (POIFSFileSystem fs = new POIFSFileSystem(f, true);