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:
Andreas Beeker 2020-06-11 00:43:45 +00:00
parent 0181d2abd9
commit d559feb7de
13 changed files with 411 additions and 232 deletions

View File

@ -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) {

View File

@ -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);
} }
} }

View File

@ -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;
}
}

View File

@ -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());
} }
} }

View File

@ -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 &quot;{01}Ole10Native&quot; which contains the actual * to include a stream &quot;{01}Ole10Native&quot; 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;
}
} }

View File

@ -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);
}
} }

View File

@ -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);
}
} }

View File

@ -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);

View File

@ -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(

View File

@ -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);

View File

@ -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();

View File

@ -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.