mirror of https://github.com/apache/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
This commit is contained in:
parent
aca91b7888
commit
87591edbf3
|
@ -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 EncryptionInfo getEncryptionInfo() {
|
||||
return (dea != null) ? dea.getEncryptionInfo() : null;
|
||||
}
|
||||
|
||||
protected void encryptInit() {
|
||||
if (enc != null) {
|
||||
return;
|
||||
}
|
||||
EncryptionInfo ei = dea.getEncryptionInfo();
|
||||
enc = (CryptoAPIEncryptor)ei.getEncryptor();
|
||||
}
|
||||
|
||||
|
||||
|
||||
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) {
|
||||
public EncryptionInfo getEncryptionInfo() throws IOException {
|
||||
DocumentEncryptionAtom dea = getDocumentEncryptionAtom();
|
||||
return (dea == null)
|
||||
? super.getPropertySet(setName)
|
||||
: super.getPropertySet(setName, dea.getEncryptionInfo());
|
||||
return (dea != null) ? dea.getEncryptionInfo() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* ******************* 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,97 +906,51 @@ 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
else if ( copyOtherEntries )
|
||||
{
|
||||
EntryUtils.copyNodeRecursively( entry, pfs.getRoot() );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !docWritten )
|
||||
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);
|
||||
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() );
|
||||
} 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();
|
||||
}
|
||||
|
||||
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* since we updated all references in FIB and etc, using new arrays to
|
||||
|
@ -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,110 +180,6 @@ public abstract class HWPFDocumentCore extends POIDocument {
|
|||
}
|
||||
_objectPool = new ObjectPoolImpl(objectPoolEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
EncryptionInfo ei;
|
||||
try {
|
||||
ei = getEncryptionInfo();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return (ei == null)
|
||||
? super.getPropertySet(setName)
|
||||
: super.getPropertySet(setName, ei);
|
||||
}
|
||||
|
||||
protected 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 );
|
||||
if (!fibBase.isFEncrypted()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String tableStrmName = fibBase.isFWhichTblStm() ? STREAM_TABLE_1 : STREAM_TABLE_0;
|
||||
byte[] tableStream = getDocumentEntryBytes(tableStrmName, -1, fibBase.getLKey());
|
||||
LittleEndianByteArrayInputStream leis = new LittleEndianByteArrayInputStream(tableStream);
|
||||
EncryptionMode em = fibBase.isFObfuscated() ? EncryptionMode.xor : null;
|
||||
EncryptionInfo ei = new EncryptionInfo(leis, em);
|
||||
Decryptor dec = ei.getDecryptor();
|
||||
dec.setChunkSize(512);
|
||||
try {
|
||||
String pass = Biff8EncryptionKey.getCurrentUserPassword();
|
||||
if (pass == null) {
|
||||
pass = Decryptor.DEFAULT_PASSWORD;
|
||||
}
|
||||
if (!dec.verifyPassword(pass)) {
|
||||
throw new EncryptedDocumentException("document is encrypted, password is invalid - use Biff8EncryptionKey.setCurrentUserPasswort() to set password before opening");
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
_encryptionInfo = ei;
|
||||
return ei;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* to decrypt the bytes
|
||||
*
|
||||
* @param name the name of the stream
|
||||
* @param encryptionOffset the offset from which to start decrypting, use {@code -1} for no decryption
|
||||
* @param len length of the bytes to be read, use {@link Integer#MAX_VALUE} for all bytes
|
||||
* @return the read bytes
|
||||
* @throws IOException if the stream can't be found
|
||||
*/
|
||||
protected byte[] getDocumentEntryBytes(String name, int encryptionOffset, int len) throws IOException {
|
||||
DirectoryNode dir = getDirectory();
|
||||
DocumentEntry documentProps = (DocumentEntry)dir.getEntry(name);
|
||||
DocumentInputStream dis = dir.createDocumentInputStream(documentProps);
|
||||
EncryptionInfo ei = (encryptionOffset > -1) ? getEncryptionInfo() : null;
|
||||
int streamSize = documentProps.getSize();
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(Math.min(streamSize,len));
|
||||
|
||||
InputStream is = dis;
|
||||
try {
|
||||
if (ei != null) {
|
||||
try {
|
||||
Decryptor dec = ei.getDecryptor();
|
||||
is = dec.getDataStream(dis, streamSize, 0);
|
||||
if (encryptionOffset > 0) {
|
||||
ChunkedCipherInputStream cis = (ChunkedCipherInputStream)is;
|
||||
byte plain[] = new byte[encryptionOffset];
|
||||
cis.readPlain(plain, 0, encryptionOffset);
|
||||
bos.write(plain);
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
// This simplifies a few combinations, so we actually always try to copy len bytes
|
||||
// regardless if encryptionOffset is greater than 0
|
||||
if (len < Integer.MAX_VALUE) {
|
||||
is = new BoundedInputStream(is, len);
|
||||
}
|
||||
IOUtils.copy(is, bos);
|
||||
return bos.toByteArray();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
IOUtils.closeQuietly(dis);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the range which covers the whole of the document, but excludes
|
||||
* any headers and footers.
|
||||
|
@ -339,4 +244,121 @@ public abstract class HWPFDocumentCore extends POIDocument {
|
|||
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
|
||||
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;
|
||||
}
|
||||
|
||||
String tableStrmName = fibBase.isFWhichTblStm() ? STREAM_TABLE_1 : STREAM_TABLE_0;
|
||||
byte[] tableStream = getDocumentEntryBytes(tableStrmName, -1, fibBase.getLKey());
|
||||
LittleEndianByteArrayInputStream leis = new LittleEndianByteArrayInputStream(tableStream);
|
||||
EncryptionMode em = fibBase.isFObfuscated() ? EncryptionMode.xor : null;
|
||||
EncryptionInfo ei = new EncryptionInfo(leis, em);
|
||||
Decryptor dec = ei.getDecryptor();
|
||||
dec.setChunkSize(RC4_REKEYING_INTERVAL);
|
||||
try {
|
||||
String pass = Biff8EncryptionKey.getCurrentUserPassword();
|
||||
if (pass == null) {
|
||||
pass = Decryptor.DEFAULT_PASSWORD;
|
||||
}
|
||||
if (!dec.verifyPassword(pass)) {
|
||||
throw new EncryptedDocumentException("document is encrypted, password is invalid - use Biff8EncryptionKey.setCurrentUserPasswort() to set password before opening");
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
_encryptionInfo = ei;
|
||||
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
|
||||
* to decrypt the bytes
|
||||
*
|
||||
* @param name the name of the stream
|
||||
* @param encryptionOffset the offset from which to start decrypting, use {@code -1} for no decryption
|
||||
* @param len length of the bytes to be read, use {@link Integer#MAX_VALUE} for all bytes
|
||||
* @return the read bytes
|
||||
* @throws IOException if the stream can't be found
|
||||
*/
|
||||
protected byte[] getDocumentEntryBytes(String name, int encryptionOffset, int len) throws IOException {
|
||||
DirectoryNode dir = getDirectory();
|
||||
DocumentEntry documentProps = (DocumentEntry)dir.getEntry(name);
|
||||
DocumentInputStream dis = dir.createDocumentInputStream(documentProps);
|
||||
EncryptionInfo ei = (encryptionOffset > -1) ? getEncryptionInfo() : null;
|
||||
int streamSize = documentProps.getSize();
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(Math.min(streamSize,len));
|
||||
|
||||
InputStream is = dis;
|
||||
try {
|
||||
if (ei != null) {
|
||||
try {
|
||||
Decryptor dec = ei.getDecryptor();
|
||||
is = dec.getDataStream(dis, streamSize, 0);
|
||||
if (encryptionOffset > 0) {
|
||||
ChunkedCipherInputStream cis = (ChunkedCipherInputStream)is;
|
||||
byte plain[] = new byte[encryptionOffset];
|
||||
cis.readPlain(plain, 0, encryptionOffset);
|
||||
bos.write(plain);
|
||||
}
|
||||
} catch (GeneralSecurityException e) {
|
||||
throw new IOException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
// This simplifies a few combinations, so we actually always try to copy len bytes
|
||||
// regardless if encryptionOffset is greater than 0
|
||||
if (len < Integer.MAX_VALUE) {
|
||||
is = new BoundedInputStream(is, len);
|
||||
}
|
||||
IOUtils.copy(is, bos);
|
||||
return bos.toByteArray();
|
||||
} finally {
|
||||
IOUtils.closeQuietly(is);
|
||||
IOUtils.closeQuietly(dis);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue