package org.apache.poi.poifs.filesystem;
-import java.io.ByteArrayOutputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.OutputStream;
-
-import org.apache.poi.util.HexDump;
-import org.apache.poi.util.LittleEndian;
+import java.io.ByteArrayOutputStream;\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+\r
+import org.apache.poi.util.LittleEndian;\r
import org.apache.poi.util.LittleEndianConsts;\r
+import org.apache.poi.util.LittleEndianOutputStream;\r
import org.apache.poi.util.StringUtil;\r
\r
/**\r
*/
public class Ole10Native {
- public static final String OLE10_NATIVE = "\u0001Ole10Native";
- protected static final String ISO1 = "ISO-8859-1";
-
- // (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 = 0; // 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 = 0; // some final flags? or zero terminators?, sometimes not there
-
- /**
- * 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
- * data relevant for this class.\r
- *\r
- * @param poifs POI Filesystem object\r
- * @return Returns an instance of this class\r
- * @throws IOException on IO error\r
- * @throws Ole10NativeException on invalid or unexcepted data format\r
- */\r
- public static Ole10Native createFromEmbeddedOleObject(POIFSFileSystem poifs) throws IOException, Ole10NativeException {\r
- return createFromEmbeddedOleObject(poifs.getRoot());\r
- }\r
+ public static final String OLE10_NATIVE = "\u0001Ole10Native";
+ protected static final String ISO1 = "ISO-8859-1";
+
+ // (the fields as they appear in the raw record:)\r
+ 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 = 0; // 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 = 0; // some final flags? or zero terminators?, sometimes not there
+
+ /**\r
+ * the field encoding mode - merely a try-and-error guess ...\r
+ **/ \r
+ private enum EncodingMode {\r
+ /**\r
+ * the data is stored in parsed format - including label, command, etc.\r
+ */\r
+ parsed,\r
+ /**\r
+ * the data is stored raw after the length field\r
+ */\r
+ unparsed,\r
+ /**\r
+ * the data is stored raw after the length field and the flags1 field\r
+ */\r
+ compact;\r
+ }\r
+ \r
+ private EncodingMode mode;\r
+\r
+ \r
+ \r
+ /**
+ * 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
+ * data relevant for this class.\r
+ *\r
+ * @param poifs POI Filesystem object\r
+ * @return Returns an instance of this class\r
+ * @throws IOException on IO error\r
+ * @throws Ole10NativeException on invalid or unexcepted data format\r
+ */\r
+ public static Ole10Native createFromEmbeddedOleObject(POIFSFileSystem poifs) throws IOException, Ole10NativeException {\r
+ return createFromEmbeddedOleObject(poifs.getRoot());\r
+ }\r
+ \r
+ /**\r
+ * Creates an instance of this class from an embedded OLE Object. The OLE Object is expected\r
+ * to include a stream "{01}Ole10Native" which contains the actual\r
+ * data relevant for this class.\r
+ *\r
+ * @param directory POI Filesystem object\r
+ * @return Returns an instance of this class\r
+ * @throws IOException on IO error\r
+ * @throws Ole10NativeException on invalid or unexcepted data format\r
+ */\r
+ public static Ole10Native createFromEmbeddedOleObject(DirectoryNode directory) throws IOException, Ole10NativeException {\r
+ DocumentEntry nativeEntry = \r
+ (DocumentEntry)directory.getEntry(OLE10_NATIVE);\r
+ byte[] data = new byte[nativeEntry.getSize()];\r
+ directory.createDocumentInputStream(nativeEntry).read(data);\r
\r
- /**\r
- * Creates an instance of this class from an embedded OLE Object. The OLE Object is expected\r
- * to include a stream "{01}Ole10Native" which contains the actual\r
- * data relevant for this class.\r
- *\r
- * @param directory POI Filesystem object\r
- * @return Returns an instance of this class\r
- * @throws IOException on IO error\r
- * @throws Ole10NativeException on invalid or unexcepted data format\r
- */\r
- public static Ole10Native createFromEmbeddedOleObject(DirectoryNode directory) throws IOException, Ole10NativeException {\r
- boolean plain = false;\r
+ return new Ole10Native(data, 0);\r
+ }
+
+ /**
+ * 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);\r
+ mode = EncodingMode.parsed;
+ }
\r
- try {\r
- directory.getEntry("\u0001Ole10ItemName");\r
- plain = true;\r
- } catch (FileNotFoundException ex) {\r
- plain = false;\r
- }\r
- \r
- DocumentEntry nativeEntry = \r
- (DocumentEntry)directory.getEntry(OLE10_NATIVE);\r
- byte[] data = new byte[nativeEntry.getSize()];\r
- directory.createDocumentInputStream(nativeEntry).read(data);\r
+ /**\r
+ * Creates an instance and fills the fields based on the data in the given buffer.\r
+ *\r
+ * @param data The buffer containing the Ole10Native record\r
+ * @param offset The start offset of the record in the buffer\r
+ * @param plain as of POI 3.11 this parameter is ignored\r
+ * @throws Ole10NativeException on invalid or unexcepted data format\r
+ * \r
+ * @deprecated parameter plain is ignored, use {@link #Ole10Native(byte[],int)}\r
+ */\r
+ public Ole10Native(byte[] data, int offset, boolean plain) throws Ole10NativeException {\r
+ this(data, offset);\r
+ }\r
+
+ /**
+ * Creates an instance and fills the fields based on the data in the given buffer.
+ *
+ * @param data The buffer containing the Ole10Native record
+ * @param offset The start offset of the record in the buffer\r
+ * @throws Ole10NativeException on invalid or unexcepted data format\r
+ */\r
+ public Ole10Native(byte[] data, int offset) throws Ole10NativeException {\r
+ int ofs = offset; // current offset, initialized to start\r
+ \r
+ if (data.length < offset + 2) {\r
+ throw new Ole10NativeException("data is too small");\r
+ }\r
+ \r
+ totalSize = LittleEndian.getInt(data, ofs);\r
+ ofs += LittleEndianConsts.INT_SIZE;\r
+ \r
+ mode = EncodingMode.unparsed;\r
+ if (LittleEndian.getShort(data, ofs) == 2) {\r
+ // some files like equations don't have a valid filename,\r
+ // but somehow encode the formula right away in the ole10 header\r
+ if (Character.isISOControl(data[ofs+LittleEndianConsts.SHORT_SIZE])) {\r
+ mode = EncodingMode.compact;\r
+ } else {\r
+ mode = EncodingMode.parsed;\r
+ }\r
+ }\r
\r
- return new Ole10Native(data, 0, plain);\r
- }
-
- /**
- * 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);
- }
-
- /**
- * Creates an instance and fills the fields based on the data in the given buffer.
- *
- * @param data The buffer containing the Ole10Native record
- * @param offset The start offset of the record in the buffer\r
- * @throws Ole10NativeException on invalid or unexcepted data format\r
- */\r
- public Ole10Native(byte[] data, int offset) throws Ole10NativeException {\r
- this(data, offset, false);\r
- }\r
- /**\r
- * Creates an instance and fills the fields based on the data in the given buffer.\r
- *\r
- * @param data The buffer containing the Ole10Native record\r
- * @param offset The start offset of the record in the buffer\r
- * @param plain Specified 'plain' format without filename\r
- * @throws Ole10NativeException on invalid or unexcepted data format\r
- */\r
- public Ole10Native(byte[] data, int offset, boolean plain) throws Ole10NativeException {\r
- int ofs = offset; // current offset, initialized to start\r
+ int dataSize;\r
+ switch (mode) {\r
+ case parsed: {\r
+ flags1 = LittleEndian.getShort(data, ofs);\r
+ \r
+ // structured format\r
+ ofs += LittleEndianConsts.SHORT_SIZE;\r
+ \r
+ int len = getStringLength(data, ofs);\r
+ label = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);\r
+ ofs += len;\r
+ \r
+ len = getStringLength(data, ofs);\r
+ fileName = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);\r
+ ofs += len;\r
+ \r
+ flags2 = LittleEndian.getShort(data, ofs);\r
+ ofs += LittleEndianConsts.SHORT_SIZE;
+
+ unknown1 = LittleEndian.getShort(data, ofs);
+ ofs += LittleEndianConsts.SHORT_SIZE;
+
+ len = LittleEndian.getInt(data, ofs);
+ ofs += LittleEndianConsts.INT_SIZE;\r
+ 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");
+ }\r
+ break;\r
+ }\r
+ case compact:\r
+ flags1 = LittleEndian.getShort(data, ofs);\r
+ ofs += LittleEndianConsts.SHORT_SIZE;\r
+ dataSize = totalSize - LittleEndianConsts.SHORT_SIZE;\r
+ break;\r
+ default:\r
+ case unparsed:\r
+ dataSize = totalSize;\r
+ break;\r
+ }
+
+ dataBuffer = new byte[dataSize];
+ System.arraycopy(data, ofs, dataBuffer, 0, dataSize);
+ ofs += dataSize;
+ }\r
\r
- if (data.length<offset+2) {\r
- throw new Ole10NativeException("data is too small");\r
+ /*\r
+ * Helper - determine length of zero terminated string (ASCIIZ).\r
+ */\r
+ private static int getStringLength(byte[] data, int ofs) {\r
+ int len = 0;\r
+ while (len + ofs < data.length && data[ofs + len] != 0) {\r
+ len++;\r
+ }\r
+ len++;\r
+ return len;\r
}\r
\r
- totalSize = LittleEndian.getInt(data, ofs);\r
- ofs += LittleEndianConsts.INT_SIZE;\r
+ /**\r
+ * Returns the value of the totalSize field - the total length of the\r
+ * structure is totalSize + 4 (value of this field + size of this field).\r
+ * \r
+ * @return the totalSize\r
+ */\r
+ public int getTotalSize() {\r
+ return totalSize;\r
+ }\r
\r
- if (plain) {
- dataBuffer = new byte[totalSize-4];
- System.arraycopy(data, 4, dataBuffer, 0, dataBuffer.length);
- // int dataSize = totalSize - 4;
-
- byte[] oleLabel = new byte[8];
- System.arraycopy(dataBuffer, 0, oleLabel, 0, Math.min(dataBuffer.length, 8));
- label = "ole-"+ HexDump.toHex(oleLabel);\r
- fileName = label;\r
- command = label;\r
- } else {
- flags1 = LittleEndian.getShort(data, ofs);
- 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");
- }
-
- int dataSize = LittleEndian.getInt(data, ofs);
- ofs += LittleEndianConsts.INT_SIZE;
-
- if (dataSize < 0 || totalSize - (ofs - LittleEndianConsts.INT_SIZE) < dataSize) {
- throw new Ole10NativeException("Invalid Ole10Native");
- }
-
- dataBuffer = new byte[dataSize];
- System.arraycopy(data, ofs, dataBuffer, 0, dataSize);
- ofs += dataSize;
- }
- }
-
- /*\r
- * Helper - determine length of zero terminated string (ASCIIZ).\r
- */\r
- private static int getStringLength(byte[] data, int ofs) {\r
- int len = 0;\r
- while (len+ofs<data.length && data[ofs + len] != 0) {\r
- len++;\r
+ /**\r
+ * Returns flags1 - currently unknown - usually 0x0002.\r
+ * \r
+ * @return the flags1\r
+ */\r
+ public short getFlags1() {\r
+ return flags1;\r
}\r
- len++;\r
- return len;\r
- }\r
\r
- /**\r
- * Returns the value of the totalSize field - the total length of the structure\r
- * is totalSize + 4 (value of this field + size of this field).\r
- *\r
- * @return the totalSize\r
- */\r
- public int getTotalSize() {\r
- return totalSize;\r
- }\r
+ /**\r
+ * Returns the label field - usually the name of the file (without\r
+ * directory) but probably may be any name specified during\r
+ * packaging/embedding the data.\r
+ * \r
+ * @return the label\r
+ */\r
+ public String getLabel() {\r
+ return label;\r
+ }\r
\r
- /**\r
- * Returns flags1 - currently unknown - usually 0x0002.\r
- *\r
- * @return the flags1\r
- */\r
- public short getFlags1() {\r
- return flags1;\r
- }\r
+ /**\r
+ * Returns the fileName field - usually the name of the file being embedded\r
+ * including the full path.\r
+ * \r
+ * @return the fileName\r
+ */\r
+ public String getFileName() {\r
+ return fileName;\r
+ }\r
\r
- /**\r
- * Returns the label field - usually the name of the file (without directory) but\r
- * probably may be any name specified during packaging/embedding the data.\r
- *\r
- * @return the label\r
- */\r
- public String getLabel() {\r
- return label;\r
- }\r
+ /**\r
+ * Returns flags2 - currently unknown - mostly 0x0000.\r
+ * \r
+ * @return the flags2\r
+ */\r
+ public short getFlags2() {\r
+ return flags2;\r
+ }\r
\r
- /**\r
- * Returns the fileName field - usually the name of the file being embedded\r
- * including the full path.\r
- *\r
- * @return the fileName\r
- */\r
- public String getFileName() {\r
- return fileName;\r
- }\r
+ /**\r
+ * Returns unknown1 field - currently unknown.\r
+ * \r
+ * @return the unknown1\r
+ */\r
+ public short getUnknown1() {\r
+ return unknown1;\r
+ }\r
\r
- /**\r
- * Returns flags2 - currently unknown - mostly 0x0000.\r
- *\r
- * @return the flags2\r
- */\r
- public short getFlags2() {\r
- return flags2;\r
- }\r
+ /**\r
+ * Returns the command field - usually the name of the file being embedded\r
+ * including the full path, may be a command specified during embedding the\r
+ * file.\r
+ * \r
+ * @return the command\r
+ */\r
+ public String getCommand() {\r
+ return command;\r
+ }\r
\r
- /**\r
- * Returns unknown1 field - currently unknown.\r
- *
- * @return the unknown1
- */
- public short getUnknown1() {
- return unknown1;
- }
-
- /**
- * Returns the command field - usually the name of the file being embedded
- * including the full path, may be a command specified during embedding the file.
- *
- * @return the command\r
- */\r
- public String getCommand() {\r
- return command;\r
- }\r
+ /**\r
+ * Returns the size of the embedded file. If the size is 0 (zero), no data\r
+ * has been embedded. To be sure, that no data has been embedded, check\r
+ * whether {@link #getDataBuffer()} returns <code>null</code>.\r
+ * \r
+ * @return the dataSize\r
+ */\r
+ public int getDataSize() {\r
+ return dataBuffer.length;\r
+ }\r
\r
- /**\r
- * Returns the size of the embedded file. If the size is 0 (zero), no data has been\r
- * embedded. To be sure, that no data has been embedded, check whether\r
- * {@link #getDataBuffer()} returns <code>null</code>.\r
- *\r
- * @return the dataSize
- */
- public int getDataSize() {
- return dataBuffer.length;
- }
-
- /**
- * Returns the buffer containing the embedded file's data, or <code>null</code>\r
- * if no data was embedded. Note that an embedding may provide information about\r
- * the data, but the actual data is not included. (So label, filename etc. are\r
- * available, but this method returns <code>null</code>.)\r
- *\r
- * @return the dataBuffer\r
- */\r
- public byte[] getDataBuffer() {\r
- return dataBuffer;\r
- }\r
+ /**\r
+ * Returns the buffer containing the embedded file's data, or\r
+ * <code>null</code> if no data was embedded. Note that an embedding may\r
+ * provide information about the data, but the actual data is not included.\r
+ * (So label, filename etc. are available, but this method returns\r
+ * <code>null</code>.)\r
+ * \r
+ * @return the dataBuffer\r
+ */\r
+ public byte[] getDataBuffer() {\r
+ return dataBuffer;\r
+ }\r
\r
- /**\r
- * Returns the flags3 - currently unknown.\r
- *\r
- * @return the flags3\r
- */\r
- 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
- * children, then chuck on their header and return)
- */
- public void writeOut(OutputStream out) throws IOException {
- byte intbuf[] = new byte[LittleEndianConsts.INT_SIZE];
- byte shortbuf[] = new byte[LittleEndianConsts.SHORT_SIZE];
-
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- bos.write(intbuf); // total size, will be determined later ..
-
- LittleEndian.putShort(shortbuf, 0, getFlags1());
- bos.write(shortbuf);
-
- bos.write(getLabel().getBytes(ISO1));
- bos.write(0);
-
- bos.write(getFileName().getBytes(ISO1));
- bos.write(0);
-
- LittleEndian.putShort(shortbuf, 0, getFlags2());
- bos.write(shortbuf);
-
- LittleEndian.putShort(shortbuf, 0, getUnknown1());
- bos.write(shortbuf);
-
- LittleEndian.putInt(intbuf, 0, getCommand().length()+1);
- bos.write(intbuf);
-
- bos.write(getCommand().getBytes(ISO1));
- bos.write(0);
-
- LittleEndian.putInt(intbuf, 0, getDataBuffer().length);
- bos.write(intbuf);
-
- bos.write(getDataBuffer());
-
- LittleEndian.putShort(shortbuf, 0, getFlags3());
- bos.write(shortbuf);
-
- // update total size - length of length-field (4 bytes)
- byte data[] = bos.toByteArray();
- totalSize = data.length - LittleEndianConsts.INT_SIZE;
- LittleEndian.putInt(data, 0, totalSize);
-
- out.write(data);
- }
-
- public void setFlags1(short flags1) {
- this.flags1 = flags1;
- }
-
- public void setFlags2(short flags2) {
- this.flags2 = flags2;
- }
-
- public void setFlags3(short flags3) {
- this.flags3 = flags3;
- }
-
- public void setLabel(String label) {
- this.label = label;
- }
-
- public void setFileName(String fileName) {
- this.fileName = fileName;
- }
-
- public void setCommand(String command) {
- this.command = command;
- }
-
- public void setUnknown1(short unknown1) {
- this.unknown1 = unknown1;
- }
+ /**\r
+ * Returns the flags3 - currently unknown.\r
+ * \r
+ * @return the flags3\r
+ */\r
+ public short getFlags3() {\r
+ return flags3;\r
+ }\r
+\r
+ /**
+ * 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
+ * children, then chuck on their header and return)
+ */
+ public void writeOut(OutputStream out) throws IOException {\r
+ // byte intbuf[] = new byte[LittleEndianConsts.INT_SIZE];\r
+ // byte shortbuf[] = new byte[LittleEndianConsts.SHORT_SIZE];\r
+\r
+ @SuppressWarnings("resource")\r
+ LittleEndianOutputStream leosOut = new LittleEndianOutputStream(out);\r
+ \r
+ switch (mode) {\r
+ case parsed: {\r
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();\r
+ LittleEndianOutputStream leos = new LittleEndianOutputStream(bos);\r
+ // total size, will be determined later ..\r
+\r
+ leos.writeShort(getFlags1());\r
+ leos.write(getLabel().getBytes(ISO1));\r
+ leos.write(0);\r
+ leos.write(getFileName().getBytes(ISO1));\r
+ leos.write(0);\r
+ leos.writeShort(getFlags2());\r
+ leos.writeShort(getUnknown1());\r
+ leos.writeInt(getCommand().length() + 1);\r
+ leos.write(getCommand().getBytes(ISO1));\r
+ leos.write(0);\r
+ leos.writeInt(getDataSize());\r
+ leos.write(getDataBuffer());\r
+ leos.writeShort(getFlags3());\r
+ leos.close(); // satisfy compiler ...\r
+ \r
+ leosOut.writeInt(bos.size()); // total size\r
+ bos.writeTo(out);\r
+ break;\r
+ }\r
+ case compact:\r
+ leosOut.writeInt(getDataSize()+LittleEndianConsts.SHORT_SIZE);\r
+ leosOut.writeShort(getFlags1());\r
+ out.write(getDataBuffer());\r
+ break;\r
+ default:\r
+ case unparsed:\r
+ leosOut.writeInt(getDataSize());\r
+ out.write(getDataBuffer());\r
+ break;\r
+ }\r
+\r
+ }\r
- public void setDataBuffer(byte dataBuffer[]) {
- this.dataBuffer = dataBuffer;
- }
+ public void setFlags1(short flags1) {\r
+ this.flags1 = flags1;\r
+ }\r
+\r
+ public void setFlags2(short flags2) {\r
+ this.flags2 = flags2;\r
+ }\r
+\r
+ public void setFlags3(short flags3) {\r
+ this.flags3 = flags3;\r
+ }\r
+\r
+ public void setLabel(String label) {\r
+ this.label = label;\r
+ }\r
+\r
+ public void setFileName(String fileName) {\r
+ this.fileName = fileName;\r
+ }\r
+\r
+ public void setCommand(String command) {\r
+ this.command = command;\r
+ }\r
+\r
+ public void setUnknown1(short unknown1) {\r
+ this.unknown1 = unknown1;\r
+ }\r
+\r
+ public void setDataBuffer(byte dataBuffer[]) {\r
+ this.dataBuffer = dataBuffer;\r
+ }\r
}