- 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-ffa450edef68tags/REL_3_17_BETA1
@@ -463,6 +463,7 @@ under the License. | |||
<exclude name="**/TestExtractorFactory.java"/> | |||
<exclude name="**/OutlookTextExtactor.java"/> | |||
<exclude name="**/TestEmbedOLEPackage.java"/> | |||
<exclude name="**/TestHxxFEncryption.java"/> | |||
</patternset> | |||
<!-- Prints POI's Ant usage help --> |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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 |
@@ -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 ); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -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(); |
@@ -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() { |
@@ -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<Object[]> 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(); | |||
} | |||
} |
@@ -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 { |
@@ -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<String> 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() { |
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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) |
@@ -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<String[]> 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(); | |||
} | |||
} |