Index: src/java/org/apache/poi/hssf/record/RecordInputStream.java
===================================================================
--- src/java/org/apache/poi/hssf/record/RecordInputStream.java (revision 801584)
+++ src/java/org/apache/poi/hssf/record/RecordInputStream.java (working copy)
@@ -19,12 +19,18 @@
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
+import java.util.Set;
+import java.util.Collections;
+import java.util.HashSet;
import org.apache.poi.hssf.dev.BiffViewer;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianInputStream;
+import org.apache.poi.util.RC4InputStream;
+import javax.crypto.Cipher;
+
/**
* Title: Record Input Stream
* Description: Wraps a stream and provides helper methods for the construction of records.
@@ -41,8 +47,26 @@
*/
private static final int DATA_LEN_NEEDS_TO_BE_READ = -1;
private static final byte[] EMPTY_BYTE_ARRAY = { };
-
- /**
+
+ private RC4InputStream rc4is;
+
+ private static final Set unencryptSid = new HashSet();
+ static {
+ /*
+ [MS-XLS] — v20090708
+ Excel Binary File Format (.xls) Structure Specification
+ */
+ unencryptSid.add(BOFRecord.sid);
+ unencryptSid.add(FilePassRecord.sid);
+ // UsrExcl
+ // FileLock
+ unencryptSid.add(InterfaceHdrRecord.sid);
+ // RRDInfo
+ // RRDHead
+ // TODO: Additionally, the lbPlyPos field of the BoundSheet8 record MUST NOT be encrypted.
+ }
+
+ /**
* For use in {@link BiffViewer} which may construct {@link Record}s that don't completely
* read all available data. This exception should never be thrown otherwise.
*/
@@ -77,13 +101,8 @@
private int _currentDataOffset;
public RecordInputStream(InputStream in) throws RecordFormatException {
- if (in instanceof LittleEndianInput) {
- // accessing directly is an optimisation
- _le = (LittleEndianInput) in;
- } else {
- // less optimal, but should work OK just the same. Often occurs in junit tests.
- _le = new LittleEndianInputStream(in);
- }
+ rc4is = new RC4InputStream(in);
+ _le = new LittleEndianInputStream(rc4is);
_nextSid = readNextSid();
}
@@ -100,6 +119,7 @@
_currentDataOffset += LittleEndian.BYTE_SIZE;
return _le.readUByte();
}
+
public int read(byte[] b, int off, int len) {
int limit = Math.min(len, remaining());
if (limit == 0) {
@@ -143,6 +163,7 @@
}
return INVALID_SID_VALUE;
}
+ rc4is.setDecrypt(false);
int result = _le.readUShort();
if (result == INVALID_SID_VALUE) {
throw new RecordFormatException("Found invalid sid (" + result + ")");
@@ -169,6 +190,10 @@
throw new RecordFormatException("The content of an excel record cannot exceed "
+ MAX_RECORD_DATA_SIZE + " bytes");
}
+
+ if (!unencryptSid.contains((short) _currentSid)) {
+ rc4is.setDecrypt(true);
+ }
}
private void checkRecordPosition(int requiredByteCount) {
@@ -394,4 +419,8 @@
// and before the formatting run data)
return _nextSid == ContinueRecord.sid;
}
+
+ public void setRC4Header(RC4Header rc4Header) {
+ rc4is.setHeader(rc4Header);
+ }
}
Index: src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java
===================================================================
--- src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java (revision 801584)
+++ src/java/org/apache/poi/hssf/record/RecordFactoryInputStream.java (working copy)
@@ -148,6 +148,10 @@
return record;
}
+ if (record instanceof FilePassRecord) {
+ recStream.setRC4Header(((FilePassRecord) record).getRC4Header());
+ }
+
if (record instanceof EOFRecord) {
bofDepth--;
if (bofDepth < 1) {
Index: src/java/org/apache/poi/hssf/record/FilePassRecord.java
===================================================================
--- src/java/org/apache/poi/hssf/record/FilePassRecord.java (revision 801584)
+++ src/java/org/apache/poi/hssf/record/FilePassRecord.java (working copy)
@@ -33,49 +33,86 @@
public final class FilePassRecord
extends StandardRecord
{
- public final static short sid = 0x2F;
- private int field_1_encryptedpassword;
+ public static final short sid = 0x2F;
+ private int wEncryptionType;
+ private static final int ENCRYPTION_XOR = 0;
+ private static final int ENCRYPTION_OTHER = 1;
+
+ private static final int ENCRYPTION_OTHER_RC4 = 1;
+ private static final int ENCRYPTION_OTHER_CAPI_2 = 2;
+ private static final int ENCRYPTION_OTHER_CAPI_3 = 3;
+
+ private RC4Header header;
+
public FilePassRecord()
{
}
public FilePassRecord(RecordInputStream in)
{
- field_1_encryptedpassword = in.readInt();
+ wEncryptionType = in.readShort();
+
+ switch (wEncryptionType) {
+ case ENCRYPTION_XOR:
+ throw new RecordFormatException("HSSF does not currently support XOR obfuscation");
+ case ENCRYPTION_OTHER:
+ int encryptionInfo = in.readShort();
+
+ switch (encryptionInfo) {
+ case ENCRYPTION_OTHER_RC4:
+ header = new RC4Header(in);
+
+ return;
+ case ENCRYPTION_OTHER_CAPI_2:
+ case ENCRYPTION_OTHER_CAPI_3:
+ throw new RecordFormatException("HSSF does not currently support CryptoAPI encryption");
+ default:
+ throw new RecordFormatException("Unknown encryption info "+wEncryptionType);
+ }
+ default:
+ throw new RecordFormatException("Unknown encryption type "+wEncryptionType);
+ }
//Whilst i have read in the password, HSSF currently has no plans to support/decrypt the remainder
//of this workbook
- throw new RecordFormatException("HSSF does not currently support encrypted workbooks");
+// throw new RecordFormatException("HSSF does not currently support encrypted workbooks");
}
- public String toString()
- {
- StringBuffer buffer = new StringBuffer();
+ @Override
+ public String toString() {
+ StringBuilder buffer = new StringBuilder();
buffer.append("[FILEPASS]\n");
- buffer.append(" .password = ").append(field_1_encryptedpassword)
- .append("\n");
+ buffer.append(" .type = ").append(wEncryptionType)
+ .append('\n');
buffer.append("[/FILEPASS]\n");
return buffer.toString();
}
+ @Override
public void serialize(LittleEndianOutput out) {
- out.writeInt(( short ) field_1_encryptedpassword);
}
+ @Override
protected int getDataSize() {
return 4;
}
+ @Override
public short getSid()
{
return sid;
}
+ @Override
public Object clone() {
FilePassRecord rec = new FilePassRecord();
- rec.field_1_encryptedpassword = field_1_encryptedpassword;
+ rec.wEncryptionType = wEncryptionType;
return rec;
}
+
+ public RC4Header getRC4Header() {
+ return header;
+ }
}
Index: src/testcases/org/apache/poi/hssf/extractor/TestExcelExtractor.java
===================================================================
--- src/testcases/org/apache/poi/hssf/extractor/TestExcelExtractor.java (revision 801584)
+++ src/testcases/org/apache/poi/hssf/extractor/TestExcelExtractor.java (working copy)
@@ -24,6 +24,7 @@
import junit.framework.TestCase;
import org.apache.poi.hssf.HSSFTestDataSamples;
+import org.apache.poi.hssf.record.RC4Header;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
@@ -289,4 +290,13 @@
assertTrue("Unable to find expected word in text\n" + text, text.indexOf("test phrase") >= 0);
}
}
+
+ public void testPassword() {
+ RC4Header.setPassword("password");
+ ExcelExtractor extractor = createExtractor("password.xls");
+ String text = extractor.getText();
+ RC4Header.setPassword(null);
+
+ assertTrue(text.contains("ZIP"));
+ }
}