mirror of https://github.com/apache/poi.git
64512 - Ole10Native aka embedded / object packager - handle UTF16 variants
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1878730 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
0181d2abd9
commit
d559feb7de
|
@ -163,10 +163,20 @@ public final class CopyCompare {
|
||||||
// Ensures that the directory hierarchy for a document in a POI fileystem is in place.
|
// Ensures that the directory hierarchy for a document in a POI fileystem is in place.
|
||||||
// Get the root directory. It does not have to be created since it always exists in a POIFS.
|
// Get the root directory. It does not have to be created since it always exists in a POIFS.
|
||||||
DirectoryEntry de = poiFs.getRoot();
|
DirectoryEntry de = poiFs.getRoot();
|
||||||
|
if ("/".equals(path.toString())) {
|
||||||
|
de.setStorageClsid(event.getStorageClassId());
|
||||||
|
}
|
||||||
|
|
||||||
for (int i=0; i<path.length(); i++) {
|
for (int i=0; i<path.length(); i++) {
|
||||||
String subDir = path.getComponent(i);
|
String subDir = path.getComponent(i);
|
||||||
de = (de.hasEntry(subDir)) ? (DirectoryEntry)de.getEntry(subDir) : de.createDirectory(subDir);
|
if (de.hasEntry(subDir)) {
|
||||||
|
de = (DirectoryEntry)de.getEntry(subDir);
|
||||||
|
} else {
|
||||||
|
de = de.createDirectory(subDir);
|
||||||
|
if (i == path.length()-1) {
|
||||||
|
de.setStorageClsid(event.getStorageClassId());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event.getName() != null) {
|
if (event.getName() != null) {
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
|
|
||||||
|
|
||||||
package org.apache.poi.poifs.eventfilesystem;
|
package org.apache.poi.poifs.eventfilesystem;
|
||||||
|
|
||||||
|
@ -25,12 +25,12 @@ import java.io.InputStream;
|
||||||
|
|
||||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSDocument;
|
import org.apache.poi.poifs.filesystem.POIFSDocument;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
|
||||||
import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
|
import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
|
||||||
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
import org.apache.poi.poifs.property.DirectoryProperty;
|
import org.apache.poi.poifs.property.DirectoryProperty;
|
||||||
import org.apache.poi.poifs.property.DocumentProperty;
|
import org.apache.poi.poifs.property.DocumentProperty;
|
||||||
import org.apache.poi.poifs.property.PropertyTable;
|
|
||||||
import org.apache.poi.poifs.property.Property;
|
import org.apache.poi.poifs.property.Property;
|
||||||
|
import org.apache.poi.poifs.property.PropertyTable;
|
||||||
import org.apache.poi.poifs.property.RootProperty;
|
import org.apache.poi.poifs.property.RootProperty;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
|
|
||||||
|
@ -228,7 +228,7 @@ public class POIFSReader
|
||||||
document = new POIFSDocument((DocumentProperty)property, poifs);
|
document = new POIFSDocument((DocumentProperty)property, poifs);
|
||||||
}
|
}
|
||||||
try (DocumentInputStream dis = new DocumentInputStream(document)) {
|
try (DocumentInputStream dis = new DocumentInputStream(document)) {
|
||||||
POIFSReaderEvent pe = new POIFSReaderEvent(dis, path, name);
|
POIFSReaderEvent pe = new POIFSReaderEvent(dis, path, name, dir.getStorageClsid());
|
||||||
rl.processPOIFSReaderEvent(pe);
|
rl.processPOIFSReaderEvent(pe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -240,7 +240,7 @@ public class POIFSReader
|
||||||
}
|
}
|
||||||
|
|
||||||
for (POIFSReaderListener rl : registry.getListeners(path, ".")) {
|
for (POIFSReaderListener rl : registry.getListeners(path, ".")) {
|
||||||
POIFSReaderEvent pe = new POIFSReaderEvent(null, path, null);
|
POIFSReaderEvent pe = new POIFSReaderEvent(null, path, null, dir.getStorageClsid());
|
||||||
rl.processPOIFSReaderEvent(pe);
|
rl.processPOIFSReaderEvent(pe);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
/* ====================================================================
|
/* ====================================================================
|
||||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
contributor license agreements. See the NOTICE file distributed with
|
contributor license agreements. See the NOTICE file distributed with
|
||||||
|
@ -15,67 +14,63 @@
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
==================================================================== */
|
==================================================================== */
|
||||||
|
|
||||||
|
|
||||||
package org.apache.poi.poifs.eventfilesystem;
|
package org.apache.poi.poifs.eventfilesystem;
|
||||||
|
|
||||||
|
import org.apache.poi.hpsf.ClassID;
|
||||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||||
import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
|
import org.apache.poi.poifs.filesystem.POIFSDocumentPath;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class POIFSReaderEvent
|
* Class POIFSReaderEvent
|
||||||
*
|
|
||||||
* @author Marc Johnson (mjohnson at apache dot org)
|
|
||||||
* @version %I%, %G%
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public class POIFSReaderEvent
|
public class POIFSReaderEvent {
|
||||||
{
|
|
||||||
private final DocumentInputStream stream;
|
private final DocumentInputStream stream;
|
||||||
private final POIFSDocumentPath path;
|
private final POIFSDocumentPath path;
|
||||||
private final String documentName;
|
private final String documentName;
|
||||||
|
private final ClassID storageClassId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* package scoped constructor
|
* package scoped constructor
|
||||||
*
|
*
|
||||||
* @param stream the DocumentInputStream, freshly opened
|
* @param stream the DocumentInputStream, freshly opened
|
||||||
* @param path the path of the document
|
* @param path the path of the document
|
||||||
* @param documentName the name of the document
|
* @param documentName the name of the document
|
||||||
*/
|
*/
|
||||||
|
|
||||||
POIFSReaderEvent(final DocumentInputStream stream,
|
POIFSReaderEvent(final DocumentInputStream stream,
|
||||||
final POIFSDocumentPath path, final String documentName)
|
final POIFSDocumentPath path, final String documentName, final ClassID storageClassId) {
|
||||||
{
|
this.stream = stream;
|
||||||
this.stream = stream;
|
this.path = path;
|
||||||
this.path = path;
|
|
||||||
this.documentName = documentName;
|
this.documentName = documentName;
|
||||||
|
this.storageClassId = storageClassId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the DocumentInputStream, freshly opened
|
* @return the DocumentInputStream, freshly opened
|
||||||
*/
|
*/
|
||||||
|
public DocumentInputStream getStream() {
|
||||||
public DocumentInputStream getStream()
|
|
||||||
{
|
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the document's path
|
* @return the document's path
|
||||||
*/
|
*/
|
||||||
|
public POIFSDocumentPath getPath() {
|
||||||
public POIFSDocumentPath getPath()
|
|
||||||
{
|
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the document's name
|
* @return the document's name
|
||||||
*/
|
*/
|
||||||
|
public String getName() {
|
||||||
public String getName()
|
|
||||||
{
|
|
||||||
return documentName;
|
return documentName;
|
||||||
}
|
}
|
||||||
} // end public class POIFSReaderEvent
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the storage class id of the path
|
||||||
|
*/
|
||||||
|
public ClassID getStorageClassId() {
|
||||||
|
return storageClassId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ public final class EntryUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies all the nodes from one POIFS Directory to another
|
* Copies all the nodes from one POIFS Directory to another
|
||||||
*
|
*
|
||||||
* @param sourceRoot
|
* @param sourceRoot
|
||||||
* is the source Directory to copy from
|
* is the source Directory to copy from
|
||||||
* @param targetRoot
|
* @param targetRoot
|
||||||
|
@ -75,7 +75,7 @@ public final class EntryUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies all nodes from one POIFS to the other
|
* Copies all nodes from one POIFS to the other
|
||||||
*
|
*
|
||||||
* @param source
|
* @param source
|
||||||
* is the source POIFS to copy from
|
* is the source POIFS to copy from
|
||||||
* @param target
|
* @param target
|
||||||
|
@ -85,13 +85,13 @@ public final class EntryUtils {
|
||||||
throws IOException {
|
throws IOException {
|
||||||
copyNodes( source.getRoot(), target.getRoot() );
|
copyNodes( source.getRoot(), target.getRoot() );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies nodes from one POIFS to the other, minus the excepts.
|
* Copies nodes from one POIFS to the other, minus the excepts.
|
||||||
* This delegates the filtering work to {@link FilteringDirectoryNode},
|
* This delegates the filtering work to {@link FilteringDirectoryNode},
|
||||||
* so excepts can be of the form "NodeToExclude" or
|
* so excepts can be of the form "NodeToExclude" or
|
||||||
* "FilteringDirectory/ExcludedChildNode"
|
* "FilteringDirectory/ExcludedChildNode"
|
||||||
*
|
*
|
||||||
* @param source is the source POIFS to copy from
|
* @param source is the source POIFS to copy from
|
||||||
* @param target is the target POIFS to copy to
|
* @param target is the target POIFS to copy to
|
||||||
* @param excepts is a list of Entry Names to be excluded from the copy
|
* @param excepts is a list of Entry Names to be excluded from the copy
|
||||||
|
@ -103,19 +103,23 @@ public final class EntryUtils {
|
||||||
new FilteringDirectoryNode(target.getRoot(), excepts)
|
new FilteringDirectoryNode(target.getRoot(), excepts)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks to see if the two Directories hold the same contents.
|
* Checks to see if the two Directories hold the same contents.
|
||||||
* For this to be true, they must have entries with the same names,
|
* For this to be true ...
|
||||||
* no entries in one but not the other, and the size+contents
|
* <ul>
|
||||||
* of each entry must match, and they must share names.
|
* <li>they must have entries with the same names</li>
|
||||||
|
* <li>no entries in one but not the other</li>
|
||||||
|
* <li>the size+contents of each entry must match</li>
|
||||||
|
* <li>the storage classid of the directories must match</li>
|
||||||
|
* </ul>
|
||||||
* To exclude certain parts of the Directory from being checked,
|
* To exclude certain parts of the Directory from being checked,
|
||||||
* use a {@link FilteringDirectoryNode}
|
* use a {@link FilteringDirectoryNode}
|
||||||
*/
|
*/
|
||||||
public static boolean areDirectoriesIdentical(DirectoryEntry dirA, DirectoryEntry dirB) {
|
public static boolean areDirectoriesIdentical(DirectoryEntry dirA, DirectoryEntry dirB) {
|
||||||
return new DirectoryDelegate(dirA).equals(new DirectoryDelegate(dirB));
|
return new DirectoryDelegate(dirA).equals(new DirectoryDelegate(dirB));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares two {@link DocumentEntry} instances of a POI file system.
|
* Compares two {@link DocumentEntry} instances of a POI file system.
|
||||||
* Documents that are not property set streams must be bitwise identical.
|
* Documents that are not property set streams must be bitwise identical.
|
||||||
|
@ -185,6 +189,10 @@ public final class EntryUtils {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!dir.getStorageClsid().equals(dd.dir.getStorageClsid())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return entries().equals(dd.entries());
|
return entries().equals(dd.entries());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,44 +21,69 @@ import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
||||||
import org.apache.poi.util.LittleEndianConsts;
|
import org.apache.poi.util.LittleEndianConsts;
|
||||||
|
import org.apache.poi.util.LittleEndianInput;
|
||||||
import org.apache.poi.util.LittleEndianOutputStream;
|
import org.apache.poi.util.LittleEndianOutputStream;
|
||||||
import org.apache.poi.util.StringUtil;
|
import org.apache.poi.util.StringUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an Ole10Native record which is wrapped around certain binary
|
* Represents an Ole10Native record which is wrapped around certain binary
|
||||||
* files being embedded in OLE2 documents.
|
* files being embedded in OLE2 documents.<p>
|
||||||
|
*
|
||||||
|
* Ole10Native objects come in different shapes:
|
||||||
|
* <ul>
|
||||||
|
* <li>unparsed: we can't identify it's structure</li>
|
||||||
|
* <li>compact: same as unparsed but with a leading flag</li>
|
||||||
|
* <li>parsed - Ole-Class "Package": data + ASCII label,command,filename</li>
|
||||||
|
* <li>parsed - Ole-Class "Package2": as above plus UTF16 label,command,filename</li>
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public class Ole10Native {
|
public class Ole10Native {
|
||||||
|
|
||||||
|
|
||||||
public static final String OLE10_NATIVE = "\u0001Ole10Native";
|
public static final String OLE10_NATIVE = "\u0001Ole10Native";
|
||||||
protected static final String ISO1 = "ISO-8859-1";
|
private static final Charset ISO1 = StandardCharsets.ISO_8859_1;
|
||||||
//arbitrarily selected; may need to increase
|
// arbitrarily selected; may need to increase
|
||||||
private static final int MAX_RECORD_LENGTH = 100_000_000;
|
private static final int MAX_RECORD_LENGTH = 100_000_000;
|
||||||
|
// arbitrarily selected; may need to increase
|
||||||
|
private static final int MAX_STRING_LENGTH = 1024;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default content of the \u0001Ole entry
|
* Default content of the \u0001Ole entry
|
||||||
*/
|
*/
|
||||||
private static final byte[] OLE_MARKER_BYTES =
|
private static final byte[] OLE_MARKER_BYTES =
|
||||||
{ 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
{1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||||
private static final String OLE_MARKER_NAME = "\u0001Ole";
|
private static final String OLE_MARKER_NAME = "\u0001Ole";
|
||||||
|
|
||||||
|
|
||||||
|
// 4 bytes, total size of record not including this field
|
||||||
// (the fields as they appear in the raw record:)
|
private int totalSize;
|
||||||
private int totalSize; // 4 bytes, total size of record not including this field
|
// 2 bytes, unknown, mostly [02 00]
|
||||||
private short flags1 = 2; // 2 bytes, unknown, mostly [02 00]
|
private short flags1 = 2;
|
||||||
private String label; // ASCIIZ, stored in this field without the terminating zero
|
// ASCIIZ, stored in this field without the terminating zero
|
||||||
private String fileName; // ASCIIZ, stored in this field without the terminating zero
|
private String label;
|
||||||
private short flags2; // 2 bytes, unknown, mostly [00 00]
|
// ASCIIZ, stored in this field without the terminating zero
|
||||||
private short unknown1 = 3; // see below
|
private String fileName;
|
||||||
private String command; // ASCIIZ, stored in this field without the terminating zero
|
// 2 bytes, unknown, mostly [00 00]
|
||||||
private byte[] dataBuffer; // varying size, the actual native data
|
private short flags2;
|
||||||
private short flags3; // some final flags? or zero terminators?, sometimes not there
|
// see below
|
||||||
|
private short unknown1 = 3;
|
||||||
|
// ASCIIZ, stored in this field without the terminating zero
|
||||||
|
private String command;
|
||||||
|
// varying size, the actual native data
|
||||||
|
private byte[] dataBuffer;
|
||||||
|
// UTF16-LE String with leading length
|
||||||
|
private String command2;
|
||||||
|
// UTF16-LE String with leading length
|
||||||
|
private String label2;
|
||||||
|
// UTF16-LE String with leading length
|
||||||
|
private String fileName2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the field encoding mode - merely a try-and-error guess ...
|
* the field encoding mode - merely a try-and-error guess ...
|
||||||
|
@ -81,7 +106,6 @@ public class Ole10Native {
|
||||||
private EncodingMode mode;
|
private EncodingMode mode;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance of this class from an embedded OLE Object. The OLE Object is expected
|
* Creates an instance of this class from an embedded OLE Object. The OLE Object is expected
|
||||||
* to include a stream "{01}Ole10Native" which contains the actual
|
* to include a stream "{01}Ole10Native" which contains the actual
|
||||||
|
@ -89,11 +113,11 @@ public class Ole10Native {
|
||||||
*
|
*
|
||||||
* @param poifs POI Filesystem object
|
* @param poifs POI Filesystem object
|
||||||
* @return Returns an instance of this class
|
* @return Returns an instance of this class
|
||||||
* @throws IOException on IO error
|
* @throws IOException on IO error
|
||||||
* @throws Ole10NativeException on invalid or unexcepted data format
|
* @throws Ole10NativeException on invalid or unexcepted data format
|
||||||
*/
|
*/
|
||||||
public static Ole10Native createFromEmbeddedOleObject(POIFSFileSystem poifs) throws IOException, Ole10NativeException {
|
public static Ole10Native createFromEmbeddedOleObject(POIFSFileSystem poifs) throws IOException, Ole10NativeException {
|
||||||
return createFromEmbeddedOleObject(poifs.getRoot());
|
return createFromEmbeddedOleObject(poifs.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,26 +127,27 @@ public class Ole10Native {
|
||||||
*
|
*
|
||||||
* @param directory POI Filesystem object
|
* @param directory POI Filesystem object
|
||||||
* @return Returns an instance of this class
|
* @return Returns an instance of this class
|
||||||
* @throws IOException on IO error
|
* @throws IOException on IO error
|
||||||
* @throws Ole10NativeException on invalid or unexcepted data format
|
* @throws Ole10NativeException on invalid or unexcepted data format
|
||||||
*/
|
*/
|
||||||
public static Ole10Native createFromEmbeddedOleObject(DirectoryNode directory) throws IOException, Ole10NativeException {
|
public static Ole10Native createFromEmbeddedOleObject(DirectoryNode directory) throws IOException, Ole10NativeException {
|
||||||
DocumentEntry nativeEntry = (DocumentEntry)directory.getEntry(OLE10_NATIVE);
|
DocumentEntry nativeEntry = (DocumentEntry) directory.getEntry(OLE10_NATIVE);
|
||||||
try (DocumentInputStream dis = directory.createDocumentInputStream(nativeEntry)) {
|
try (DocumentInputStream dis = directory.createDocumentInputStream(nativeEntry)) {
|
||||||
byte[] data = IOUtils.toByteArray(dis, nativeEntry.getSize(), MAX_RECORD_LENGTH);
|
byte[] data = IOUtils.toByteArray(dis, nativeEntry.getSize(), MAX_RECORD_LENGTH);
|
||||||
return new Ole10Native(data, 0);
|
return new Ole10Native(data, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance and fills the fields based on ... the fields
|
* Creates an instance and fills the fields based on ... the fields
|
||||||
*/
|
*/
|
||||||
public Ole10Native(String label, String filename, String command, byte[] data) {
|
public Ole10Native(String label, String filename, String command, byte[] data) {
|
||||||
setLabel(label);
|
setLabel(label);
|
||||||
setFileName(filename);
|
setFileName(filename);
|
||||||
setCommand(command);
|
setCommand(command);
|
||||||
setDataBuffer(data);
|
command2 = command;
|
||||||
mode = EncodingMode.parsed;
|
setDataBuffer(data);
|
||||||
|
mode = EncodingMode.parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -132,81 +157,64 @@ public class Ole10Native {
|
||||||
* @param offset The start offset of the record in the buffer
|
* @param offset The start offset of the record in the buffer
|
||||||
* @throws Ole10NativeException on invalid or unexcepted data format
|
* @throws Ole10NativeException on invalid or unexcepted data format
|
||||||
*/
|
*/
|
||||||
public Ole10Native(byte[] data, int offset) throws Ole10NativeException {
|
public Ole10Native(final byte[] data, final int offset) throws Ole10NativeException {
|
||||||
int ofs = offset; // current offset, initialized to start
|
LittleEndianByteArrayInputStream leis = new LittleEndianByteArrayInputStream(data, offset);
|
||||||
|
|
||||||
if (data.length < offset + 2) {
|
totalSize = leis.readInt();
|
||||||
throw new Ole10NativeException("data is too small");
|
leis.limit(totalSize + LittleEndianConsts.INT_SIZE);
|
||||||
}
|
|
||||||
|
|
||||||
totalSize = LittleEndian.getInt(data, ofs);
|
leis.mark(0);
|
||||||
ofs += LittleEndianConsts.INT_SIZE;
|
|
||||||
|
|
||||||
mode = EncodingMode.unparsed;
|
try {
|
||||||
if (LittleEndian.getShort(data, ofs) == 2) {
|
flags1 = leis.readShort();
|
||||||
// some files like equations don't have a valid filename,
|
if (flags1 == 2) {
|
||||||
// but somehow encode the formula right away in the ole10 header
|
leis.mark(0);
|
||||||
if (Character.isISOControl(data[ofs+LittleEndianConsts.SHORT_SIZE])) {
|
// some files like equations don't have a valid filename,
|
||||||
mode = EncodingMode.compact;
|
// but somehow encode the formula right away in the ole10 header
|
||||||
|
boolean validFileName = !Character.isISOControl(leis.readByte());
|
||||||
|
leis.reset();
|
||||||
|
|
||||||
|
if (validFileName) {
|
||||||
|
readParsed(leis);
|
||||||
|
} else {
|
||||||
|
readCompact(leis);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
mode = EncodingMode.parsed;
|
leis.reset();
|
||||||
|
readUnparsed(leis);
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new Ole10NativeException("Invalid Ole10Native", e);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int dataSize;
|
private void readParsed(LittleEndianByteArrayInputStream leis) throws Ole10NativeException, IOException {
|
||||||
switch (mode) {
|
mode = EncodingMode.parsed;
|
||||||
case parsed: {
|
label = readAsciiZ(leis);
|
||||||
flags1 = LittleEndian.getShort(data, ofs);
|
fileName = readAsciiZ(leis);
|
||||||
|
flags2 = leis.readShort();
|
||||||
|
unknown1 = leis.readShort();
|
||||||
|
command = readAsciiLen(leis);
|
||||||
|
dataBuffer = IOUtils.toByteArray(leis, leis.readInt(), MAX_RECORD_LENGTH);
|
||||||
|
|
||||||
// structured format
|
leis.mark(0);
|
||||||
ofs += LittleEndianConsts.SHORT_SIZE;
|
short lowSize = leis.readShort();
|
||||||
|
if (lowSize != 0) {
|
||||||
int len = getStringLength(data, ofs);
|
leis.reset();
|
||||||
label = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
|
command2 = readUtf16(leis);
|
||||||
ofs += len;
|
label2 = readUtf16(leis);
|
||||||
|
fileName2 = readUtf16(leis);
|
||||||
len = getStringLength(data, ofs);
|
|
||||||
fileName = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
|
|
||||||
ofs += len;
|
|
||||||
|
|
||||||
flags2 = LittleEndian.getShort(data, ofs);
|
|
||||||
ofs += LittleEndianConsts.SHORT_SIZE;
|
|
||||||
|
|
||||||
unknown1 = LittleEndian.getShort(data, ofs);
|
|
||||||
ofs += LittleEndianConsts.SHORT_SIZE;
|
|
||||||
|
|
||||||
len = LittleEndian.getInt(data, ofs);
|
|
||||||
ofs += LittleEndianConsts.INT_SIZE;
|
|
||||||
command = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
|
|
||||||
ofs += len;
|
|
||||||
|
|
||||||
if (totalSize < ofs) {
|
|
||||||
throw new Ole10NativeException("Invalid Ole10Native");
|
|
||||||
}
|
|
||||||
|
|
||||||
dataSize = LittleEndian.getInt(data, ofs);
|
|
||||||
ofs += LittleEndianConsts.INT_SIZE;
|
|
||||||
|
|
||||||
if (dataSize < 0 || totalSize - (ofs - LittleEndianConsts.INT_SIZE) < dataSize) {
|
|
||||||
throw new Ole10NativeException("Invalid Ole10Native");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case compact:
|
|
||||||
flags1 = LittleEndian.getShort(data, ofs);
|
|
||||||
ofs += LittleEndianConsts.SHORT_SIZE;
|
|
||||||
dataSize = totalSize - LittleEndianConsts.SHORT_SIZE;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
case unparsed:
|
|
||||||
dataSize = totalSize;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if ((long)dataSize + (long)ofs > (long)data.length) { //cast to avoid overflow
|
private void readCompact(LittleEndianByteArrayInputStream leis) throws IOException {
|
||||||
throw new Ole10NativeException("Invalid Ole10Native: declared data length > available data");
|
mode = EncodingMode.compact;
|
||||||
}
|
dataBuffer = IOUtils.toByteArray(leis, totalSize - LittleEndianConsts.SHORT_SIZE, MAX_RECORD_LENGTH);
|
||||||
dataBuffer = IOUtils.safelyClone(data, ofs, dataSize, MAX_RECORD_LENGTH);
|
}
|
||||||
|
|
||||||
|
private void readUnparsed(LittleEndianByteArrayInputStream leis) throws IOException {
|
||||||
|
mode = EncodingMode.unparsed;
|
||||||
|
dataBuffer = IOUtils.toByteArray(leis, totalSize, MAX_RECORD_LENGTH);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -230,16 +238,30 @@ public class Ole10Native {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Helper - determine length of zero terminated string (ASCIIZ).
|
* Read zero terminated string (ASCIIZ).
|
||||||
*/
|
*/
|
||||||
private static int getStringLength(byte[] data, int ofs) {
|
private static String readAsciiZ(LittleEndianInput is) throws Ole10NativeException {
|
||||||
int len = 0;
|
// arbitrary sized buffer - not sure how big strings can get in an Ole10 record
|
||||||
while (len + ofs < data.length && data[ofs + len] != 0) {
|
byte[] buf = new byte[MAX_STRING_LENGTH];
|
||||||
len++;
|
for (int i=0; i<buf.length; i++) {
|
||||||
|
if ((buf[i] = is.readByte()) == 0) {
|
||||||
|
return StringUtil.getFromCompressedUnicode(buf, 0, i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
len++;
|
throw new Ole10NativeException("AsciiZ string was not null terminated after " + MAX_STRING_LENGTH + " bytes - Exiting.");
|
||||||
return len;
|
}
|
||||||
|
|
||||||
|
private static String readAsciiLen(LittleEndianByteArrayInputStream leis) throws IOException {
|
||||||
|
int size = leis.readInt();
|
||||||
|
byte[] buf = IOUtils.toByteArray(leis, size, MAX_STRING_LENGTH);
|
||||||
|
return (buf.length == 0) ? "" : StringUtil.getFromCompressedUnicode(buf, 0, size - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String readUtf16(LittleEndianByteArrayInputStream leis) throws IOException {
|
||||||
|
int size = leis.readInt();
|
||||||
|
byte[] buf = IOUtils.toByteArray(leis, size * 2, MAX_STRING_LENGTH);
|
||||||
|
return StringUtil.getFromUnicodeLE(buf, 0, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -335,15 +357,6 @@ public class Ole10Native {
|
||||||
return dataBuffer;
|
return dataBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the flags3 - currently unknown.
|
|
||||||
*
|
|
||||||
* @return the flags3
|
|
||||||
*/
|
|
||||||
public short getFlags3() {
|
|
||||||
return flags3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Have the contents printer out into an OutputStream, used when writing a
|
* Have the contents printer out into an OutputStream, used when writing a
|
||||||
* file back out to disk (Normally, atom classes will keep their bytes
|
* file back out to disk (Normally, atom classes will keep their bytes
|
||||||
|
@ -358,40 +371,53 @@ public class Ole10Native {
|
||||||
LittleEndianOutputStream leosOut = new LittleEndianOutputStream(out);
|
LittleEndianOutputStream leosOut = new LittleEndianOutputStream(out);
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
case parsed: {
|
case parsed: {
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
LittleEndianOutputStream leos = new LittleEndianOutputStream(bos);
|
try (LittleEndianOutputStream leos = new LittleEndianOutputStream(bos)) {
|
||||||
// total size, will be determined later ..
|
// total size, will be determined later ..
|
||||||
|
|
||||||
leos.writeShort(getFlags1());
|
leos.writeShort(getFlags1());
|
||||||
leos.write(getLabel().getBytes(ISO1));
|
leos.write(getLabel().getBytes(ISO1));
|
||||||
leos.write(0);
|
leos.write(0);
|
||||||
leos.write(getFileName().getBytes(ISO1));
|
leos.write(getFileName().getBytes(ISO1));
|
||||||
leos.write(0);
|
leos.write(0);
|
||||||
leos.writeShort(getFlags2());
|
leos.writeShort(getFlags2());
|
||||||
leos.writeShort(getUnknown1());
|
leos.writeShort(getUnknown1());
|
||||||
leos.writeInt(getCommand().length() + 1);
|
leos.writeInt(getCommand().length() + 1);
|
||||||
leos.write(getCommand().getBytes(ISO1));
|
leos.write(getCommand().getBytes(ISO1));
|
||||||
leos.write(0);
|
leos.write(0);
|
||||||
leos.writeInt(getDataSize());
|
leos.writeInt(getDataSize());
|
||||||
leos.write(getDataBuffer());
|
leos.write(getDataBuffer());
|
||||||
leos.writeShort(getFlags3());
|
|
||||||
leos.close(); // satisfy compiler ...
|
|
||||||
|
|
||||||
leosOut.writeInt(bos.size()); // total size
|
if (command2 == null || label2 == null || fileName2 == null) {
|
||||||
bos.writeTo(out);
|
leos.writeShort(0);
|
||||||
break;
|
} else {
|
||||||
}
|
leos.writeUInt(command2.length());
|
||||||
case compact:
|
leos.write(StringUtil.getToUnicodeLE(command2));
|
||||||
leosOut.writeInt(getDataSize()+LittleEndianConsts.SHORT_SIZE);
|
leos.writeUInt(label2.length());
|
||||||
leosOut.writeShort(getFlags1());
|
leos.write(StringUtil.getToUnicodeLE(label2));
|
||||||
out.write(getDataBuffer());
|
leos.writeUInt(fileName2.length());
|
||||||
break;
|
leos.write(StringUtil.getToUnicodeLE(fileName2));
|
||||||
default:
|
}
|
||||||
case unparsed:
|
}
|
||||||
leosOut.writeInt(getDataSize());
|
|
||||||
out.write(getDataBuffer());
|
// total size
|
||||||
break;
|
leosOut.writeInt(bos.size());
|
||||||
|
bos.writeTo(out);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case compact:
|
||||||
|
leosOut.writeInt(getDataSize() + LittleEndianConsts.SHORT_SIZE);
|
||||||
|
leosOut.writeShort(getFlags1());
|
||||||
|
out.write(getDataBuffer());
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
case unparsed:
|
||||||
|
leosOut.writeInt(getDataSize());
|
||||||
|
out.write(getDataBuffer());
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -404,10 +430,6 @@ public class Ole10Native {
|
||||||
this.flags2 = flags2;
|
this.flags2 = flags2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFlags3(short flags3) {
|
|
||||||
this.flags3 = flags3;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLabel(String label) {
|
public void setLabel(String label) {
|
||||||
this.label = label;
|
this.label = label;
|
||||||
}
|
}
|
||||||
|
@ -427,4 +449,46 @@ public class Ole10Native {
|
||||||
public void setDataBuffer(byte[] dataBuffer) {
|
public void setDataBuffer(byte[] dataBuffer) {
|
||||||
this.dataBuffer = dataBuffer.clone();
|
this.dataBuffer = dataBuffer.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Command string of UTF16 extended OLE packages or {@code null} if not set or not UTF16 extended
|
||||||
|
*/
|
||||||
|
public String getCommand2() {
|
||||||
|
return command2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Command string for UTF16 extended OLE packages or {@code null} if not set or not UTF16 extended
|
||||||
|
*/
|
||||||
|
public void setCommand2(String command2) {
|
||||||
|
this.command2 = command2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Label string for UTF16 extended OLE packages or {@code null} if not set or not UTF16 extended
|
||||||
|
*/
|
||||||
|
public String getLabel2() {
|
||||||
|
return label2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set Label string for UTF16 extended OLE packages or {@code null} if not set or not UTF16 extended
|
||||||
|
*/
|
||||||
|
public void setLabel2(String label2) {
|
||||||
|
this.label2 = label2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get filename string for UTF16 extended OLE packages or {@code null} if not set or not UTF16 extended
|
||||||
|
*/
|
||||||
|
public String getFileName2() {
|
||||||
|
return fileName2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set filename string for UTF16 extended OLE packages or {@code null} if not set or not UTF16 extended
|
||||||
|
*/
|
||||||
|
public void setFileName2(String fileName2) {
|
||||||
|
this.fileName2 = fileName2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,4 +21,12 @@ public class Ole10NativeException extends Exception {
|
||||||
public Ole10NativeException(String message) {
|
public Ole10NativeException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Ole10NativeException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Ole10NativeException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -91,8 +91,8 @@ public class LittleEndianByteArrayInputStream extends ByteArrayInputStream imple
|
||||||
}
|
}
|
||||||
this.pos = pos;
|
this.pos = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte readByte() {
|
public byte readByte() {
|
||||||
checkPosition(1);
|
checkPosition(1);
|
||||||
|
@ -140,14 +140,14 @@ public class LittleEndianByteArrayInputStream extends ByteArrayInputStream imple
|
||||||
}
|
}
|
||||||
|
|
||||||
public long readUInt() {
|
public long readUInt() {
|
||||||
return readInt() & 0x00FFFFFFFFL;
|
return readInt() & 0x00FFFFFFFFL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double readDouble() {
|
public double readDouble() {
|
||||||
return Double.longBitsToDouble(readLong());
|
return Double.longBitsToDouble(readLong());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void readFully(byte[] buffer, int off, int len) {
|
public void readFully(byte[] buffer, int off, int len) {
|
||||||
checkPosition(len);
|
checkPosition(len);
|
||||||
|
@ -164,4 +164,12 @@ public class LittleEndianByteArrayInputStream extends ByteArrayInputStream imple
|
||||||
public void readPlain(byte[] buf, int off, int len) {
|
public void readPlain(byte[] buf, int off, int len) {
|
||||||
readFully(buf, off, len);
|
readFully(buf, off, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the limit of the ByteArrayInputStream
|
||||||
|
* @param size the new limit - is truncated to length of internal buffer
|
||||||
|
*/
|
||||||
|
public void limit(int size) {
|
||||||
|
count = Math.min(size, buf.length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,6 @@ import java.util.List;
|
||||||
|
|
||||||
import javax.xml.namespace.QName;
|
import javax.xml.namespace.QName;
|
||||||
|
|
||||||
import org.apache.poi.ooxml.POIXMLDocument;
|
|
||||||
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
||||||
import org.apache.poi.ooxml.POIXMLException;
|
import org.apache.poi.ooxml.POIXMLException;
|
||||||
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
|
||||||
|
@ -441,14 +440,14 @@ public final class XSSFDrawing extends POIXMLDocumentPart implements Drawing<XSS
|
||||||
long shapeId = (sheetIndex + 1L) * 1024 + newShapeId();
|
long shapeId = (sheetIndex + 1L) * 1024 + newShapeId();
|
||||||
|
|
||||||
// add reference to OLE part
|
// add reference to OLE part
|
||||||
|
final XSSFRelation rel = XSSFRelation.OLEEMBEDDINGS;
|
||||||
PackagePartName olePN;
|
PackagePartName olePN;
|
||||||
try {
|
try {
|
||||||
olePN = PackagingURIHelper.createPartName("/xl/embeddings/oleObject" + storageId + ".bin");
|
olePN = PackagingURIHelper.createPartName(rel.getFileName(storageId));
|
||||||
} catch (InvalidFormatException e) {
|
} catch (InvalidFormatException e) {
|
||||||
throw new POIXMLException(e);
|
throw new POIXMLException(e);
|
||||||
}
|
}
|
||||||
PackageRelationship olePR = sheetPart.addRelationship(olePN, TargetMode.INTERNAL,
|
PackageRelationship olePR = sheetPart.addRelationship(olePN, TargetMode.INTERNAL, rel.getRelation());
|
||||||
POIXMLDocument.OLE_OBJECT_REL_TYPE);
|
|
||||||
|
|
||||||
// add reference to image part
|
// add reference to image part
|
||||||
XSSFPictureData imgPD = sh.getWorkbook().getAllPictures().get(pictureIndex);
|
XSSFPictureData imgPD = sh.getWorkbook().getAllPictures().get(pictureIndex);
|
||||||
|
|
|
@ -247,9 +247,9 @@ public final class XSSFRelation extends POIXMLRelation {
|
||||||
);
|
);
|
||||||
|
|
||||||
public static final XSSFRelation OLEEMBEDDINGS = new XSSFRelation(
|
public static final XSSFRelation OLEEMBEDDINGS = new XSSFRelation(
|
||||||
null,
|
"application/vnd.openxmlformats-officedocument.oleObject",
|
||||||
POIXMLDocument.OLE_OBJECT_REL_TYPE,
|
POIXMLDocument.OLE_OBJECT_REL_TYPE,
|
||||||
null
|
"/xl/embeddings/oleObject#.bin"
|
||||||
);
|
);
|
||||||
|
|
||||||
public static final XSSFRelation PACKEMBEDDINGS = new XSSFRelation(
|
public static final XSSFRelation PACKEMBEDDINGS = new XSSFRelation(
|
||||||
|
|
|
@ -2383,19 +2383,20 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Date1904Su
|
||||||
@Override
|
@Override
|
||||||
public int addOlePackage(byte[] oleData, String label, String fileName, String command)
|
public int addOlePackage(byte[] oleData, String label, String fileName, String command)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
|
final XSSFRelation rel = XSSFRelation.OLEEMBEDDINGS;
|
||||||
|
|
||||||
// find an unused part name
|
// find an unused part name
|
||||||
OPCPackage opc = getPackage();
|
OPCPackage opc = getPackage();
|
||||||
PackagePartName pnOLE;
|
PackagePartName pnOLE;
|
||||||
int oleId=0;
|
int oleId;
|
||||||
do {
|
try {
|
||||||
try {
|
oleId = opc.getUnusedPartIndex(rel.getDefaultFileName());
|
||||||
pnOLE = PackagingURIHelper.createPartName( "/xl/embeddings/oleObject"+(++oleId)+".bin" );
|
pnOLE = PackagingURIHelper.createPartName(rel.getFileName(oleId));
|
||||||
} catch (InvalidFormatException e) {
|
} catch (InvalidFormatException e) {
|
||||||
throw new IOException("ole object name not recognized", e);
|
throw new IOException("ole object name not recognized", e);
|
||||||
}
|
}
|
||||||
} while (opc.containPart(pnOLE));
|
|
||||||
|
|
||||||
PackagePart pp = opc.createPart( pnOLE, "application/vnd.openxmlformats-officedocument.oleObject" );
|
PackagePart pp = opc.createPart( pnOLE, rel.getContentType() );
|
||||||
|
|
||||||
Ole10Native ole10 = new Ole10Native(label, fileName, command, oleData);
|
Ole10Native ole10 = new Ole10Native(label, fileName, command, oleData);
|
||||||
|
|
||||||
|
|
|
@ -23,16 +23,29 @@ import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.junit.Assume.assumeFalse;
|
import static org.junit.Assume.assumeFalse;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64;
|
||||||
import org.apache.poi.POIDataSamples;
|
import org.apache.poi.POIDataSamples;
|
||||||
|
import org.apache.poi.hpsf.ClassIDPredefined;
|
||||||
import org.apache.poi.hssf.HSSFTestDataSamples;
|
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||||
|
import org.apache.poi.poifs.crypt.CryptoFunctions;
|
||||||
|
import org.apache.poi.poifs.crypt.HashAlgorithm;
|
||||||
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
import org.apache.poi.poifs.filesystem.DirectoryNode;
|
||||||
|
import org.apache.poi.poifs.filesystem.EntryUtils;
|
||||||
|
import org.apache.poi.poifs.filesystem.Ole10Native;
|
||||||
|
import org.apache.poi.poifs.filesystem.Ole10NativeException;
|
||||||
|
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
|
||||||
import org.apache.poi.sl.usermodel.AutoShape;
|
import org.apache.poi.sl.usermodel.AutoShape;
|
||||||
import org.apache.poi.sl.usermodel.ShapeType;
|
import org.apache.poi.sl.usermodel.ShapeType;
|
||||||
import org.apache.poi.sl.usermodel.Slide;
|
import org.apache.poi.sl.usermodel.Slide;
|
||||||
|
@ -41,20 +54,92 @@ import org.apache.poi.ss.extractor.EmbeddedData;
|
||||||
import org.apache.poi.ss.extractor.EmbeddedExtractor;
|
import org.apache.poi.ss.extractor.EmbeddedExtractor;
|
||||||
import org.apache.poi.xslf.usermodel.XMLSlideShow;
|
import org.apache.poi.xslf.usermodel.XMLSlideShow;
|
||||||
import org.apache.poi.xssf.XSSFTestDataSamples;
|
import org.apache.poi.xssf.XSSFTestDataSamples;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFObjectData;
|
||||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
public class TestEmbedOLEPackage {
|
public class TestEmbedOLEPackage {
|
||||||
private static byte[] samplePPT, samplePPTX, samplePNG;
|
private static byte[] samplePPT, samplePPTX, samplePNG;
|
||||||
|
|
||||||
|
private static final POIDataSamples ssamples = POIDataSamples.getSpreadSheetInstance();
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void init() throws IOException, ReflectiveOperationException {
|
public static void init() throws IOException, ReflectiveOperationException {
|
||||||
samplePPT = getSamplePPT(false);
|
samplePPT = getSamplePPT(false);
|
||||||
samplePPTX = getSamplePPT(true);
|
samplePPTX = getSamplePPT(true);
|
||||||
samplePNG = POIDataSamples.getSpreadSheetInstance().readFile("logoKarmokar4.png");
|
samplePNG = ssamples.readFile("logoKarmokar4.png");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void embedPDF() throws IOException {
|
||||||
|
try (InputStream is = ssamples.openResourceAsStream("bug64512_embed.xlsx");
|
||||||
|
XSSFWorkbook wb = new XSSFWorkbook(is)) {
|
||||||
|
List<XSSFObjectData> oleShapes = new ArrayList<>();
|
||||||
|
List<Ole10Native> ole10s = new ArrayList<>();
|
||||||
|
List<String> digests = new ArrayList<>();
|
||||||
|
|
||||||
|
final boolean digestMatch =
|
||||||
|
wb.getSheetAt(0).getDrawingPatriarch().getShapes().stream()
|
||||||
|
.map(s -> (XSSFObjectData)s)
|
||||||
|
.filter(oleShapes::add)
|
||||||
|
.map(TestEmbedOLEPackage::extractOle10Native)
|
||||||
|
.filter(ole10s::add)
|
||||||
|
.map(TestEmbedOLEPackage::digest)
|
||||||
|
.allMatch("FUJBVHTAZ0ly/TNDNmEj1gQ4a2TbZwDMVF4WUkDQLaM="::equals);
|
||||||
|
|
||||||
|
assertEquals(2, oleShapes.size());
|
||||||
|
assertEquals("Package", oleShapes.get(0).getOLE2ClassName());
|
||||||
|
assertEquals("Package2", oleShapes.get(1).getOLE2ClassName());
|
||||||
|
assertTrue(digestMatch);
|
||||||
|
|
||||||
|
final String expLabel = "Apache_POI_project_logo_(2018).pdf";
|
||||||
|
final String expFilenName = "C:\\Dell\\Apache_POI_project_logo_(2018).pdf";
|
||||||
|
final String expCmd1 = "C:\\Users\\KIWIWI~1\\AppData\\Local\\Temp\\{84287F34-B79C-4F3A-9A92-6BB664586F48}\\Apache_POI_project_logo_(2018).pdf";
|
||||||
|
final String expCmd2 = "C:\\Users\\KIWIWI~1\\AppData\\Local\\Temp\\{84287F34-B79C-4F3A-9A92-6BB664586F48}\\Apache_POI_project_logo_(2).pdf";
|
||||||
|
|
||||||
|
assertTrue(ole10s.stream().map(Ole10Native::getLabel).allMatch(expLabel::equals));
|
||||||
|
assertTrue(ole10s.stream().map(Ole10Native::getFileName).allMatch(expFilenName::equals));
|
||||||
|
assertEquals(expCmd1, ole10s.get(0).getCommand());
|
||||||
|
assertEquals(expCmd2, ole10s.get(1).getCommand());
|
||||||
|
|
||||||
|
for (Ole10Native o : ole10s) {
|
||||||
|
assertEquals(o.getLabel(), o.getLabel2());
|
||||||
|
assertEquals(o.getCommand(), o.getCommand2());
|
||||||
|
assertEquals(o.getFileName(), o.getFileName2());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ole10Native scratch = new Ole10Native(expLabel, expFilenName, expCmd1, ole10s.get(0).getDataBuffer());
|
||||||
|
scratch.setLabel2(expLabel);
|
||||||
|
scratch.setFileName2(expFilenName);
|
||||||
|
scratch.setCommand2(expCmd1);
|
||||||
|
|
||||||
|
try (POIFSFileSystem scratchFS = new POIFSFileSystem();
|
||||||
|
POIFSFileSystem ole1FS = new POIFSFileSystem(new ByteArrayInputStream(oleShapes.get(0).getObjectData()))) {
|
||||||
|
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||||
|
scratch.writeOut(bos);
|
||||||
|
scratchFS.createDocument(new ByteArrayInputStream(bos.toByteArray()), Ole10Native.OLE10_NATIVE);
|
||||||
|
scratchFS.getRoot().setStorageClsid(ClassIDPredefined.OLE_V1_PACKAGE.getClassID());
|
||||||
|
assertTrue(EntryUtils.areDirectoriesIdentical(ole1FS.getRoot(), scratchFS.getRoot()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Ole10Native extractOle10Native(XSSFObjectData objectData) {
|
||||||
|
try (InputStream is = objectData.getObjectPart().getInputStream();
|
||||||
|
POIFSFileSystem poifs = new POIFSFileSystem(is)) {
|
||||||
|
return Ole10Native.createFromEmbeddedOleObject(poifs);
|
||||||
|
} catch (IOException | Ole10NativeException e) {
|
||||||
|
throw new AssertionError(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String digest(Ole10Native ole10) {
|
||||||
|
MessageDigest sha = CryptoFunctions.getMessageDigest(HashAlgorithm.sha256);
|
||||||
|
byte[] digest = sha.digest(ole10.getDataBuffer());
|
||||||
|
return Base64.encodeBase64String(digest);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void embedXSSF() throws IOException {
|
public void embedXSSF() throws IOException {
|
||||||
Workbook wb1 = new XSSFWorkbook();
|
Workbook wb1 = new XSSFWorkbook();
|
||||||
|
@ -71,9 +156,9 @@ public class TestEmbedOLEPackage {
|
||||||
public void embedHSSF() throws IOException {
|
public void embedHSSF() throws IOException {
|
||||||
assumeFalse(xslfOnly());
|
assumeFalse(xslfOnly());
|
||||||
|
|
||||||
Workbook wb1 = new HSSFWorkbook();
|
HSSFWorkbook wb1 = new HSSFWorkbook();
|
||||||
addEmbeddedObjects(wb1);
|
addEmbeddedObjects(wb1);
|
||||||
Workbook wb2 = HSSFTestDataSamples.writeOutAndReadBack((HSSFWorkbook)wb1);
|
Workbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb1);
|
||||||
validateEmbeddedObjects(wb2);
|
validateEmbeddedObjects(wb2);
|
||||||
|
|
||||||
wb2.close();
|
wb2.close();
|
||||||
|
@ -97,17 +182,17 @@ public class TestEmbedOLEPackage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void addEmbeddedObjects(Workbook wb) throws IOException {
|
static void addEmbeddedObjects(Workbook wb) throws IOException {
|
||||||
boolean ooxml = wb.getClass().getName().toLowerCase(Locale.ROOT).contains("xssf");
|
boolean ooxml = wb.getClass().getName().toLowerCase(Locale.ROOT).contains("xssf");
|
||||||
int picIdx = wb.addPicture(samplePNG, Workbook.PICTURE_TYPE_PNG);
|
int picIdx = wb.addPicture(samplePNG, Workbook.PICTURE_TYPE_PNG);
|
||||||
byte[] data = (ooxml) ? samplePPTX : samplePPT;
|
byte[] data = (ooxml) ? samplePPTX : samplePPT;
|
||||||
String ext = (ooxml) ? ".pptx" : ".ppt";
|
String ext = (ooxml) ? ".pptx" : ".ppt";
|
||||||
|
|
||||||
int oleIdx1a = wb.addOlePackage(data, "dummy1a"+ext, "dummy1a"+ext, "dummy1a"+ext);
|
int oleIdx1a = wb.addOlePackage(data, "dummy1a"+ext, "dummy1a"+ext, "dummy1a"+ext);
|
||||||
int oleIdx1b = wb.addOlePackage(data, "dummy1b"+ext, "dummy1b"+ext, "dummy1b"+ext);
|
int oleIdx1b = wb.addOlePackage(data, "dummy1b"+ext, "dummy1b"+ext, "dummy1b"+ext);
|
||||||
int oleIdx2 = wb.addOlePackage(data, "dummy2"+ext, "dummy2"+ext, "dummy2"+ext);
|
int oleIdx2 = wb.addOlePackage(data, "dummy2"+ext, "dummy2"+ext, "dummy2"+ext);
|
||||||
|
|
||||||
Sheet sh1 = wb.createSheet();
|
Sheet sh1 = wb.createSheet();
|
||||||
Drawing<?> pat1 = sh1.createDrawingPatriarch();
|
Drawing<?> pat1 = sh1.createDrawingPatriarch();
|
||||||
ClientAnchor anchor1a = pat1.createAnchor(0, 0, 0, 0, 1, 1, 3, 6);
|
ClientAnchor anchor1a = pat1.createAnchor(0, 0, 0, 0, 1, 1, 3, 6);
|
||||||
|
@ -120,7 +205,7 @@ public class TestEmbedOLEPackage {
|
||||||
ClientAnchor anchor2 = pat2.createAnchor(0, 0, 0, 0, 1, 1, 3, 6);
|
ClientAnchor anchor2 = pat2.createAnchor(0, 0, 0, 0, 1, 1, 3, 6);
|
||||||
pat2.createObjectData(anchor2, oleIdx2, picIdx);
|
pat2.createObjectData(anchor2, oleIdx2, picIdx);
|
||||||
}
|
}
|
||||||
|
|
||||||
static byte[] getSamplePPT(boolean ooxml) throws IOException, ReflectiveOperationException {
|
static byte[] getSamplePPT(boolean ooxml) throws IOException, ReflectiveOperationException {
|
||||||
SlideShow<?,?> ppt = (ooxml) ? new XMLSlideShow()
|
SlideShow<?,?> ppt = (ooxml) ? new XMLSlideShow()
|
||||||
: (SlideShow<?,?>)Class.forName("org.apache.poi.hslf.usermodel.HSLFSlideShow").newInstance();
|
: (SlideShow<?,?>)Class.forName("org.apache.poi.hslf.usermodel.HSLFSlideShow").newInstance();
|
||||||
|
|
|
@ -17,11 +17,9 @@
|
||||||
|
|
||||||
package org.apache.poi.poifs.filesystem;
|
package org.apache.poi.poifs.filesystem;
|
||||||
|
|
||||||
import static org.apache.poi.POITestCase.assertContains;
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
import static org.hamcrest.core.IsEqual.equalTo;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -33,11 +31,17 @@ import java.util.List;
|
||||||
|
|
||||||
import org.apache.poi.POIDataSamples;
|
import org.apache.poi.POIDataSamples;
|
||||||
import org.apache.poi.util.IOUtils;
|
import org.apache.poi.util.IOUtils;
|
||||||
|
import org.apache.poi.util.RecordFormatException;
|
||||||
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
public class TestOle10Native {
|
public class TestOle10Native {
|
||||||
private static final POIDataSamples dataSamples = POIDataSamples.getPOIFSInstance();
|
private static final POIDataSamples dataSamples = POIDataSamples.getPOIFSInstance();
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ExpectedException thrown = ExpectedException.none();
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOleNative() throws IOException, Ole10NativeException {
|
public void testOleNative() throws IOException, Ole10NativeException {
|
||||||
POIFSFileSystem fs = new POIFSFileSystem(dataSamples.openResourceAsStream("oleObject1.bin"));
|
POIFSFileSystem fs = new POIFSFileSystem(dataSamples.openResourceAsStream("oleObject1.bin"));
|
||||||
|
@ -59,26 +63,26 @@ public class TestOle10Native {
|
||||||
POIDataSamples.getDocumentInstance().getFile("Bug53380_3.doc"),
|
POIDataSamples.getDocumentInstance().getFile("Bug53380_3.doc"),
|
||||||
POIDataSamples.getDocumentInstance().getFile("Bug47731.doc")
|
POIDataSamples.getDocumentInstance().getFile("Bug47731.doc")
|
||||||
};
|
};
|
||||||
|
|
||||||
for (File f : files) {
|
for (File f : files) {
|
||||||
POIFSFileSystem fs = new POIFSFileSystem(f, true);
|
POIFSFileSystem fs = new POIFSFileSystem(f, true);
|
||||||
List<Entry> entries = new ArrayList<>();
|
List<Entry> entries = new ArrayList<>();
|
||||||
findOle10(entries, fs.getRoot(), "/");
|
findOle10(entries, fs.getRoot(), "/");
|
||||||
|
|
||||||
for (Entry e : entries) {
|
for (Entry e : entries) {
|
||||||
ByteArrayOutputStream bosExp = new ByteArrayOutputStream();
|
ByteArrayOutputStream bosExp = new ByteArrayOutputStream();
|
||||||
InputStream is = ((DirectoryNode)e.getParent()).createDocumentInputStream(e);
|
InputStream is = ((DirectoryNode)e.getParent()).createDocumentInputStream(e);
|
||||||
IOUtils.copy(is,bosExp);
|
IOUtils.copy(is,bosExp);
|
||||||
is.close();
|
is.close();
|
||||||
|
|
||||||
Ole10Native ole = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)e.getParent());
|
Ole10Native ole = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)e.getParent());
|
||||||
|
|
||||||
ByteArrayOutputStream bosAct = new ByteArrayOutputStream();
|
ByteArrayOutputStream bosAct = new ByteArrayOutputStream();
|
||||||
ole.writeOut(bosAct);
|
ole.writeOut(bosAct);
|
||||||
|
|
||||||
assertThat(bosExp.toByteArray(), equalTo(bosAct.toByteArray()));
|
assertThat(bosExp.toByteArray(), equalTo(bosAct.toByteArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.close();
|
fs.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,14 +101,11 @@ public class TestOle10Native {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOleNativeOOM() throws IOException {
|
public void testOleNativeOOM() throws IOException, Ole10NativeException {
|
||||||
POIFSFileSystem fs = new POIFSFileSystem(dataSamples.openResourceAsStream("60256.bin"));
|
POIFSFileSystem fs = new POIFSFileSystem(dataSamples.openResourceAsStream("60256.bin"));
|
||||||
try {
|
thrown.expect(RecordFormatException.class);
|
||||||
Ole10Native.createFromEmbeddedOleObject(fs);
|
thrown.expectMessage("Tried to allocate");
|
||||||
fail("Should have thrown exception because OLENative lacks a length parameter");
|
Ole10Native.createFromEmbeddedOleObject(fs);
|
||||||
} catch (Ole10NativeException e) {
|
|
||||||
assertContains(e.getMessage(), "declared data length");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue