diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2017-06-14 17:21:50 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2017-06-14 17:21:50 +0000 |
commit | 87591edbf3d531dfe73eeadfe691c785a8ba6ff0 (patch) | |
tree | c450869130c146e2474320a74b8e8c716bb10ae0 /src/java | |
parent | aca91b788823c3e14bc355eb0e9068c6018a7112 (diff) | |
download | poi-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')
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() { |