aboutsummaryrefslogtreecommitdiffstats
path: root/src/java
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2017-06-14 17:21:50 +0000
committerAndreas Beeker <kiwiwings@apache.org>2017-06-14 17:21:50 +0000
commit87591edbf3d531dfe73eeadfe691c785a8ba6ff0 (patch)
treec450869130c146e2474320a74b8e8c716bb10ae0 /src/java
parentaca91b788823c3e14bc355eb0e9068c6018a7112 (diff)
downloadpoi-87591edbf3d531dfe73eeadfe691c785a8ba6ff0.tar.gz
poi-87591edbf3d531dfe73eeadfe691c785a8ba6ff0.zip
#61162 - En-/decryption support for HWPF
- encryption support for HWPF - refactor/unify EncryptionInfo handling in H**F classes - remove Iterable interface from WorkbookRecordList - use getRecords() instead, to prevent modifications over the Iterator git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1798722 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java')
-rw-r--r--src/java/org/apache/poi/POIDocument.java140
-rw-r--r--src/java/org/apache/poi/POIOLE2TextExtractor.java32
-rw-r--r--src/java/org/apache/poi/hssf/model/InternalWorkbook.java66
-rw-r--r--src/java/org/apache/poi/hssf/model/LinkTable.java12
-rw-r--r--src/java/org/apache/poi/hssf/model/WorkbookRecordList.java110
-rw-r--r--src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java81
-rw-r--r--src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java11
-rw-r--r--src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java34
8 files changed, 287 insertions, 199 deletions
diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java
index 774507722a..5c2c201512 100644
--- a/src/java/org/apache/poi/POIDocument.java
+++ b/src/java/org/apache/poi/POIDocument.java
@@ -24,6 +24,7 @@ import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.security.GeneralSecurityException;
import java.util.List;
import org.apache.poi.hpsf.DocumentSummaryInformation;
@@ -31,12 +32,15 @@ import org.apache.poi.hpsf.PropertySet;
import org.apache.poi.hpsf.PropertySetFactory;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.Encryptor;
import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor;
+import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.util.IOUtils;
import org.apache.poi.util.Internal;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
@@ -60,8 +64,6 @@ public abstract class POIDocument implements Closeable {
/* Have the property streams been read yet? (Only done on-demand) */
private boolean initialized;
- private static final String[] encryptedStreamNames = { "EncryptedSummary" };
-
/**
* Constructs a POIDocument with the given directory node.
*
@@ -103,7 +105,9 @@ public abstract class POIDocument implements Closeable {
* if it could not be read for this document.
*/
public DocumentSummaryInformation getDocumentSummaryInformation() {
- if(!initialized) readProperties();
+ if(!initialized) {
+ readProperties();
+ }
return dsInf;
}
@@ -114,7 +118,9 @@ public abstract class POIDocument implements Closeable {
* if it could not be read for this document.
*/
public SummaryInformation getSummaryInformation() {
- if(!initialized) readProperties();
+ if(!initialized) {
+ readProperties();
+ }
return sInf;
}
@@ -128,7 +134,9 @@ public abstract class POIDocument implements Closeable {
* then nothing will happen.
*/
public void createInformationProperties() {
- if (!initialized) readProperties();
+ if (!initialized) {
+ readProperties();
+ }
if (sInf == null) {
sInf = PropertySetFactory.newSummaryInformation();
}
@@ -144,32 +152,40 @@ public abstract class POIDocument implements Closeable {
* it will remain null;
*/
protected void readProperties() {
- PropertySet ps;
-
- // DocumentSummaryInformation
- ps = getPropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
- if (ps instanceof DocumentSummaryInformation) {
- dsInf = (DocumentSummaryInformation)ps;
- } else if (ps != null) {
- logger.log(POILogger.WARN, "DocumentSummaryInformation property set came back with wrong class - ", ps.getClass());
- } else {
- logger.log(POILogger.WARN, "DocumentSummaryInformation property set came back as null");
+ if (initialized) {
+ return;
}
-
- // SummaryInformation
- ps = getPropertySet(SummaryInformation.DEFAULT_STREAM_NAME);
- if (ps instanceof SummaryInformation) {
- sInf = (SummaryInformation)ps;
- } else if (ps != null) {
- logger.log(POILogger.WARN, "SummaryInformation property set came back with wrong class - ", ps.getClass());
- } else {
- logger.log(POILogger.WARN, "SummaryInformation property set came back as null");
+ DocumentSummaryInformation dsi = readPropertySet(DocumentSummaryInformation.class, DocumentSummaryInformation.DEFAULT_STREAM_NAME);
+ if (dsi != null) {
+ dsInf = dsi;
+ }
+ SummaryInformation si = readPropertySet(SummaryInformation.class, SummaryInformation.DEFAULT_STREAM_NAME);
+ if (si != null) {
+ sInf = si;
}
// Mark the fact that we've now loaded up the properties
initialized = true;
}
+ @SuppressWarnings("unchecked")
+ private <T> T readPropertySet(Class<T> clazz, String name) {
+ String localName = clazz.getName().substring(clazz.getName().lastIndexOf('.')+1);
+ try {
+ PropertySet ps = getPropertySet(name);
+ if (clazz.isInstance(ps)) {
+ return (T)ps;
+ } else if (ps != null) {
+ logger.log(POILogger.WARN, localName+" property set came back with wrong class - "+ps.getClass().getName());
+ } else {
+ logger.log(POILogger.WARN, localName+" property set came back as null");
+ }
+ } catch (IOException e) {
+ logger.log(POILogger.ERROR, "can't retrieve property set", e);
+ }
+ return null;
+ }
+
/**
* For a given named property entry, either return it or null if
* if it wasn't found
@@ -177,8 +193,8 @@ public abstract class POIDocument implements Closeable {
* @param setName The property to read
* @return The value of the given property or null if it wasn't found.
*/
- protected PropertySet getPropertySet(String setName) {
- return getPropertySet(setName, null);
+ protected PropertySet getPropertySet(String setName) throws IOException {
+ return getPropertySet(setName, getEncryptionInfo());
}
/**
@@ -189,7 +205,7 @@ public abstract class POIDocument implements Closeable {
* @param encryptionInfo the encryption descriptor in case of cryptoAPI encryption
* @return The value of the given property or null if it wasn't found.
*/
- protected PropertySet getPropertySet(String setName, EncryptionInfo encryptionInfo) {
+ protected PropertySet getPropertySet(String setName, EncryptionInfo encryptionInfo) throws IOException {
DirectoryNode dirNode = directory;
NPOIFSFileSystem encPoifs = null;
@@ -197,14 +213,9 @@ public abstract class POIDocument implements Closeable {
try {
if (encryptionInfo != null && encryptionInfo.isDocPropsEncrypted()) {
step = "getting encrypted";
- String encryptedStream = null;
- for (String s : encryptedStreamNames) {
- if (dirNode.hasEntry(s)) {
- encryptedStream = s;
- }
- }
- if (encryptedStream == null) {
- throw new EncryptedDocumentException("can't find matching encrypted property stream");
+ String encryptedStream = getEncryptedPropertyStreamName();
+ if (!dirNode.hasEntry(encryptedStream)) {
+ throw new EncryptedDocumentException("can't find encrypted property stream '"+encryptedStream+"'");
}
CryptoAPIDecryptor dec = (CryptoAPIDecryptor)encryptionInfo.getDecryptor();
encPoifs = dec.getSummaryEntries(dirNode, encryptedStream);
@@ -226,17 +237,12 @@ public abstract class POIDocument implements Closeable {
} finally {
dis.close();
}
+ } catch (IOException e) {
+ throw e;
} catch (Exception e) {
- logger.log(POILogger.WARN, "Error "+step+" property set with name " + setName, e);
- return null;
+ throw new IOException("Error "+step+" property set with name " + setName, e);
} finally {
- if (encPoifs != null) {
- try {
- encPoifs.close();
- } catch(IOException e) {
- logger.log(POILogger.WARN, "Error closing encrypted property poifs", e);
- }
- }
+ IOUtils.closeQuietly(encPoifs);
}
}
@@ -271,20 +277,48 @@ public abstract class POIDocument implements Closeable {
* {@link NPOIFSFileSystem} occurs
*/
protected void writeProperties(NPOIFSFileSystem outFS, List<String> writtenEntries) throws IOException {
+ EncryptionInfo ei = getEncryptionInfo();
+ final boolean encryptProps = (ei != null && ei.isDocPropsEncrypted());
+ NPOIFSFileSystem fs = (encryptProps) ? new NPOIFSFileSystem() : outFS;
+
SummaryInformation si = getSummaryInformation();
if (si != null) {
- writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME, si, outFS);
+ writePropertySet(SummaryInformation.DEFAULT_STREAM_NAME, si, fs);
if(writtenEntries != null) {
writtenEntries.add(SummaryInformation.DEFAULT_STREAM_NAME);
}
}
DocumentSummaryInformation dsi = getDocumentSummaryInformation();
if (dsi != null) {
- writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, outFS);
+ writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, fs);
if(writtenEntries != null) {
writtenEntries.add(DocumentSummaryInformation.DEFAULT_STREAM_NAME);
}
}
+
+ if (!encryptProps) {
+ return;
+ }
+
+ // create empty document summary
+ dsi = PropertySetFactory.newDocumentSummaryInformation();
+ writePropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME, dsi, outFS);
+ // remove summary, if previously available
+ if (outFS.getRoot().hasEntry(SummaryInformation.DEFAULT_STREAM_NAME)) {
+ outFS.getRoot().getEntry(SummaryInformation.DEFAULT_STREAM_NAME).delete();
+ }
+ Encryptor encGen = ei.getEncryptor();
+ if (!(encGen instanceof CryptoAPIEncryptor)) {
+ throw new EncryptedDocumentException("Using "+ei.getEncryptionMode()+" encryption. Only CryptoAPI encryption supports encrypted property sets!");
+ }
+ CryptoAPIEncryptor enc = (CryptoAPIEncryptor)encGen;
+ try {
+ enc.setSummaryEntries(outFS.getRoot(), getEncryptedPropertyStreamName(), fs);
+ } catch (GeneralSecurityException e) {
+ throw new IOException(e);
+ } finally {
+ fs.close();
+ }
}
/**
@@ -443,4 +477,18 @@ public abstract class POIDocument implements Closeable {
directory = newDirectory;
return dn;
}
+
+ /**
+ * @return the stream name of the property set collection, if the document is encrypted
+ */
+ protected String getEncryptedPropertyStreamName() {
+ return "encryption";
+ }
+
+ /**
+ * @return the encryption info if the document is encrypted, otherwise {@code null}
+ */
+ public EncryptionInfo getEncryptionInfo() throws IOException {
+ return null;
+ }
}
diff --git a/src/java/org/apache/poi/POIOLE2TextExtractor.java b/src/java/org/apache/poi/POIOLE2TextExtractor.java
index 5884a9054e..05c7781057 100644
--- a/src/java/org/apache/poi/POIOLE2TextExtractor.java
+++ b/src/java/org/apache/poi/POIOLE2TextExtractor.java
@@ -27,7 +27,7 @@ import org.apache.poi.poifs.filesystem.DirectoryEntry;
* You will typically find the implementation of
* a given format's text extractor under
* org.apache.poi.[format].extractor .
- *
+ *
* @see org.apache.poi.hssf.extractor.ExcelExtractor
* @see org.apache.poi.hslf.extractor.PowerPointExtractor
* @see org.apache.poi.hdgf.extractor.VisioTextExtractor
@@ -39,12 +39,12 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
/**
* Creates a new text extractor for the given document
- *
+ *
* @param document The POIDocument to use in this extractor.
*/
public POIOLE2TextExtractor(POIDocument document) {
this.document = document;
-
+
// Ensure any underlying resources, such as open files,
// will get cleaned up if the user calls #close()
setFilesystem(document);
@@ -54,17 +54,17 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
* Creates a new text extractor, using the same
* document as another text extractor. Normally
* only used by properties extractors.
- *
+ *
* @param otherExtractor the extractor which document to be used
*/
protected POIOLE2TextExtractor(POIOLE2TextExtractor otherExtractor) {
this.document = otherExtractor.document;
}
-
+
/**
* Returns the document information metadata for the document
- *
- * @return The Document Summary Information or null
+ *
+ * @return The Document Summary Information or null
* if it could not be read for this document.
*/
public DocumentSummaryInformation getDocSummaryInformation() {
@@ -72,7 +72,7 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
}
/**
* Returns the summary information metadata for the document.
- *
+ *
* @return The Summary information for the document or null
* if it could not be read for this document.
*/
@@ -83,7 +83,7 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
/**
* Returns an HPSF powered text extractor for the
* document properties metadata, such as title and author.
- *
+ *
* @return an instance of POIExtractor that can extract meta-data.
*/
@Override
@@ -96,8 +96,16 @@ public abstract class POIOLE2TextExtractor extends POITextExtractor {
*
* @return the DirectoryEntry that is associated with the POIDocument of this extractor.
*/
- public DirectoryEntry getRoot()
- {
+ public DirectoryEntry getRoot() {
return document.getDirectory();
}
-}
+
+ /**
+ * Return the underlying POIDocument
+ *
+ * @return the underlying POIDocument
+ */
+ public POIDocument getDocument() {
+ return document;
+ }
+} \ No newline at end of file
diff --git a/src/java/org/apache/poi/hssf/model/InternalWorkbook.java b/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
index 7bddcdd670..1538ca5835 100644
--- a/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
+++ b/src/java/org/apache/poi/hssf/model/InternalWorkbook.java
@@ -20,16 +20,12 @@ package org.apache.poi.hssf.model;
import static org.apache.poi.util.POILogger.DEBUG;
import java.security.AccessControlException;
-import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
-import javax.crypto.SecretKey;
-
-import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.ddf.EscherBSERecord;
import org.apache.poi.ddf.EscherBoolProperty;
import org.apache.poi.ddf.EscherContainerRecord;
@@ -57,7 +53,6 @@ import org.apache.poi.hssf.record.EscherAggregate;
import org.apache.poi.hssf.record.ExtSSTRecord;
import org.apache.poi.hssf.record.ExtendedFormatRecord;
import org.apache.poi.hssf.record.ExternSheetRecord;
-import org.apache.poi.hssf.record.FilePassRecord;
import org.apache.poi.hssf.record.FileSharingRecord;
import org.apache.poi.hssf.record.FnGroupCountRecord;
import org.apache.poi.hssf.record.FontRecord;
@@ -88,13 +83,8 @@ import org.apache.poi.hssf.record.WindowProtectRecord;
import org.apache.poi.hssf.record.WriteAccessRecord;
import org.apache.poi.hssf.record.WriteProtectRecord;
import org.apache.poi.hssf.record.common.UnicodeString;
-import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.hssf.util.HSSFColor.HSSFColorPredefined;
import org.apache.poi.poifs.crypt.CryptoFunctions;
-import org.apache.poi.poifs.crypt.Decryptor;
-import org.apache.poi.poifs.crypt.EncryptionInfo;
-import org.apache.poi.poifs.crypt.EncryptionMode;
-import org.apache.poi.poifs.crypt.Encryptor;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalName;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheet;
import org.apache.poi.ss.formula.EvaluationWorkbook.ExternalSheetRange;
@@ -1048,7 +1038,7 @@ public final class InternalWorkbook {
SSTRecord lSST = null;
int sstPos = 0;
boolean wroteBoundSheets = false;
- for ( Record record : records ) {
+ for ( Record record : records.getRecords() ) {
int len = 0;
if (record instanceof SSTRecord) {
lSST = (SSTRecord)record;
@@ -1080,8 +1070,6 @@ public final class InternalWorkbook {
* Include in it ant code that modifies the workbook record stream and affects its size.
*/
public void preSerialize(){
- updateEncryptionRecord();
-
// Ensure we have enough tab IDs
// Can be a few short if new sheets were added
if(records.getTabpos() > 0) {
@@ -1092,45 +1080,11 @@ public final class InternalWorkbook {
}
}
- private void updateEncryptionRecord() {
- FilePassRecord fpr = (FilePassRecord)findFirstRecordBySid(FilePassRecord.sid);
-
- String password = Biff8EncryptionKey.getCurrentUserPassword();
- if (password == null) {
- if (fpr != null) {
- // need to remove password data
- records.remove(fpr);
- }
- } else {
- // create password record
- if (fpr == null) {
- fpr = new FilePassRecord(EncryptionMode.binaryRC4);
- records.add(1, fpr);
- }
-
- // check if the password has been changed
- EncryptionInfo ei = fpr.getEncryptionInfo();
- byte encVer[] = ei.getVerifier().getEncryptedVerifier();
- try {
- Decryptor dec = ei.getDecryptor();
- Encryptor enc = ei.getEncryptor();
- if (encVer == null || !dec.verifyPassword(password)) {
- enc.confirmPassword(password);
- } else {
- SecretKey sk = dec.getSecretKey();
- ei.getEncryptor().setSecretKey(sk);
- }
- } catch (GeneralSecurityException e) {
- throw new EncryptedDocumentException("can't validate/update encryption setting", e);
- }
- }
- }
-
public int getSize() {
int retval = 0;
SSTRecord lSST = null;
- for ( Record record : records ) {
+ for ( Record record : records.getRecords() ) {
if (record instanceof SSTRecord) {
lSST = (SSTRecord)record;
}
@@ -1803,7 +1757,7 @@ public final class InternalWorkbook {
* @return the matching record or {@code null} if it wasn't found
*/
public Record findFirstRecordBySid(short sid) {
- for (Record record : records) {
+ for (Record record : records.getRecords() ) {
if (record.getSid() == sid) {
return record;
}
@@ -1818,7 +1772,7 @@ public final class InternalWorkbook {
*/
public int findFirstRecordLocBySid(short sid) {
int index = 0;
- for (Record record : records) {
+ for (Record record : records.getRecords() ) {
if (record.getSid() == sid) {
return index;
}
@@ -1837,7 +1791,7 @@ public final class InternalWorkbook {
*/
public Record findNextRecordBySid(short sid, int pos) {
int matches = 0;
- for (Record record : records) {
+ for (Record record : records.getRecords() ) {
if (record.getSid() == sid && matches++ == pos) {
return record;
}
@@ -1901,7 +1855,7 @@ public final class InternalWorkbook {
}
// Need to find a DrawingGroupRecord that contains a EscherDggRecord
- for(Record r : records) {
+ for(Record r : records.getRecords() ) {
if (!(r instanceof DrawingGroupRecord)) {
continue;
}
@@ -2301,4 +2255,12 @@ public final class InternalWorkbook {
public boolean changeExternalReference(String oldUrl, String newUrl) {
return linkTable.changeExternalReference(oldUrl, newUrl);
}
+
+ /**
+ * Only for internal calls - code based on this is not supported ...
+ */
+ @Internal
+ public WorkbookRecordList getWorkbookRecordList() {
+ return records;
+ }
} \ No newline at end of file
diff --git a/src/java/org/apache/poi/hssf/model/LinkTable.java b/src/java/org/apache/poi/hssf/model/LinkTable.java
index f6f75692ce..cc40258382 100644
--- a/src/java/org/apache/poi/hssf/model/LinkTable.java
+++ b/src/java/org/apache/poi/hssf/model/LinkTable.java
@@ -522,9 +522,7 @@ final class LinkTable {
*/
private int findFirstRecordLocBySid(short sid) {
int index = 0;
- for (Iterator<Record> iterator = _workbookRecordList.iterator(); iterator.hasNext(); ) {
- Record record = iterator.next();
-
+ for (Record record : _workbookRecordList.getRecords()) {
if (record.getSid() == sid) {
return index;
}
@@ -639,11 +637,11 @@ final class LinkTable {
int supLinkIndex = 0;
// find the posistion of the Add-In SupBookRecord in the workbook stream,
// the created ExternalNameRecord will be appended to it
- for (Iterator<Record> iterator = _workbookRecordList.iterator(); iterator.hasNext(); supLinkIndex++) {
- Record record = iterator.next();
- if (record instanceof SupBookRecord) {
- if (((SupBookRecord) record).isAddInFunctions()) break;
+ for (Record record : _workbookRecordList.getRecords()) {
+ if (record instanceof SupBookRecord && ((SupBookRecord) record).isAddInFunctions()) {
+ break;
}
+ supLinkIndex++;
}
int numberOfNames = extBlock.getNumberOfNames();
// a new name is inserted in the end of the SupBookRecord, after the last name
diff --git a/src/java/org/apache/poi/hssf/model/WorkbookRecordList.java b/src/java/org/apache/poi/hssf/model/WorkbookRecordList.java
index e02f8ba84a..b29d7268e9 100644
--- a/src/java/org/apache/poi/hssf/model/WorkbookRecordList.java
+++ b/src/java/org/apache/poi/hssf/model/WorkbookRecordList.java
@@ -18,28 +18,37 @@
package org.apache.poi.hssf.model;
import java.util.ArrayList;
-import java.util.Iterator;
import java.util.List;
import org.apache.poi.hssf.record.Record;
-public final class WorkbookRecordList implements Iterable<Record> {
+public final class WorkbookRecordList {
private List<Record> records = new ArrayList<Record>();
- private int protpos = 0; // holds the position of the protect record.
- private int bspos = 0; // holds the position of the last bound sheet.
- private int tabpos = 0; // holds the position of the tabid record
- private int fontpos = 0; // hold the position of the last font record
- private int xfpos = 0; // hold the position of the last extended font record
- private int backuppos = 0; // holds the position of the backup record.
- private int namepos = 0; // holds the position of last name record
- private int supbookpos = 0; // holds the position of sup book
- private int externsheetPos = 0;// holds the position of the extern sheet
- private int palettepos = -1; // hold the position of the palette, if applicable
+ /** holds the position of the protect record */
+ private int protpos = 0;
+ /** holds the position of the last bound sheet */
+ private int bspos = 0;
+ /** holds the position of the tabid record */
+ private int tabpos = 0;
+ /** hold the position of the last font record */
+ private int fontpos = 0;
+ /** hold the position of the last extended font record */
+ private int xfpos = 0;
+ /** holds the position of the backup record */
+ private int backuppos = 0;
+ /** holds the position of last name record */
+ private int namepos = 0;
+ /** holds the position of sup book */
+ private int supbookpos = 0;
+ /** holds the position of the extern sheet */
+ private int externsheetPos = 0;
+ /** hold the position of the palette, if applicable */
+ private int palettepos = -1;
public void setRecords(List<Record> records) {
- this.records = records;
+ this.records = records;
}
public int size() {
@@ -52,26 +61,13 @@ public final class WorkbookRecordList implements Iterable<Record> {
public void add(int pos, Record r) {
records.add(pos, r);
- if (getProtpos() >= pos) setProtpos( protpos + 1 );
- if (getBspos() >= pos) setBspos( bspos + 1 );
- if (getTabpos() >= pos) setTabpos( tabpos + 1 );
- if (getFontpos() >= pos) setFontpos( fontpos + 1 );
- if (getXfpos() >= pos) setXfpos( xfpos + 1 );
- if (getBackuppos() >= pos) setBackuppos( backuppos + 1 );
- if (getNamepos() >= pos) setNamepos(namepos+1);
- if (getSupbookpos() >= pos) setSupbookpos(supbookpos+1);
- if ((getPalettepos() != -1) && (getPalettepos() >= pos)) setPalettepos( palettepos + 1 );
- if (getExternsheetPos() >= pos) setExternsheetPos(getExternsheetPos() + 1);
+ updateRecordPos(pos, true);
}
public List<Record> getRecords() {
return records;
}
- public Iterator<Record> iterator() {
- return records.iterator();
- }
-
/**
* Find the given record in the record list by identity and removes it
*
@@ -89,19 +85,9 @@ public final class WorkbookRecordList implements Iterable<Record> {
}
}
- public void remove( int pos )
- {
+ public void remove( int pos ) {
records.remove(pos);
- if (getProtpos() >= pos) setProtpos( protpos - 1 );
- if (getBspos() >= pos) setBspos( bspos - 1 );
- if (getTabpos() >= pos) setTabpos( tabpos - 1 );
- if (getFontpos() >= pos) setFontpos( fontpos - 1 );
- if (getXfpos() >= pos) setXfpos( xfpos - 1 );
- if (getBackuppos() >= pos) setBackuppos( backuppos - 1 );
- if (getNamepos() >= pos) setNamepos(getNamepos()-1);
- if (getSupbookpos() >= pos) setSupbookpos(getSupbookpos()-1);
- if ((getPalettepos() != -1) && (getPalettepos() >= pos)) setPalettepos( palettepos - 1 );
- if (getExternsheetPos() >= pos) setExternsheetPos( getExternsheetPos() -1);
+ updateRecordPos(pos, false);
}
public int getProtpos() {
@@ -160,7 +146,7 @@ public final class WorkbookRecordList implements Iterable<Record> {
this.palettepos = palettepos;
}
-
+
/**
* Returns the namepos.
* @return int
@@ -208,4 +194,48 @@ public final class WorkbookRecordList implements Iterable<Record> {
public void setExternsheetPos(int externsheetPos) {
this.externsheetPos = externsheetPos;
}
+
+ private void updateRecordPos(int pos, boolean add) {
+ int delta = (add) ? 1 : -1;
+ int p = getProtpos();
+ if (p >= pos) {
+ setProtpos( p + delta );
+ }
+ p = getBspos();
+ if (p >= pos) {
+ setBspos( p + delta );
+ }
+ p = getTabpos();
+ if (p >= pos) {
+ setTabpos( p + delta );
+ }
+ p = getFontpos();
+ if (p >= pos) {
+ setFontpos( p + delta );
+ }
+ p = getXfpos();
+ if (p >= pos) {
+ setXfpos( p + delta );
+ }
+ p = getBackuppos();
+ if (p >= pos) {
+ setBackuppos( p + delta );
+ }
+ p = getNamepos();
+ if (p >= pos) {
+ setNamepos(p + delta );
+ }
+ p = getSupbookpos();
+ if (p >= pos) {
+ setSupbookpos(p + delta);
+ }
+ p = getPalettepos();
+ if (p != -1 && p >= pos) {
+ setPalettepos( p + delta );
+ }
+ p = getExternsheetPos();
+ if (p >= pos) {
+ setExternsheetPos( p + delta );
+ }
+ }
}
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
index 4cfe3b9fe8..6fe2fd309d 100644
--- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java
@@ -30,6 +30,7 @@ import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.Charset;
+import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -52,6 +53,8 @@ import org.apache.poi.ddf.EscherBlipRecord;
import org.apache.poi.ddf.EscherMetafileBlip;
import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.hpsf.ClassID;
+import org.apache.poi.hpsf.DocumentSummaryInformation;
+import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hssf.OldExcelFormatException;
import org.apache.poi.hssf.model.DrawingManager2;
import org.apache.poi.hssf.model.HSSFFormulaParser;
@@ -59,6 +62,7 @@ import org.apache.poi.hssf.model.InternalSheet;
import org.apache.poi.hssf.model.InternalSheet.UnsupportedBOFType;
import org.apache.poi.hssf.model.InternalWorkbook;
import org.apache.poi.hssf.model.RecordStream;
+import org.apache.poi.hssf.model.WorkbookRecordList;
import org.apache.poi.hssf.record.AbstractEscherHolderRecord;
import org.apache.poi.hssf.record.BackupRecord;
import org.apache.poi.hssf.record.BoundSheetRecord;
@@ -77,9 +81,13 @@ import org.apache.poi.hssf.record.UnknownRecord;
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
import org.apache.poi.hssf.record.common.UnicodeString;
import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.crypt.EncryptionInfo;
+import org.apache.poi.poifs.crypt.EncryptionMode;
+import org.apache.poi.poifs.crypt.EncryptionVerifier;
import org.apache.poi.poifs.crypt.Encryptor;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DirectoryNode;
@@ -1454,13 +1462,21 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
// Write out our HPFS properties, if we have them
writeProperties(fs, excepts);
-
+
if (preserveNodes) {
// Don't write out the old Workbook, we'll be doing our new one
// If the file had an "incorrect" name for the workbook stream,
// don't write the old one as we'll use the correct name shortly
excepts.addAll(Arrays.asList(WORKBOOK_DIR_ENTRY_NAMES));
+ // summary information has been already written via writeProperties and might go in a
+ // different stream, if the file is cryptoapi encrypted
+ excepts.addAll(Arrays.asList(
+ DocumentSummaryInformation.DEFAULT_STREAM_NAME,
+ SummaryInformation.DEFAULT_STREAM_NAME,
+ getEncryptedPropertyStreamName()
+ ));
+
// Copy over all the other nodes to our new poifs
EntryUtils.copyNodes(
new FilteringDirectoryNode(getDirectory(), excepts)
@@ -1520,6 +1536,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
HSSFSheet[] sheets = getSheets();
int nSheets = sheets.length;
+ updateEncryptionInfo();
// before getting the workbook size we must tell the sheets that
// serialization is about to occur.
@@ -1566,22 +1583,14 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
@SuppressWarnings("resource")
protected void encryptBytes(byte buf[]) {
- int initialOffset = 0;
- FilePassRecord fpr = null;
- for (Record r : workbook.getRecords()) {
- initialOffset += r.getRecordSize();
- if (r instanceof FilePassRecord) {
- fpr = (FilePassRecord)r;
- break;
- }
- }
- if (fpr == null) {
+ EncryptionInfo ei = getEncryptionInfo();
+ if (ei == null) {
return;
}
-
+ Encryptor enc = ei.getEncryptor();
+ int initialOffset = 0;
LittleEndianByteArrayInputStream plain = new LittleEndianByteArrayInputStream(buf, 0); // NOSONAR
LittleEndianByteArrayOutputStream leos = new LittleEndianByteArrayOutputStream(buf, 0); // NOSONAR
- Encryptor enc = fpr.getEncryptionInfo().getEncryptor();
enc.setChunkSize(Biff8DecryptingStream.RC4_REKEYING_INTERVAL);
byte tmp[] = new byte[1024];
try {
@@ -2306,4 +2315,50 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
public SpreadsheetVersion getSpreadsheetVersion() {
return SpreadsheetVersion.EXCEL97;
}
+
+ @Override
+ public EncryptionInfo getEncryptionInfo() {
+ FilePassRecord fpr = (FilePassRecord)workbook.findFirstRecordBySid(FilePassRecord.sid);
+ return (fpr != null) ? fpr.getEncryptionInfo() : null;
+ }
+
+
+ private void updateEncryptionInfo() {
+ // make sure, that we've read all the streams ...
+ readProperties();
+ FilePassRecord fpr = (FilePassRecord)workbook.findFirstRecordBySid(FilePassRecord.sid);
+
+ String password = Biff8EncryptionKey.getCurrentUserPassword();
+ WorkbookRecordList wrl = workbook.getWorkbookRecordList();
+ if (password == null) {
+ if (fpr != null) {
+ // need to remove password data
+ wrl.remove(fpr);
+ }
+ } else {
+ // create password record
+ if (fpr == null) {
+ fpr = new FilePassRecord(EncryptionMode.cryptoAPI);
+ wrl.add(1, fpr);
+ }
+
+ // check if the password has been changed
+ EncryptionInfo ei = fpr.getEncryptionInfo();
+ EncryptionVerifier ver = ei.getVerifier();
+ byte encVer[] = ver.getEncryptedVerifier();
+ Decryptor dec = ei.getDecryptor();
+ Encryptor enc = ei.getEncryptor();
+ try {
+ if (encVer == null || !dec.verifyPassword(password)) {
+ enc.confirmPassword(password);
+ } else {
+ byte verifier[] = dec.getVerifier();
+ byte salt[] = ver.getSalt();
+ enc.confirmPassword(password, null, null, verifier, salt, null);
+ }
+ } catch (GeneralSecurityException e) {
+ throw new EncryptedDocumentException("can't validate/update encryption setting", e);
+ }
+ }
+ }
}
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
index 222b425178..f1b5e235ac 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIDecryptor.java
@@ -153,14 +153,19 @@ public class CryptoAPIDecryptor extends Decryptor implements Cloneable {
/**
* Decrypt the Document-/SummaryInformation and other optionally streams.
* Opposed to other crypto modes, cryptoapi is record based and can't be used
- * to stream-decrypt a whole file
+ * to stream-decrypt a whole file.<p>
+ *
+ * Summary entries are only encrypted within cryptoapi encrypted files.
+ * Binary RC4 encrypted files use non-encrypted/default property sets
+ *
+ * @param root root directory node of the OLE file containing the encrypted properties
+ * @param encryptedStream name of the encrypted stream -
+ * "encryption" for HSSF/HWPF, "encryptedStream" (or encryptedSummary?) for HSLF
*
* @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
*/
public POIFSFileSystem getSummaryEntries(DirectoryNode root, String encryptedStream)
throws IOException, GeneralSecurityException {
- // HSLF: encryptedStream
- // HSSF: encryption
DocumentNode es = (DocumentNode) root.getEntry(encryptedStream);
DocumentInputStream dis = root.createDocumentInputStream(es);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
diff --git a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
index 2a47922883..2dec416034 100644
--- a/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
+++ b/src/java/org/apache/poi/poifs/crypt/cryptoapi/CryptoAPIEncryptor.java
@@ -32,10 +32,6 @@ import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import org.apache.poi.EncryptedDocumentException;
-import org.apache.poi.hpsf.DocumentSummaryInformation;
-import org.apache.poi.hpsf.PropertySetFactory;
-import org.apache.poi.hpsf.SummaryInformation;
-import org.apache.poi.hpsf.WritingNotSupportedException;
import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream;
import org.apache.poi.poifs.crypt.CryptoFunctions;
import org.apache.poi.poifs.crypt.DataSpaceMapUtils;
@@ -46,6 +42,8 @@ import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIDecryptor.StreamDescriptorE
import org.apache.poi.poifs.crypt.standard.EncryptionRecord;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.Entry;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LittleEndianByteArrayOutputStream;
@@ -124,41 +122,34 @@ public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
*
* @see <a href="http://msdn.microsoft.com/en-us/library/dd943321(v=office.12).aspx">2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream</a>
*/
- public OutputStream getSummaryEntries(DirectoryNode dir)
+ public void setSummaryEntries(DirectoryNode dir, String encryptedStream, NPOIFSFileSystem entries)
throws IOException, GeneralSecurityException {
CryptoAPIDocumentOutputStream bos = new CryptoAPIDocumentOutputStream(this); // NOSONAR
byte buf[] = new byte[8];
bos.write(buf, 0, 8); // skip header
- String entryNames[] = {
- SummaryInformation.DEFAULT_STREAM_NAME,
- DocumentSummaryInformation.DEFAULT_STREAM_NAME
- };
-
List<StreamDescriptorEntry> descList = new ArrayList<StreamDescriptorEntry>();
int block = 0;
- for (String entryName : entryNames) {
- if (!dir.hasEntry(entryName)) {
+ for (Entry entry : entries.getRoot()) {
+ if (entry.isDirectoryEntry()) {
continue;
}
StreamDescriptorEntry descEntry = new StreamDescriptorEntry();
descEntry.block = block;
descEntry.streamOffset = bos.size();
- descEntry.streamName = entryName;
+ descEntry.streamName = entry.getName();
descEntry.flags = StreamDescriptorEntry.flagStream.setValue(0, 1);
descEntry.reserved2 = 0;
bos.setBlock(block);
- DocumentInputStream dis = dir.createDocumentInputStream(entryName);
+ DocumentInputStream dis = dir.createDocumentInputStream(entry);
IOUtils.copy(dis, bos);
dis.close();
descEntry.streamSize = bos.size() - descEntry.streamOffset;
descList.add(descEntry);
- dir.getEntry(entryName).delete();
-
block++;
}
@@ -197,16 +188,7 @@ public class CryptoAPIEncryptor extends Encryptor implements Cloneable {
bos.write(buf, 0, 8);
bos.setSize(savedSize);
- dir.createDocument("EncryptedSummary", new ByteArrayInputStream(bos.getBuf(), 0, savedSize));
- DocumentSummaryInformation dsi = PropertySetFactory.newDocumentSummaryInformation();
-
- try {
- dsi.write(dir, DocumentSummaryInformation.DEFAULT_STREAM_NAME);
- } catch (WritingNotSupportedException e) {
- throw new IOException(e);
- }
-
- return bos;
+ dir.createDocument(encryptedStream, new ByteArrayInputStream(bos.getBuf(), 0, savedSize));
}
protected int getKeySizeInBytes() {