Author: kiwiwings Date: Thu Jun 11 00:43:45 2020 New Revision: 1878730 URL: http://svn.apache.org/viewvc?rev=1878730&view=rev Log: 64512 - Ole10Native aka embedded / object packager - handle UTF16 variants
Added: poi/trunk/test-data/spreadsheet/bug64512_embed.xlsx (with props) Modified: poi/site/src/documentation/content/xdocs/changes.xml poi/trunk/src/examples/src/org/apache/poi/hpsf/examples/CopyCompare.java poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReaderEvent.java poi/trunk/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10NativeException.java poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java poi/trunk/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestEmbedOLEPackage.java poi/trunk/src/testcases/org/apache/poi/poifs/filesystem/TestOle10Native.java Modified: poi/site/src/documentation/content/xdocs/changes.xml URL: http://svn.apache.org/viewvc/poi/site/src/documentation/content/xdocs/changes.xml?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/site/src/documentation/content/xdocs/changes.xml (original) +++ poi/site/src/documentation/content/xdocs/changes.xml Thu Jun 11 00:43:45 2020 @@ -102,6 +102,7 @@ <action type="add" fixes-bug="63819" context="SS_Common">Support DateValue function</action> <action type="add" fixes-bug="github-179" context="SS_Common">Add an option for RangeCopier.copyRange() also clone styles</action> <action type="fix" fixes-bug="63290" context="XSLF">Retrieve default run properties from paragraph</action> + <action type="add" fixes-bug="64512" context="POIFS">Ole10Native aka embedded / object packager - handle UTF16 variants</action> </actions> </release> Modified: poi/trunk/src/examples/src/org/apache/poi/hpsf/examples/CopyCompare.java URL: http://svn.apache.org/viewvc/poi/trunk/src/examples/src/org/apache/poi/hpsf/examples/CopyCompare.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/examples/src/org/apache/poi/hpsf/examples/CopyCompare.java (original) +++ poi/trunk/src/examples/src/org/apache/poi/hpsf/examples/CopyCompare.java Thu Jun 11 00:43:45 2020 @@ -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) { Modified: poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java (original) +++ poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReader.java Thu Jun 11 00:43:45 2020 @@ -15,7 +15,7 @@ See the License for the specific language governing permissions and limitations under the License. ==================================================================== */ - + 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.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); } } Modified: poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReaderEvent.java URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReaderEvent.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReaderEvent.java (original) +++ poi/trunk/src/java/org/apache/poi/poifs/eventfilesystem/POIFSReaderEvent.java Thu Jun 11 00:43:45 2020 @@ -1,4 +1,3 @@ - /* ==================================================================== Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with @@ -15,67 +14,63 @@ See the License for the specific language governing permissions and 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; + } +} \ No newline at end of file Modified: poi/trunk/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java (original) +++ poi/trunk/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java Thu Jun 11 00:43:45 2020 @@ -60,7 +60,7 @@ public final class EntryUtils { /** * Copies all the nodes from one POIFS Directory to another - * + * * @param sourceRoot * is the source Directory to copy from * @param targetRoot @@ -75,7 +75,7 @@ public final class EntryUtils { /** * Copies all nodes from one POIFS to the other - * + * * @param source * is the source POIFS to copy from * @param target @@ -85,13 +85,13 @@ public final class EntryUtils { throws IOException { copyNodes( source.getRoot(), target.getRoot() ); } - + /** * Copies nodes from one POIFS to the other, minus the excepts. * This delegates the filtering work to {@link FilteringDirectoryNode}, * so excepts can be of the form "NodeToExclude" or * "FilteringDirectory/ExcludedChildNode" - * + * * @param source is the source POIFS to copy from * @param target is the target POIFS to copy to * @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) ); } - + /** * 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} */ public static boolean areDirectoriesIdentical(DirectoryEntry dirA, DirectoryEntry dirB) { return new DirectoryDelegate(dirA).equals(new DirectoryDelegate(dirB)); } - + /** * Compares two {@link DocumentEntry} instances of a POI file system. * Documents that are not property set streams must be bitwise identical. @@ -185,6 +189,10 @@ public final class EntryUtils { return false; } + if (!dir.getStorageClsid().equals(dd.dir.getStorageClsid())) { + return false; + } + return entries().equals(dd.entries()); } } Modified: poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java (original) +++ poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java Thu Jun 11 00:43:45 2020 @@ -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); - - // 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; + 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); + + 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); } /** @@ -336,15 +358,6 @@ public class Ole10Native { } /** - * 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 * around, but non atom classes will just request the bytes from their @@ -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 .. - - 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 ... - - 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; + 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()); + + 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; + } } Modified: poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10NativeException.java URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10NativeException.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10NativeException.java (original) +++ poi/trunk/src/java/org/apache/poi/poifs/filesystem/Ole10NativeException.java Thu Jun 11 00:43:45 2020 @@ -21,4 +21,12 @@ public class Ole10NativeException extend public Ole10NativeException(String message) { super(message); } + + public Ole10NativeException(Throwable cause) { + super(cause); + } + + public Ole10NativeException(String message, Throwable cause) { + super(message, cause); + } } Modified: poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java URL: http://svn.apache.org/viewvc/poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java (original) +++ poi/trunk/src/java/org/apache/poi/util/LittleEndianByteArrayInputStream.java Thu Jun 11 00:43:45 2020 @@ -91,8 +91,8 @@ public class LittleEndianByteArrayInputS } this.pos = pos; } - - + + @Override public byte readByte() { checkPosition(1); @@ -140,14 +140,14 @@ public class LittleEndianByteArrayInputS } public long readUInt() { - return readInt() & 0x00FFFFFFFFL; + return readInt() & 0x00FFFFFFFFL; } @Override public double readDouble() { return Double.longBitsToDouble(readLong()); } - + @Override public void readFully(byte[] buffer, int off, int len) { checkPosition(len); @@ -164,4 +164,12 @@ public class LittleEndianByteArrayInputS 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); + } } Modified: poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java (original) +++ poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFDrawing.java Thu Jun 11 00:43:45 2020 @@ -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 P 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); Modified: poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java (original) +++ poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFRelation.java Thu Jun 11 00:43:45 2020 @@ -247,9 +247,9 @@ public final class XSSFRelation extends ); 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( Modified: poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java (original) +++ poi/trunk/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java Thu Jun 11 00:43:45 2020 @@ -2383,19 +2383,20 @@ public class XSSFWorkbook extends POIXML @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); Modified: poi/trunk/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestEmbedOLEPackage.java URL: http://svn.apache.org/viewvc/poi/trunk/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestEmbedOLEPackage.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestEmbedOLEPackage.java (original) +++ poi/trunk/src/ooxml/testcases/org/apache/poi/ss/usermodel/TestEmbedOLEPackage.java Thu Jun 11 00:43:45 2020 @@ -23,16 +23,29 @@ import static org.junit.Assert.assertEqu 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,20 +54,92 @@ import org.apache.poi.ss.extractor.Embed 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; 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 public void embedXSSF() throws IOException { Workbook wb1 = new XSSFWorkbook(); @@ -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(); @@ -97,17 +182,17 @@ public class TestEmbedOLEPackage { } } } - + static void addEmbeddedObjects(Workbook wb) throws IOException { boolean ooxml = wb.getClass().getName().toLowerCase(Locale.ROOT).contains("xssf"); int picIdx = wb.addPicture(samplePNG, Workbook.PICTURE_TYPE_PNG); byte[] data = (ooxml) ? samplePPTX : samplePPT; String ext = (ooxml) ? ".pptx" : ".ppt"; - + int oleIdx1a = wb.addOlePackage(data, "dummy1a"+ext, "dummy1a"+ext, "dummy1a"+ext); int oleIdx1b = wb.addOlePackage(data, "dummy1b"+ext, "dummy1b"+ext, "dummy1b"+ext); int oleIdx2 = wb.addOlePackage(data, "dummy2"+ext, "dummy2"+ext, "dummy2"+ext); - + Sheet sh1 = wb.createSheet(); Drawing<?> pat1 = sh1.createDrawingPatriarch(); 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); pat2.createObjectData(anchor2, oleIdx2, picIdx); } - + static byte[] getSamplePPT(boolean ooxml) throws IOException, ReflectiveOperationException { SlideShow<?,?> ppt = (ooxml) ? new XMLSlideShow() : (SlideShow<?,?>)Class.forName("org.apache.poi.hslf.usermodel.HSLFSlideShow").newInstance(); Modified: poi/trunk/src/testcases/org/apache/poi/poifs/filesystem/TestOle10Native.java URL: http://svn.apache.org/viewvc/poi/trunk/src/testcases/org/apache/poi/poifs/filesystem/TestOle10Native.java?rev=1878730&r1=1878729&r2=1878730&view=diff ============================================================================== --- poi/trunk/src/testcases/org/apache/poi/poifs/filesystem/TestOle10Native.java (original) +++ poi/trunk/src/testcases/org/apache/poi/poifs/filesystem/TestOle10Native.java Thu Jun 11 00:43:45 2020 @@ -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")); @@ -59,26 +63,26 @@ public class TestOle10Native { POIDataSamples.getDocumentInstance().getFile("Bug53380_3.doc"), POIDataSamples.getDocumentInstance().getFile("Bug47731.doc") }; - + for (File f : files) { POIFSFileSystem fs = new POIFSFileSystem(f, true); List<Entry> entries = new ArrayList<>(); findOle10(entries, fs.getRoot(), "/"); - + for (Entry e : entries) { ByteArrayOutputStream bosExp = new ByteArrayOutputStream(); InputStream is = ((DirectoryNode)e.getParent()).createDocumentInputStream(e); IOUtils.copy(is,bosExp); is.close(); - + Ole10Native ole = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)e.getParent()); - + ByteArrayOutputStream bosAct = new ByteArrayOutputStream(); ole.writeOut(bosAct); - + assertThat(bosExp.toByteArray(), equalTo(bosAct.toByteArray())); } - + fs.close(); } } @@ -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); } } Added: poi/trunk/test-data/spreadsheet/bug64512_embed.xlsx URL: http://svn.apache.org/viewvc/poi/trunk/test-data/spreadsheet/bug64512_embed.xlsx?rev=1878730&view=auto ============================================================================== Binary file - no diff available. Propchange: poi/trunk/test-data/spreadsheet/bug64512_embed.xlsx ------------------------------------------------------------------------------ svn:executable = * Propchange: poi/trunk/test-data/spreadsheet/bug64512_embed.xlsx ------------------------------------------------------------------------------ --- svn:mime-type (added) +++ svn:mime-type Thu Jun 11 00:43:45 2020 @@ -0,0 +1 @@ +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@poi.apache.org For additional commands, e-mail: commits-h...@poi.apache.org