From: Andreas Beeker Date: Wed, 14 Jun 2017 17:21:50 +0000 (+0000) Subject: #61162 - En-/decryption support for HWPF X-Git-Tag: REL_3_17_BETA1~35 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=87591edbf3d531dfe73eeadfe691c785a8ba6ff0;p=poi.git #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 --- diff --git a/build.xml b/build.xml index 4106a0fa0b..52ed07b3fe 100644 --- a/build.xml +++ b/build.xml @@ -463,6 +463,7 @@ under the License. + 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 readPropertySet(Class 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 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 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 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 { +public final class WorkbookRecordList { private List records = new ArrayList(); - 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 records) { - this.records = records; + this.records = records; } public int size() { @@ -52,26 +61,13 @@ public final class WorkbookRecordList implements Iterable { 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 getRecords() { return records; } - public Iterator 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 { } } - 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 { this.palettepos = palettepos; } - + /** * Returns the namepos. * @return int @@ -208,4 +194,48 @@ public final class WorkbookRecordList implements Iterable { 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.

+ * + * 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 2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream */ 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 2.3.5.4 RC4 CryptoAPI Encrypted Summary Stream */ - 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 descList = new ArrayList(); 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() { diff --git a/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java new file mode 100644 index 0000000000..c9be3d9ed2 --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/poifs/crypt/TestHxxFEncryption.java @@ -0,0 +1,187 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.poifs.crypt; + +import static org.apache.poi.POIDataSamples.getDocumentInstance; +import static org.apache.poi.POIDataSamples.getSlideShowInstance; +import static org.apache.poi.POIDataSamples.getSpreadSheetInstance; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.Collection; + +import org.apache.poi.POIDataSamples; +import org.apache.poi.POIDocument; +import org.apache.poi.POIOLE2TextExtractor; +import org.apache.poi.POITextExtractor; +import org.apache.poi.extractor.ExtractorFactory; +import org.apache.poi.hslf.usermodel.HSLFSlideShowImpl; +import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; +import org.apache.poi.openxml4j.exceptions.OpenXML4JException; +import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptionHeader; +import org.apache.poi.poifs.storage.RawDataUtil; +import org.apache.xmlbeans.XmlException; +import org.junit.AfterClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class TestHxxFEncryption { + @AfterClass + public static void clearPass() { + Biff8EncryptionKey.setCurrentUserPassword(null); + } + + @Parameter(value = 0) + public POIDataSamples sampleDir; + + @Parameter(value = 1) + public String file; + + @Parameter(value = 2) + public String password; + + @Parameter(value = 3) + public String expected; + + @Parameters(name="{1}") + public static Collection data() throws IOException { + return Arrays.asList( + // binary rc4 + new Object[]{ getDocumentInstance(), "password_tika_binaryrc4.doc", "tika", "This is an encrypted Word 2007 File." }, + // cryptoapi + new Object[]{ getDocumentInstance(), "password_password_cryptoapi.doc", "password", "This is a test" }, + // binary rc4 + new Object[]{ getSpreadSheetInstance(), "password.xls", "password", + x("H4sIAAAAAAAAAF1Uu24bMRDs/RULVwkgCUhSpHaZwkDgpHJH8fZ0G/Nx4ZI6y13yG/mRfIb9R5mlZFlIpdPtcnZmdnjPf57/vvx6+f3h6obuv3"+ + "ylbY5bEiVHe1fEpUp5pOgkrK0iabehm7FyoZi1ks8xcvHiQu8h5bLnorTlnUvkJ/YPOHKsLVInAqCs91KakuaxLq4w3g00SgCo9Xou1UnCmSBe"+ + "MhpRY6qHmXVFteQfQJ5yUaaOw4qXwgPVjPGAqhNH5bBHAfTmwqqoSkLdFT/J3nC0eZBRk7yiu5s7yoU+r+9l3tDtm5A3jgt6AQxNOY2ya+U4sK"+ + "XZ+YczbpfSVVuzFOuunKraqIVD2ND3yVXauT3TNthR/O3IJAM7gzTOGeIcXZvj14ahotW8wSognlMu0Yyp/Fi7O6s+CK6haUUjtPCji7MVcgqH"+ + "jh+42tqeqPDMroJ/lBAE4AZbJbJu6Fu35ej42Tw9mYeTwVXoBKJiPeFV94q2rZJAyNEPo/qOdWYLBpq3B2JX8GDZeJ14mZf3tOQWBmpd9yQ7kI"+ + "DCY/jmkj1oGOicFy62r9vutC5uJsVEMFgmAXXfYcC6BRBKNHCybALFJolnrDcPXNLl+K60Vctt09YZT7YgbeOICGJ/ZgC2JztOnm1JhX3eJXni"+ + "U5Bqhezzlu334vD/Ajr3yDGXw5G9IZ6aLmLfQafY42N3J7cjj1LaXOHihSrcC5ThmuYIB5FX5AU8tKlnNG9Dn1EnsdD4KcnPhsSNPRiXtz461b"+ + "VZw8Pm6vn0afh4fvr0D5P/+cMuBAAA") }, + // cryptoapi + new Object[]{ getSpreadSheetInstance(), "35897-type4.xls", "freedom", "Sheet1\nhello there!" }, + // cryptoapi (PPT only supports cryptoapi...) + new Object[]{ getSlideShowInstance(), "cryptoapi-proc2356.ppt", "crypto", "Dominic Salemno" } + ); + } + + private static String x(String base64) throws IOException { + return new String(RawDataUtil.decompress(base64), Charset.forName("UTF-8")); + } + + @Test + public void extract() throws IOException, OpenXML4JException, XmlException { + Biff8EncryptionKey.setCurrentUserPassword(password); + File f = sampleDir.getFile(file); + POITextExtractor te = ExtractorFactory.createExtractor(f); + String actual = te.getText().trim(); + assertEquals(expected, actual); + te.close(); + } + + @Test + public void changePassword() throws IOException, OpenXML4JException, XmlException { + newPassword("test"); + } + + @Test + public void removePassword() throws IOException, OpenXML4JException, XmlException { + newPassword(null); + } + + public void newPassword(String newPass) throws IOException, OpenXML4JException, XmlException { + Biff8EncryptionKey.setCurrentUserPassword(password); + File f = sampleDir.getFile(file); + POIOLE2TextExtractor te1 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(f); + Biff8EncryptionKey.setCurrentUserPassword(newPass); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + POIDocument doc = te1.getDocument(); + doc.write(bos); + doc.close(); + te1.close(); + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + POITextExtractor te2 = ExtractorFactory.createExtractor(bis); + String actual = te2.getText().trim(); + assertEquals(expected, actual); + te2.close(); + } + + /** changing the encryption mode and key size in poor mans style - see comments below */ + @Test + public void changeEncryption() throws IOException, OpenXML4JException, XmlException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + Biff8EncryptionKey.setCurrentUserPassword(password); + File f = sampleDir.getFile(file); + POIOLE2TextExtractor te1 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(f); + // first remove encryption + Biff8EncryptionKey.setCurrentUserPassword(null); + POIDocument doc = te1.getDocument(); + doc.write(bos); + doc.close(); + te1.close(); + // then use default setting, which is cryptoapi + String newPass = "newPass"; + POIOLE2TextExtractor te2 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); + Biff8EncryptionKey.setCurrentUserPassword(newPass); + doc = te2.getDocument(); + bos.reset(); + doc.write(bos); + doc.close(); + te2.close(); + // and finally update cryptoapi setting + POIOLE2TextExtractor te3 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); + doc = te3.getDocument(); + // need to cache data (i.e. read all data) before changing the key size + if (doc instanceof HSLFSlideShowImpl) { + HSLFSlideShowImpl hss = (HSLFSlideShowImpl)doc; + hss.getPictureData(); + hss.getDocumentSummaryInformation(); + } + EncryptionInfo ei = doc.getEncryptionInfo(); + assertNotNull(ei); + assertTrue(ei.getHeader() instanceof CryptoAPIEncryptionHeader); + assertEquals(0x28, ((CryptoAPIEncryptionHeader)ei.getHeader()).getKeySize()); + ((CryptoAPIEncryptionHeader)ei.getHeader()).setKeySize(0x78); + bos.reset(); + doc.write(bos); + doc.close(); + te3.close(); + // check the setting + POIOLE2TextExtractor te4 = (POIOLE2TextExtractor)ExtractorFactory.createExtractor(new ByteArrayInputStream(bos.toByteArray())); + doc = te4.getDocument(); + ei = doc.getEncryptionInfo(); + assertNotNull(ei); + assertTrue(ei.getHeader() instanceof CryptoAPIEncryptionHeader); + assertEquals(0x78, ((CryptoAPIEncryptionHeader)ei.getHeader()).getKeySize()); + doc.close(); + te4.close(); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java index 1251904b21..83925bc540 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowEncrypted.java @@ -40,8 +40,7 @@ import org.apache.poi.poifs.crypt.ChunkedCipherInputStream; 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.cryptoapi.CryptoAPIDecryptor; -import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor; +import org.apache.poi.poifs.crypt.Encryptor; import org.apache.poi.util.BitField; import org.apache.poi.util.IOUtils; import org.apache.poi.util.Internal; @@ -55,8 +54,7 @@ import org.apache.poi.util.LittleEndianByteArrayOutputStream; @Internal public class HSLFSlideShowEncrypted implements Closeable { DocumentEncryptionAtom dea; - CryptoAPIEncryptor enc = null; - CryptoAPIDecryptor dec = null; + EncryptionInfo _encryptionInfo; // Cipher cipher = null; ChunkedCipherOutputStream cyos = null; @@ -120,11 +118,15 @@ public class HSLFSlideShowEncrypted implements Closeable { } assert(r instanceof DocumentEncryptionAtom); this.dea = (DocumentEncryptionAtom)r; - decryptInit(); String pass = Biff8EncryptionKey.getCurrentUserPassword(); - if(!dec.verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) { - throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()"); + EncryptionInfo ei = getEncryptionInfo(); + try { + if(!ei.getDecryptor().verifyPassword(pass != null ? pass : Decryptor.DEFAULT_PASSWORD)) { + throw new EncryptedPowerPointFileException("PowerPoint file is encrypted. The correct password needs to be set via Biff8EncryptionKey.setCurrentUserPassword()"); + } + } catch (GeneralSecurityException e) { + throw new EncryptedPowerPointFileException(e); } } @@ -132,24 +134,10 @@ public class HSLFSlideShowEncrypted implements Closeable { return dea; } - protected void decryptInit() { - if (dec != null) { - return; - } - EncryptionInfo ei = dea.getEncryptionInfo(); - dec = (CryptoAPIDecryptor)ei.getDecryptor(); - } - - protected void encryptInit() { - if (enc != null) { - return; - } - EncryptionInfo ei = dea.getEncryptionInfo(); - enc = (CryptoAPIEncryptor)ei.getEncryptor(); + protected EncryptionInfo getEncryptionInfo() { + return (dea != null) ? dea.getEncryptionInfo() : null; } - - protected OutputStream encryptRecord(OutputStream plainStream, int persistId, Record record) { boolean isPlain = (dea == null || record instanceof UserEditAtom @@ -166,9 +154,8 @@ public class HSLFSlideShowEncrypted implements Closeable { return plainStream; } - encryptInit(); - if (cyos == null) { + Encryptor enc = getEncryptionInfo().getEncryptor(); enc.setChunkSize(-1); cyos = enc.getDataStream(plainStream, 0); } @@ -190,12 +177,12 @@ public class HSLFSlideShowEncrypted implements Closeable { return; } - decryptInit(); + Decryptor dec = getEncryptionInfo().getDecryptor(); dec.setChunkSize(-1); LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(docstream, offset); // NOSONAR ChunkedCipherInputStream ccis = null; try { - ccis = dec.getDataStream(lei, docstream.length-offset, 0); + ccis = (ChunkedCipherInputStream)dec.getDataStream(lei, docstream.length-offset, 0); ccis.initCipherForBlock(persistId); // decrypt header and read length to be decrypted @@ -217,7 +204,8 @@ public class HSLFSlideShowEncrypted implements Closeable { // when reading the picture elements, each time a segment is read, the cipher needs // to be reset (usually done when calling Cipher.doFinal) LittleEndianByteArrayInputStream lei = new LittleEndianByteArrayInputStream(pictstream, offset); - ChunkedCipherInputStream ccis = dec.getDataStream(lei, len, 0); + Decryptor dec = getEncryptionInfo().getDecryptor(); + ChunkedCipherInputStream ccis = (ChunkedCipherInputStream)dec.getDataStream(lei, len, 0); readFully(ccis, pictstream, offset, len); ccis.close(); lei.close(); @@ -228,8 +216,6 @@ public class HSLFSlideShowEncrypted implements Closeable { return; } - decryptInit(); - try { // decrypt header and read length to be decrypted decryptPicBytes(pictstream, offset, 8); @@ -302,12 +288,11 @@ public class HSLFSlideShowEncrypted implements Closeable { return; } - encryptInit(); - LittleEndianByteArrayOutputStream los = new LittleEndianByteArrayOutputStream(pictstream, offset); // NOSONAR ChunkedCipherOutputStream ccos = null; try { + Encryptor enc = getEncryptionInfo().getEncryptor(); enc.setChunkSize(-1); ccos = enc.getDataStream(los, 0); int recInst = fieldRecInst.getValue(LittleEndian.getUShort(pictstream, offset)); @@ -396,11 +381,10 @@ public class HSLFSlideShowEncrypted implements Closeable { // create password record if (dea == null) { dea = new DocumentEncryptionAtom(); - enc = null; } - encryptInit(); EncryptionInfo ei = dea.getEncryptionInfo(); byte salt[] = ei.getVerifier().getSalt(); + Encryptor enc = getEncryptionInfo().getEncryptor(); if (salt == null) { enc.confirmPassword(password); } else { diff --git a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java index 02e3d3e0cc..2b758cff3d 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java +++ b/src/scratchpad/src/org/apache/poi/hslf/usermodel/HSLFSlideShowImpl.java @@ -24,7 +24,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -34,7 +33,6 @@ import java.util.NavigableMap; import java.util.TreeMap; import org.apache.poi.POIDocument; -import org.apache.poi.hpsf.PropertySet; import org.apache.poi.hslf.exceptions.CorruptPowerPointFileException; import org.apache.poi.hslf.exceptions.HSLFException; import org.apache.poi.hslf.record.CurrentUserAtom; @@ -46,7 +44,7 @@ import org.apache.poi.hslf.record.PositionDependentRecord; import org.apache.poi.hslf.record.Record; import org.apache.poi.hslf.record.RecordTypes; import org.apache.poi.hslf.record.UserEditAtom; -import org.apache.poi.poifs.crypt.cryptoapi.CryptoAPIEncryptor; +import org.apache.poi.poifs.crypt.EncryptionInfo; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentEntry; import org.apache.poi.poifs.filesystem.DocumentInputStream; @@ -731,44 +729,15 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable { } } - /** - * For a given named property entry, either return it or null if - * if it wasn't found - * - * @param setName The property to read - * @return The value of the given property or null if it wasn't found. - */ - @Override - protected PropertySet getPropertySet(String setName) { - DocumentEncryptionAtom dea = getDocumentEncryptionAtom(); - return (dea == null) - ? super.getPropertySet(setName) - : super.getPropertySet(setName, dea.getEncryptionInfo()); - } - /** - * Writes out the standard Documment Information Properties (HPSF) - * - * @param outFS the POIFSFileSystem to write the properties into - * @param writtenEntries a list of POIFS entries to add the property names too - * @throws IOException if an error when writing to the - * {@link POIFSFileSystem} occurs - */ @Override - protected void writeProperties(NPOIFSFileSystem outFS, List writtenEntries) throws IOException { - super.writeProperties(outFS, writtenEntries); + public EncryptionInfo getEncryptionInfo() throws IOException { DocumentEncryptionAtom dea = getDocumentEncryptionAtom(); - if (dea != null) { - CryptoAPIEncryptor enc = (CryptoAPIEncryptor) dea.getEncryptionInfo().getEncryptor(); - try { - enc.getSummaryEntries(outFS.getRoot()); // ignore OutputStream - } catch (IOException e) { - throw e; - } catch (GeneralSecurityException e) { - throw new IOException(e); - } - } + return (dea != null) ? dea.getEncryptionInfo() : null; } + + + /* ******************* adding methods follow ********************* */ @@ -893,6 +862,10 @@ public final class HSLFSlideShowImpl extends POIDocument implements Closeable { } } + @Override + protected String getEncryptedPropertyStreamName() { + return "EncryptedSummary"; + } private static class BufAccessBAOS extends ByteArrayOutputStream { public byte[] getBuf() { diff --git a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java index 25e83b95ef..1369841acf 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocument.java @@ -23,6 +23,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.security.GeneralSecurityException; import org.apache.poi.hpsf.DocumentSummaryInformation; import org.apache.poi.hpsf.SummaryInformation; @@ -61,13 +62,18 @@ import org.apache.poi.hwpf.usermodel.OfficeDrawings; import org.apache.poi.hwpf.usermodel.OfficeDrawingsImpl; import org.apache.poi.hwpf.usermodel.Range; import org.apache.poi.poifs.common.POIFSConstants; +import org.apache.poi.poifs.crypt.ChunkedCipherOutputStream; 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.poifs.crypt.standard.EncryptionRecord; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.Entry; import org.apache.poi.poifs.filesystem.EntryUtils; import org.apache.poi.poifs.filesystem.NPOIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.util.Internal; +import org.apache.poi.util.LittleEndianByteArrayOutputStream; /** * @@ -322,6 +328,7 @@ public final class HWPFDocument extends HWPFDocumentCore { _fields = new FieldsImpl(_fieldsTables); } + @Override @Internal public TextPieceTable getTextTable() { @@ -340,6 +347,7 @@ public final class HWPFDocument extends HWPFDocumentCore { return _dop; } + @Override public Range getOverallRange() { return new Range(0, _text.length(), this); } @@ -348,6 +356,7 @@ public final class HWPFDocument extends HWPFDocumentCore { * Returns the range which covers the whole of the document, but excludes * any headers and footers. */ + @Override public Range getRange() { // // First up, trigger a full-recalculate @@ -394,8 +403,9 @@ public final class HWPFDocument extends HWPFDocumentCore { { int length = getFileInformationBlock() .getSubdocumentTextStreamLength( previos ); - if ( subdocument == previos ) + if ( subdocument == previos ) { return new Range( startCp, startCp + length, this ); + } startCp += length; } throw new UnsupportedOperationException( @@ -603,34 +613,52 @@ public final class HWPFDocument extends HWPFDocumentCore { * @throws IOException If there is an unexpected IOException from the passed * in OutputStream. */ + @Override public void write(OutputStream out) throws IOException { NPOIFSFileSystem pfs = new NPOIFSFileSystem(); write(pfs, true); pfs.writeFilesystem( out ); } - private void write(NPOIFSFileSystem pfs, boolean copyOtherEntries) throws IOException { - // initialize our streams for writing. - HWPFFileSystem docSys = new HWPFFileSystem(); - ByteArrayOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT); - ByteArrayOutputStream tableStream = docSys.getStream(STREAM_TABLE_1); - //HWPFOutputStream dataStream = docSys.getStream("Data"); - int tableOffset = 0; - // FileInformationBlock fib = (FileInformationBlock)_fib.clone(); + private void write(NPOIFSFileSystem pfs, boolean copyOtherEntries) throws IOException { // clear the offsets and sizes in our FileInformationBlock. _fib.clearOffsetsSizes(); // determine the FileInformationBLock size int fibSize = _fib.getSize(); - fibSize += POIFSConstants.SMALLER_BIG_BLOCK_SIZE - - (fibSize % POIFSConstants.SMALLER_BIG_BLOCK_SIZE); + fibSize += POIFSConstants.SMALLER_BIG_BLOCK_SIZE - (fibSize % POIFSConstants.SMALLER_BIG_BLOCK_SIZE); + + // initialize our streams for writing. + HWPFFileSystem docSys = new HWPFFileSystem(); + ByteArrayOutputStream wordDocumentStream = docSys.getStream(STREAM_WORD_DOCUMENT); + ByteArrayOutputStream tableStream = docSys.getStream(STREAM_TABLE_1); // preserve space for the FileInformationBlock because we will be writing // it after we write everything else. byte[] placeHolder = new byte[fibSize]; wordDocumentStream.write(placeHolder); int mainOffset = wordDocumentStream.size(); + int tableOffset = 0; + // write out EncryptionInfo + updateEncryptionInfo(); + EncryptionInfo ei = getEncryptionInfo(); + if (ei != null) { + byte buf[] = new byte[1000]; + LittleEndianByteArrayOutputStream leos = new LittleEndianByteArrayOutputStream(buf, 0); + leos.writeShort(ei.getVersionMajor()); + leos.writeShort(ei.getVersionMinor()); + if (ei.getEncryptionMode() == EncryptionMode.cryptoAPI) { + leos.writeInt(ei.getEncryptionFlags()); + } + + ((EncryptionRecord)ei.getHeader()).write(leos); + ((EncryptionRecord)ei.getVerifier()).write(leos); + tableStream.write(buf, 0, leos.getWriteIndex()); + tableOffset += leos.getWriteIndex(); + _fib.getFibBase().setLKey(tableOffset); + } + // write out the StyleSheet. _fib.setFcStshf(tableOffset); _ss.writeTo(tableStream); @@ -869,13 +897,7 @@ public final class HWPFDocument extends HWPFDocumentCore { _fib.setCbMac(wordDocumentStream.size()); // make sure that the table, doc and data streams use big blocks. - byte[] mainBuf = wordDocumentStream.toByteArray(); - if (mainBuf.length < 4096) - { - byte[] tempBuf = new byte[4096]; - System.arraycopy(mainBuf, 0, tempBuf, 0, mainBuf.length); - mainBuf = tempBuf; - } + byte[] mainBuf = fillUp4096(wordDocumentStream); // Table1 stream will be used _fib.getFibBase().setFWhichTblStm( true ); @@ -884,98 +906,52 @@ public final class HWPFDocument extends HWPFDocumentCore { //_fib.serialize(mainBuf, 0); _fib.writeTo(mainBuf, tableStream); - byte[] tableBuf = tableStream.toByteArray(); - if (tableBuf.length < 4096) - { - byte[] tempBuf = new byte[4096]; - System.arraycopy(tableBuf, 0, tempBuf, 0, tableBuf.length); - tableBuf = tempBuf; - } - - byte[] dataBuf = _dataStream; - if (dataBuf == null) - { - dataBuf = new byte[4096]; - } - if (dataBuf.length < 4096) - { - byte[] tempBuf = new byte[4096]; - System.arraycopy(dataBuf, 0, tempBuf, 0, dataBuf.length); - dataBuf = tempBuf; + byte[] tableBuf = fillUp4096(tableStream); + byte[] dataBuf = fillUp4096(_dataStream); + + // Create a new document - ignoring the order of the old entries + if (ei == null) { + write(pfs, mainBuf, STREAM_WORD_DOCUMENT); + write(pfs, tableBuf, STREAM_TABLE_1); + write(pfs, dataBuf, STREAM_DATA); + } else { + ByteArrayOutputStream bos = new ByteArrayOutputStream(100000); + encryptBytes(mainBuf, FIB_BASE_LEN, bos); + write(pfs, bos.toByteArray(), STREAM_WORD_DOCUMENT); + bos.reset(); + encryptBytes(tableBuf, _fib.getFibBase().getLKey(), bos); + write(pfs, bos.toByteArray(), STREAM_TABLE_1); + bos.reset(); + encryptBytes(dataBuf, 0, bos); + write(pfs, bos.toByteArray(), STREAM_DATA); + bos.reset(); } - // Create a new document preserving order of entries / Update existing - boolean docWritten = false; - boolean dataWritten = false; - boolean objectPoolWritten = false; - boolean tableWritten = false; - boolean propertiesWritten = false; - for (Entry entry : getDirectory()) { - if ( entry.getName().equals( STREAM_WORD_DOCUMENT ) ) - { - if ( !docWritten ) - { - write(pfs, mainBuf, STREAM_WORD_DOCUMENT); - docWritten = true; - } - } - else if ( entry.getName().equals( STREAM_OBJECT_POOL ) ) - { - if ( !objectPoolWritten ) - { - if ( copyOtherEntries ) { - _objectPool.writeTo( pfs.getRoot() ); - } else { - // Object pool is already there, no need to change/copy - } - objectPoolWritten = true; - } - } - else if ( entry.getName().equals( STREAM_TABLE_0 ) - || entry.getName().equals( STREAM_TABLE_1 ) ) - { - if ( !tableWritten ) - { - write(pfs, tableBuf, STREAM_TABLE_1); - tableWritten = true; - } - } - else if ( entry.getName().equals( - SummaryInformation.DEFAULT_STREAM_NAME ) - || entry.getName().equals( - DocumentSummaryInformation.DEFAULT_STREAM_NAME ) ) - { - if ( !propertiesWritten ) - { - writeProperties( pfs ); - propertiesWritten = true; - } - } - else if ( entry.getName().equals( STREAM_DATA ) ) - { - if ( !dataWritten ) - { - write(pfs, dataBuf, STREAM_DATA); - dataWritten = true; + writeProperties( pfs ); + + if ( copyOtherEntries && ei == null ) { + // For encrypted files: + // The ObjectPool storage MUST NOT be present and if the file contains OLE objects, the storage + // objects for the OLE objects MUST be stored in the Data stream as specified in sprmCPicLocation. + DirectoryNode newRoot = pfs.getRoot(); + _objectPool.writeTo( newRoot ); + + for (Entry entry : getDirectory()) { + String entryName = entry.getName(); + if ( !( + STREAM_WORD_DOCUMENT.equals(entryName) || + STREAM_TABLE_0.equals(entryName) || + STREAM_TABLE_1.equals(entryName) || + STREAM_DATA.equals(entryName) || + STREAM_OBJECT_POOL.equals(entryName) || + SummaryInformation.DEFAULT_STREAM_NAME.equals(entryName) || + DocumentSummaryInformation.DEFAULT_STREAM_NAME.equals(entryName) + ) ) { + EntryUtils.copyNodeRecursively( entry, newRoot ); } } - else if ( copyOtherEntries ) - { - EntryUtils.copyNodeRecursively( entry, pfs.getRoot() ); - } } - if ( !docWritten ) - write(pfs, mainBuf, STREAM_WORD_DOCUMENT); - if ( !tableWritten ) - write(pfs, tableBuf, STREAM_TABLE_1); - if ( !propertiesWritten ) - writeProperties( pfs ); - if ( !dataWritten ) - write(pfs, dataBuf, STREAM_DATA); - if ( !objectPoolWritten && copyOtherEntries ) - _objectPool.writeTo( pfs.getRoot() ); - /* * since we updated all references in FIB and etc, using new arrays to * access data @@ -984,6 +960,43 @@ public final class HWPFDocument extends HWPFDocumentCore { this._tableStream = tableStream.toByteArray(); this._dataStream = dataBuf; } + + private void encryptBytes(byte[] plain, int encryptOffset, OutputStream bos) throws IOException { + try { + EncryptionInfo ei = getEncryptionInfo(); + Encryptor enc = ei.getEncryptor(); + enc.setChunkSize(RC4_REKEYING_INTERVAL); + ChunkedCipherOutputStream os = enc.getDataStream(bos, 0); + if (encryptOffset > 0) { + os.writePlain(plain, 0, encryptOffset); + } + os.write(plain, encryptOffset, plain.length-encryptOffset); + os.close(); + } catch (GeneralSecurityException e) { + throw new IOException(e); + } + } + + private static byte[] fillUp4096(byte[] buf) { + if (buf == null) { + return new byte[4096]; + } else if (buf.length < 4096) { + ByteArrayOutputStream bos = new ByteArrayOutputStream(4096); + bos.write(buf, 0, buf.length); + return fillUp4096(bos); + } else { + return buf; + } + } + + private static byte[] fillUp4096(ByteArrayOutputStream bos) { + int fillSize = 4096 - bos.size(); + if (fillSize > 0) { + bos.write(new byte[fillSize], 0, fillSize); + } + return bos.toByteArray(); + } + private static void write(NPOIFSFileSystem pfs, byte[] data, String name) throws IOException { pfs.createOrUpdateDocument(new ByteArrayInputStream(data), name); } @@ -1009,9 +1022,8 @@ public final class HWPFDocument extends HWPFDocumentCore { list.getLFOData() ); } - public void delete(int start, int length) - { - Range r = new Range(start, start + length, this); - r.delete(); - } -} + public void delete(int start, int length) { + Range r = new Range(start, start + length, this); + r.delete(); + } +} \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java index c52abc101e..42639decbb 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/HWPFDocumentCore.java @@ -18,7 +18,6 @@ package org.apache.poi.hwpf; import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PushbackInputStream; @@ -26,7 +25,6 @@ import java.security.GeneralSecurityException; import org.apache.poi.EncryptedDocumentException; import org.apache.poi.POIDocument; -import org.apache.poi.hpsf.PropertySet; import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; import org.apache.poi.hwpf.model.CHPBinTable; import org.apache.poi.hwpf.model.FibBase; @@ -44,6 +42,7 @@ import org.apache.poi.poifs.crypt.ChunkedCipherInputStream; 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.poifs.filesystem.DirectoryEntry; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentEntry; @@ -67,7 +66,17 @@ public abstract class HWPFDocumentCore extends POIDocument { protected static final String STREAM_TABLE_0 = "0Table"; protected static final String STREAM_TABLE_1 = "1Table"; - private static final int FIB_BASE_LEN = 68; + /** + * Size of the not encrypted part of the FIB + */ + protected static final int FIB_BASE_LEN = 68; + + /** + * [MS-DOC] 2.2.6.2/3 Office Binary Document ... Encryption: + * "... The block number MUST be set to zero at the beginning of the stream and + * MUST be incremented at each 512 byte boundary. ..." + */ + protected static final int RC4_REKEYING_INTERVAL = 512; /** Holds OLE2 objects */ protected ObjectPoolImpl _objectPool; @@ -171,35 +180,85 @@ public abstract class HWPFDocumentCore extends POIDocument { } _objectPool = new ObjectPoolImpl(objectPoolEntry); } + /** + * Returns the range which covers the whole of the document, but excludes + * any headers and footers. + */ + public abstract Range getRange(); /** - * For a given named property entry, either return it or null if - * if it wasn't found - * - * @param setName The property to read - * @return The value of the given property or null if it wasn't found. + * Returns the range that covers all text in the file, including main text, + * footnotes, headers and comments */ - @Override - protected PropertySet getPropertySet(String setName) { - EncryptionInfo ei; - try { - ei = getEncryptionInfo(); - } catch (IOException e) { - throw new RuntimeException(e); - } - return (ei == null) - ? super.getPropertySet(setName) - : super.getPropertySet(setName, ei); + public abstract Range getOverallRange(); + + /** + * Returns document text, i.e. text information from all text pieces, + * including OLE descriptions and field codes + */ + public String getDocumentText() { + return getText().toString(); + } + + /** + * Internal method to access document text + */ + @Internal + public abstract StringBuilder getText(); + + public CHPBinTable getCharacterTable() { + return _cbt; } - protected EncryptionInfo getEncryptionInfo() throws IOException { + public PAPBinTable getParagraphTable() { + return _pbt; + } + + public SectionTable getSectionTable() { + return _st; + } + + public StyleSheet getStyleSheet() { + return _ss; + } + + public ListTables getListTables() { + return _lt; + } + + public FontTable getFontTable() { + return _ft; + } + + public FileInformationBlock getFileInformationBlock() { + return _fib; + } + + public ObjectsPool getObjectsPool() { + return _objectPool; + } + + public abstract TextPieceTable getTextTable(); + + @Internal + public byte[] getMainStream() { + return _mainStream; + } + + @Override + public EncryptionInfo getEncryptionInfo() throws IOException { if (_encryptionInfo != null) { return _encryptionInfo; } // Create our FIB, and check for the doc being encrypted - byte[] fibBaseBytes = (_mainStream != null) ? _mainStream : getDocumentEntryBytes(STREAM_WORD_DOCUMENT, -1, FIB_BASE_LEN); - FibBase fibBase = new FibBase( fibBaseBytes, 0 ); + FibBase fibBase; + if (_fib != null && _fib.getFibBase() != null) { + fibBase = _fib.getFibBase(); + } else { + byte[] fibBaseBytes = (_mainStream != null) ? _mainStream : getDocumentEntryBytes(STREAM_WORD_DOCUMENT, -1, FIB_BASE_LEN); + fibBase = new FibBase( fibBaseBytes, 0 ); + } if (!fibBase.isFEncrypted()) { return null; } @@ -210,7 +269,7 @@ public abstract class HWPFDocumentCore extends POIDocument { EncryptionMode em = fibBase.isFObfuscated() ? EncryptionMode.xor : null; EncryptionInfo ei = new EncryptionInfo(leis, em); Decryptor dec = ei.getDecryptor(); - dec.setChunkSize(512); + dec.setChunkSize(RC4_REKEYING_INTERVAL); try { String pass = Biff8EncryptionKey.getCurrentUserPassword(); if (pass == null) { @@ -226,6 +285,35 @@ public abstract class HWPFDocumentCore extends POIDocument { return ei; } + protected void updateEncryptionInfo() { + // make sure, that we've read all the streams ... + readProperties(); + // now check for the password + String password = Biff8EncryptionKey.getCurrentUserPassword(); + FibBase fBase = _fib.getFibBase(); + if (password == null) { + fBase.setLKey(0); + fBase.setFEncrypted(false); + fBase.setFObfuscated(false); + _encryptionInfo = null; + } else { + // create password record + if (_encryptionInfo == null) { + _encryptionInfo = new EncryptionInfo(EncryptionMode.cryptoAPI); + fBase.setFEncrypted(true); + fBase.setFObfuscated(false); + } + Encryptor enc = _encryptionInfo.getEncryptor(); + byte salt[] = _encryptionInfo.getVerifier().getSalt(); + if (salt == null) { + enc.confirmPassword(password); + } else { + byte verifier[] = _encryptionInfo.getDecryptor().getVerifier(); + enc.confirmPassword(password, null, null, verifier, salt, null); + } + } + } + /** * Reads OLE Stream into byte array - if an {@link EncryptionInfo} is available, * decrypt the bytes starting at encryptionOffset. If encryptionOffset = -1, then do not try @@ -273,70 +361,4 @@ public abstract class HWPFDocumentCore extends POIDocument { IOUtils.closeQuietly(dis); } } - - - /** - * Returns the range which covers the whole of the document, but excludes - * any headers and footers. - */ - public abstract Range getRange(); - - /** - * Returns the range that covers all text in the file, including main text, - * footnotes, headers and comments - */ - public abstract Range getOverallRange(); - - /** - * Returns document text, i.e. text information from all text pieces, - * including OLE descriptions and field codes - */ - public String getDocumentText() { - return getText().toString(); - } - - /** - * Internal method to access document text - */ - @Internal - public abstract StringBuilder getText(); - - public CHPBinTable getCharacterTable() { - return _cbt; - } - - public PAPBinTable getParagraphTable() { - return _pbt; - } - - public SectionTable getSectionTable() { - return _st; - } - - public StyleSheet getStyleSheet() { - return _ss; - } - - public ListTables getListTables() { - return _lt; - } - - public FontTable getFontTable() { - return _ft; - } - - public FileInformationBlock getFileInformationBlock() { - return _fib; - } - - public ObjectsPool getObjectsPool() { - return _objectPool; - } - - public abstract TextPieceTable getTextTable(); - - @Internal - public byte[] getMainStream() { - return _mainStream; - } } \ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/io/HWPFFileSystem.java b/src/scratchpad/src/org/apache/poi/hwpf/model/io/HWPFFileSystem.java index 4ab383f9b3..f8aa03a1de 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/io/HWPFFileSystem.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/io/HWPFFileSystem.java @@ -31,9 +31,9 @@ public final class HWPFFileSystem public HWPFFileSystem() { - _streams.put("WordDocument", new ByteArrayOutputStream()); - _streams.put("1Table", new ByteArrayOutputStream()); - _streams.put("Data", new ByteArrayOutputStream()); + _streams.put("WordDocument", new ByteArrayOutputStream(100000)); + _streams.put("1Table", new ByteArrayOutputStream(100000)); + _streams.put("Data", new ByteArrayOutputStream(100000)); } public ByteArrayOutputStream getStream(String name) diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java b/src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java deleted file mode 100644 index 875fb9ec7b..0000000000 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/HWPFTestEncryption.java +++ /dev/null @@ -1,69 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hwpf; - -import static org.junit.Assert.assertEquals; - -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; - -import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey; -import org.apache.poi.hwpf.extractor.WordExtractor; -import org.junit.AfterClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -@RunWith(Parameterized.class) -public class HWPFTestEncryption { - @AfterClass - public static void clearPass() { - Biff8EncryptionKey.setCurrentUserPassword(null); - } - - @Parameter(value = 0) - public String file; - - @Parameter(value = 1) - public String password; - - @Parameter(value = 2) - public String expected; - - @Parameters(name="{0}") - public static Collection data() { - return Arrays.asList( - new String[]{ "password_tika_binaryrc4.doc", "tika", "This is an encrypted Word 2007 File." }, - new String[]{ "password_password_cryptoapi.doc", "password", "This is a test" } - ); - } - - @Test - public void extract() throws IOException { - Biff8EncryptionKey.setCurrentUserPassword(password); - HWPFDocument docD = HWPFTestDataSamples.openSampleFile(file); - WordExtractor we = new WordExtractor(docD); - String actual = we.getText().trim(); - assertEquals(expected, actual); - we.close(); - docD.close(); - } -}