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.
|
||||
// Get the root directory. It does not have to be created since it always exists in a POIFS.
|
||||
DirectoryEntry de = poiFs.getRoot();
|
||||
if ("/".equals(path.toString())) {
|
||||
de.setStorageClsid(event.getStorageClassId());
|
||||
}
|
||||
|
||||
for (int i=0; i<path.length(); 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) {
|
||||
|
|
|
@ -25,12 +25,12 @@ import java.io.InputStream;
|
|||
|
||||
import org.apache.poi.poifs.filesystem.DocumentInputStream;
|
||||
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.POIFSFileSystem;
|
||||
import org.apache.poi.poifs.property.DirectoryProperty;
|
||||
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.PropertyTable;
|
||||
import org.apache.poi.poifs.property.RootProperty;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
|
||||
|
@ -228,7 +228,7 @@ public class POIFSReader
|
|||
document = new POIFSDocument((DocumentProperty)property, poifs);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -240,7 +240,7 @@ public class POIFSReader
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
/* ====================================================================
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
|
@ -16,66 +15,62 @@
|
|||
limitations under the License.
|
||||
==================================================================== */
|
||||
|
||||
|
||||
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.POIFSDocumentPath;
|
||||
|
||||
/**
|
||||
* 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 POIFSDocumentPath path;
|
||||
private final String documentName;
|
||||
private final POIFSDocumentPath path;
|
||||
private final String documentName;
|
||||
private final ClassID storageClassId;
|
||||
|
||||
/**
|
||||
* package scoped constructor
|
||||
*
|
||||
* @param stream the DocumentInputStream, freshly opened
|
||||
* @param path the path of the document
|
||||
* @param stream the DocumentInputStream, freshly opened
|
||||
* @param path the path of the document
|
||||
* @param documentName the name of the document
|
||||
*/
|
||||
|
||||
POIFSReaderEvent(final DocumentInputStream stream,
|
||||
final POIFSDocumentPath path, final String documentName)
|
||||
{
|
||||
this.stream = stream;
|
||||
this.path = path;
|
||||
final POIFSDocumentPath path, final String documentName, final ClassID storageClassId) {
|
||||
this.stream = stream;
|
||||
this.path = path;
|
||||
this.documentName = documentName;
|
||||
this.storageClassId = storageClassId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the DocumentInputStream, freshly opened
|
||||
*/
|
||||
|
||||
public DocumentInputStream getStream()
|
||||
{
|
||||
public DocumentInputStream getStream() {
|
||||
return stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the document's path
|
||||
*/
|
||||
|
||||
public POIFSDocumentPath getPath()
|
||||
{
|
||||
public POIFSDocumentPath getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the document's name
|
||||
*/
|
||||
|
||||
public String getName()
|
||||
{
|
||||
public String getName() {
|
||||
return documentName;
|
||||
}
|
||||
} // end public class POIFSReaderEvent
|
||||
|
||||
/**
|
||||
* @return the storage class id of the path
|
||||
*/
|
||||
public ClassID getStorageClassId() {
|
||||
return storageClassId;
|
||||
}
|
||||
}
|
|
@ -106,9 +106,13 @@ public final class EntryUtils {
|
|||
|
||||
/**
|
||||
* Checks to see if the two Directories hold the same contents.
|
||||
* For this to be true, they must have entries with the same names,
|
||||
* no entries in one but not the other, and the size+contents
|
||||
* of each entry must match, and they must share names.
|
||||
* For this to be true ...
|
||||
* <ul>
|
||||
* <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,
|
||||
* use a {@link FilteringDirectoryNode}
|
||||
*/
|
||||
|
@ -185,6 +189,10 @@ public final class EntryUtils {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!dir.getStorageClsid().equals(dd.dir.getStorageClsid())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return entries().equals(dd.entries());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,44 +21,69 @@ import java.io.ByteArrayInputStream;
|
|||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
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.LittleEndian;
|
||||
import org.apache.poi.util.LittleEndianByteArrayInputStream;
|
||||
import org.apache.poi.util.LittleEndianConsts;
|
||||
import org.apache.poi.util.LittleEndianInput;
|
||||
import org.apache.poi.util.LittleEndianOutputStream;
|
||||
import org.apache.poi.util.StringUtil;
|
||||
|
||||
/**
|
||||
* 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 static final String OLE10_NATIVE = "\u0001Ole10Native";
|
||||
protected static final String ISO1 = "ISO-8859-1";
|
||||
//arbitrarily selected; may need to increase
|
||||
private static final Charset ISO1 = StandardCharsets.ISO_8859_1;
|
||||
// arbitrarily selected; may need to increase
|
||||
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
|
||||
*/
|
||||
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";
|
||||
|
||||
|
||||
|
||||
// (the fields as they appear in the raw record:)
|
||||
private int totalSize; // 4 bytes, total size of record not including this field
|
||||
private short flags1 = 2; // 2 bytes, unknown, mostly [02 00]
|
||||
private String label; // ASCIIZ, stored in this field without the terminating zero
|
||||
private String fileName; // ASCIIZ, stored in this field without the terminating zero
|
||||
private short flags2; // 2 bytes, unknown, mostly [00 00]
|
||||
private short unknown1 = 3; // see below
|
||||
private String command; // ASCIIZ, stored in this field without the terminating zero
|
||||
private byte[] dataBuffer; // varying size, the actual native data
|
||||
private short flags3; // some final flags? or zero terminators?, sometimes not there
|
||||
// 4 bytes, total size of record not including this field
|
||||
private int totalSize;
|
||||
// 2 bytes, unknown, mostly [02 00]
|
||||
private short flags1 = 2;
|
||||
// ASCIIZ, stored in this field without the terminating zero
|
||||
private String label;
|
||||
// ASCIIZ, stored in this field without the terminating zero
|
||||
private String fileName;
|
||||
// 2 bytes, unknown, mostly [00 00]
|
||||
private short flags2;
|
||||
// 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 ...
|
||||
|
@ -81,7 +106,6 @@ public class Ole10Native {
|
|||
private EncodingMode mode;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -89,11 +113,11 @@ public class Ole10Native {
|
|||
*
|
||||
* @param poifs POI Filesystem object
|
||||
* @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
|
||||
*/
|
||||
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
|
||||
* @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
|
||||
*/
|
||||
public static Ole10Native createFromEmbeddedOleObject(DirectoryNode directory) throws IOException, Ole10NativeException {
|
||||
DocumentEntry nativeEntry = (DocumentEntry)directory.getEntry(OLE10_NATIVE);
|
||||
try (DocumentInputStream dis = directory.createDocumentInputStream(nativeEntry)) {
|
||||
byte[] data = IOUtils.toByteArray(dis, nativeEntry.getSize(), MAX_RECORD_LENGTH);
|
||||
return new Ole10Native(data, 0);
|
||||
}
|
||||
DocumentEntry nativeEntry = (DocumentEntry) directory.getEntry(OLE10_NATIVE);
|
||||
try (DocumentInputStream dis = directory.createDocumentInputStream(nativeEntry)) {
|
||||
byte[] data = IOUtils.toByteArray(dis, nativeEntry.getSize(), MAX_RECORD_LENGTH);
|
||||
return new Ole10Native(data, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance and fills the fields based on ... the fields
|
||||
*/
|
||||
public Ole10Native(String label, String filename, String command, byte[] data) {
|
||||
setLabel(label);
|
||||
setFileName(filename);
|
||||
setCommand(command);
|
||||
setDataBuffer(data);
|
||||
mode = EncodingMode.parsed;
|
||||
setLabel(label);
|
||||
setFileName(filename);
|
||||
setCommand(command);
|
||||
command2 = command;
|
||||
setDataBuffer(data);
|
||||
mode = EncodingMode.parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -132,81 +157,64 @@ public class Ole10Native {
|
|||
* @param offset The start offset of the record in the buffer
|
||||
* @throws Ole10NativeException on invalid or unexcepted data format
|
||||
*/
|
||||
public Ole10Native(byte[] data, int offset) throws Ole10NativeException {
|
||||
int ofs = offset; // current offset, initialized to start
|
||||
public Ole10Native(final byte[] data, final int offset) throws Ole10NativeException {
|
||||
LittleEndianByteArrayInputStream leis = new LittleEndianByteArrayInputStream(data, offset);
|
||||
|
||||
if (data.length < offset + 2) {
|
||||
throw new Ole10NativeException("data is too small");
|
||||
}
|
||||
totalSize = leis.readInt();
|
||||
leis.limit(totalSize + LittleEndianConsts.INT_SIZE);
|
||||
|
||||
totalSize = LittleEndian.getInt(data, ofs);
|
||||
ofs += LittleEndianConsts.INT_SIZE;
|
||||
leis.mark(0);
|
||||
|
||||
mode = EncodingMode.unparsed;
|
||||
if (LittleEndian.getShort(data, ofs) == 2) {
|
||||
// some files like equations don't have a valid filename,
|
||||
// but somehow encode the formula right away in the ole10 header
|
||||
if (Character.isISOControl(data[ofs+LittleEndianConsts.SHORT_SIZE])) {
|
||||
mode = EncodingMode.compact;
|
||||
try {
|
||||
flags1 = leis.readShort();
|
||||
if (flags1 == 2) {
|
||||
leis.mark(0);
|
||||
// some files like equations don't have a valid filename,
|
||||
// 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 {
|
||||
mode = EncodingMode.parsed;
|
||||
leis.reset();
|
||||
readUnparsed(leis);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new Ole10NativeException("Invalid Ole10Native", e);
|
||||
}
|
||||
}
|
||||
|
||||
int dataSize;
|
||||
switch (mode) {
|
||||
case parsed: {
|
||||
flags1 = LittleEndian.getShort(data, ofs);
|
||||
private void readParsed(LittleEndianByteArrayInputStream leis) throws Ole10NativeException, IOException {
|
||||
mode = EncodingMode.parsed;
|
||||
label = readAsciiZ(leis);
|
||||
fileName = readAsciiZ(leis);
|
||||
flags2 = leis.readShort();
|
||||
unknown1 = leis.readShort();
|
||||
command = readAsciiLen(leis);
|
||||
dataBuffer = IOUtils.toByteArray(leis, leis.readInt(), MAX_RECORD_LENGTH);
|
||||
|
||||
// structured format
|
||||
ofs += LittleEndianConsts.SHORT_SIZE;
|
||||
|
||||
int len = getStringLength(data, ofs);
|
||||
label = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
|
||||
ofs += len;
|
||||
|
||||
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;
|
||||
leis.mark(0);
|
||||
short lowSize = leis.readShort();
|
||||
if (lowSize != 0) {
|
||||
leis.reset();
|
||||
command2 = readUtf16(leis);
|
||||
label2 = readUtf16(leis);
|
||||
fileName2 = readUtf16(leis);
|
||||
}
|
||||
}
|
||||
|
||||
if ((long)dataSize + (long)ofs > (long)data.length) { //cast to avoid overflow
|
||||
throw new Ole10NativeException("Invalid Ole10Native: declared data length > available data");
|
||||
}
|
||||
dataBuffer = IOUtils.safelyClone(data, ofs, dataSize, MAX_RECORD_LENGTH);
|
||||
private void readCompact(LittleEndianByteArrayInputStream leis) throws IOException {
|
||||
mode = EncodingMode.compact;
|
||||
dataBuffer = IOUtils.toByteArray(leis, totalSize - LittleEndianConsts.SHORT_SIZE, 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) {
|
||||
int len = 0;
|
||||
while (len + ofs < data.length && data[ofs + len] != 0) {
|
||||
len++;
|
||||
private static String readAsciiZ(LittleEndianInput is) throws Ole10NativeException {
|
||||
// arbitrary sized buffer - not sure how big strings can get in an Ole10 record
|
||||
byte[] buf = new byte[MAX_STRING_LENGTH];
|
||||
for (int i=0; i<buf.length; i++) {
|
||||
if ((buf[i] = is.readByte()) == 0) {
|
||||
return StringUtil.getFromCompressedUnicode(buf, 0, i);
|
||||
}
|
||||
}
|
||||
len++;
|
||||
return len;
|
||||
throw new Ole10NativeException("AsciiZ string was not null terminated after " + MAX_STRING_LENGTH + " bytes - Exiting.");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* file back out to disk (Normally, atom classes will keep their bytes
|
||||
|
@ -358,40 +371,53 @@ public class Ole10Native {
|
|||
LittleEndianOutputStream leosOut = new LittleEndianOutputStream(out);
|
||||
|
||||
switch (mode) {
|
||||
case parsed: {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
LittleEndianOutputStream leos = new LittleEndianOutputStream(bos);
|
||||
// total size, will be determined later ..
|
||||
case parsed: {
|
||||
ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
try (LittleEndianOutputStream leos = new LittleEndianOutputStream(bos)) {
|
||||
// total size, will be determined later ..
|
||||
|
||||
leos.writeShort(getFlags1());
|
||||
leos.write(getLabel().getBytes(ISO1));
|
||||
leos.write(0);
|
||||
leos.write(getFileName().getBytes(ISO1));
|
||||
leos.write(0);
|
||||
leos.writeShort(getFlags2());
|
||||
leos.writeShort(getUnknown1());
|
||||
leos.writeInt(getCommand().length() + 1);
|
||||
leos.write(getCommand().getBytes(ISO1));
|
||||
leos.write(0);
|
||||
leos.writeInt(getDataSize());
|
||||
leos.write(getDataBuffer());
|
||||
leos.writeShort(getFlags3());
|
||||
leos.close(); // satisfy compiler ...
|
||||
leos.writeShort(getFlags1());
|
||||
leos.write(getLabel().getBytes(ISO1));
|
||||
leos.write(0);
|
||||
leos.write(getFileName().getBytes(ISO1));
|
||||
leos.write(0);
|
||||
leos.writeShort(getFlags2());
|
||||
leos.writeShort(getUnknown1());
|
||||
leos.writeInt(getCommand().length() + 1);
|
||||
leos.write(getCommand().getBytes(ISO1));
|
||||
leos.write(0);
|
||||
leos.writeInt(getDataSize());
|
||||
leos.write(getDataBuffer());
|
||||
|
||||
leosOut.writeInt(bos.size()); // total 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;
|
||||
if (command2 == null || label2 == null || fileName2 == null) {
|
||||
leos.writeShort(0);
|
||||
} else {
|
||||
leos.writeUInt(command2.length());
|
||||
leos.write(StringUtil.getToUnicodeLE(command2));
|
||||
leos.writeUInt(label2.length());
|
||||
leos.write(StringUtil.getToUnicodeLE(label2));
|
||||
leos.writeUInt(fileName2.length());
|
||||
leos.write(StringUtil.getToUnicodeLE(fileName2));
|
||||
}
|
||||
}
|
||||
|
||||
// total size
|
||||
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;
|
||||
}
|
||||
|
||||
public void setFlags3(short flags3) {
|
||||
this.flags3 = flags3;
|
||||
}
|
||||
|
||||
public void setLabel(String label) {
|
||||
this.label = label;
|
||||
}
|
||||
|
@ -427,4 +449,46 @@ public class Ole10Native {
|
|||
public void setDataBuffer(byte[] dataBuffer) {
|
||||
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) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public Ole10NativeException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public Ole10NativeException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -164,4 +164,12 @@ public class LittleEndianByteArrayInputStream extends ByteArrayInputStream imple
|
|||
public void readPlain(byte[] buf, int off, int 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 org.apache.poi.ooxml.POIXMLDocument;
|
||||
import org.apache.poi.ooxml.POIXMLDocumentPart;
|
||||
import org.apache.poi.ooxml.POIXMLException;
|
||||
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();
|
||||
|
||||
// add reference to OLE part
|
||||
final XSSFRelation rel = XSSFRelation.OLEEMBEDDINGS;
|
||||
PackagePartName olePN;
|
||||
try {
|
||||
olePN = PackagingURIHelper.createPartName("/xl/embeddings/oleObject" + storageId + ".bin");
|
||||
olePN = PackagingURIHelper.createPartName(rel.getFileName(storageId));
|
||||
} catch (InvalidFormatException e) {
|
||||
throw new POIXMLException(e);
|
||||
}
|
||||
PackageRelationship olePR = sheetPart.addRelationship(olePN, TargetMode.INTERNAL,
|
||||
POIXMLDocument.OLE_OBJECT_REL_TYPE);
|
||||
PackageRelationship olePR = sheetPart.addRelationship(olePN, TargetMode.INTERNAL, rel.getRelation());
|
||||
|
||||
// add reference to image part
|
||||
XSSFPictureData imgPD = sh.getWorkbook().getAllPictures().get(pictureIndex);
|
||||
|
|
|
@ -247,9 +247,9 @@ public final class XSSFRelation extends POIXMLRelation {
|
|||
);
|
||||
|
||||
public static final XSSFRelation OLEEMBEDDINGS = new XSSFRelation(
|
||||
null,
|
||||
"application/vnd.openxmlformats-officedocument.oleObject",
|
||||
POIXMLDocument.OLE_OBJECT_REL_TYPE,
|
||||
null
|
||||
"/xl/embeddings/oleObject#.bin"
|
||||
);
|
||||
|
||||
public static final XSSFRelation PACKEMBEDDINGS = new XSSFRelation(
|
||||
|
|
|
@ -2383,19 +2383,20 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Date1904Su
|
|||
@Override
|
||||
public int addOlePackage(byte[] oleData, String label, String fileName, String command)
|
||||
throws IOException {
|
||||
final XSSFRelation rel = XSSFRelation.OLEEMBEDDINGS;
|
||||
|
||||
// find an unused part name
|
||||
OPCPackage opc = getPackage();
|
||||
PackagePartName pnOLE;
|
||||
int oleId=0;
|
||||
do {
|
||||
try {
|
||||
pnOLE = PackagingURIHelper.createPartName( "/xl/embeddings/oleObject"+(++oleId)+".bin" );
|
||||
} catch (InvalidFormatException e) {
|
||||
throw new IOException("ole object name not recognized", e);
|
||||
}
|
||||
} while (opc.containPart(pnOLE));
|
||||
int oleId;
|
||||
try {
|
||||
oleId = opc.getUnusedPartIndex(rel.getDefaultFileName());
|
||||
pnOLE = PackagingURIHelper.createPartName(rel.getFileName(oleId));
|
||||
} catch (InvalidFormatException e) {
|
||||
throw new IOException("ole object name not recognized", e);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -23,16 +23,29 @@ import static org.junit.Assert.assertEquals;
|
|||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assume.assumeFalse;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.poi.POIDataSamples;
|
||||
import org.apache.poi.hpsf.ClassIDPredefined;
|
||||
import org.apache.poi.hssf.HSSFTestDataSamples;
|
||||
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.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.ShapeType;
|
||||
import org.apache.poi.sl.usermodel.Slide;
|
||||
|
@ -41,6 +54,7 @@ import org.apache.poi.ss.extractor.EmbeddedData;
|
|||
import org.apache.poi.ss.extractor.EmbeddedExtractor;
|
||||
import org.apache.poi.xslf.usermodel.XMLSlideShow;
|
||||
import org.apache.poi.xssf.XSSFTestDataSamples;
|
||||
import org.apache.poi.xssf.usermodel.XSSFObjectData;
|
||||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
@ -48,11 +62,82 @@ import org.junit.Test;
|
|||
public class TestEmbedOLEPackage {
|
||||
private static byte[] samplePPT, samplePPTX, samplePNG;
|
||||
|
||||
private static final POIDataSamples ssamples = POIDataSamples.getSpreadSheetInstance();
|
||||
|
||||
@BeforeClass
|
||||
public static void init() throws IOException, ReflectiveOperationException {
|
||||
samplePPT = getSamplePPT(false);
|
||||
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
|
||||
|
@ -71,9 +156,9 @@ public class TestEmbedOLEPackage {
|
|||
public void embedHSSF() throws IOException {
|
||||
assumeFalse(xslfOnly());
|
||||
|
||||
Workbook wb1 = new HSSFWorkbook();
|
||||
HSSFWorkbook wb1 = new HSSFWorkbook();
|
||||
addEmbeddedObjects(wb1);
|
||||
Workbook wb2 = HSSFTestDataSamples.writeOutAndReadBack((HSSFWorkbook)wb1);
|
||||
Workbook wb2 = HSSFTestDataSamples.writeOutAndReadBack(wb1);
|
||||
validateEmbeddedObjects(wb2);
|
||||
|
||||
wb2.close();
|
||||
|
|
|
@ -17,11 +17,9 @@
|
|||
|
||||
package org.apache.poi.poifs.filesystem;
|
||||
|
||||
import static org.apache.poi.POITestCase.assertContains;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
|
@ -33,11 +31,17 @@ import java.util.List;
|
|||
|
||||
import org.apache.poi.POIDataSamples;
|
||||
import org.apache.poi.util.IOUtils;
|
||||
import org.apache.poi.util.RecordFormatException;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
public class TestOle10Native {
|
||||
private static final POIDataSamples dataSamples = POIDataSamples.getPOIFSInstance();
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void testOleNative() throws IOException, Ole10NativeException {
|
||||
POIFSFileSystem fs = new POIFSFileSystem(dataSamples.openResourceAsStream("oleObject1.bin"));
|
||||
|
@ -97,14 +101,11 @@ public class TestOle10Native {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testOleNativeOOM() throws IOException {
|
||||
public void testOleNativeOOM() throws IOException, Ole10NativeException {
|
||||
POIFSFileSystem fs = new POIFSFileSystem(dataSamples.openResourceAsStream("60256.bin"));
|
||||
try {
|
||||
Ole10Native.createFromEmbeddedOleObject(fs);
|
||||
fail("Should have thrown exception because OLENative lacks a length parameter");
|
||||
} catch (Ole10NativeException e) {
|
||||
assertContains(e.getMessage(), "declared data length");
|
||||
}
|
||||
thrown.expect(RecordFormatException.class);
|
||||
thrown.expectMessage("Tried to allocate");
|
||||
Ole10Native.createFromEmbeddedOleObject(fs);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue