Bugzilla 55578 - Support embedding OLE1.0 packages in HSSF

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1531623 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Yegor Kozlov 2013-10-13 07:39:40 +00:00
parent 489b0b5bdb
commit cf7a4c117a
11 changed files with 800 additions and 143 deletions

View File

@ -30,7 +30,12 @@ import org.apache.poi.util.HexDump;
*/
public class ClassID
{
public static final ClassID OLE10_PACKAGE = new ClassID("{0003000C-0000-0000-C000-000000000046}");
public static final ClassID PPT_SHOW = new ClassID("{64818D10-4F9B-11CF-86EA-00AA00B929E8}");
public static final ClassID XLS_WORKBOOK = new ClassID("{00020841-0000-0000-C000-000000000046}");
public static final ClassID TXT_ONLY = new ClassID("{5e941d80-bf96-11cd-b579-08002b30bfeb}"); // ???
/**
* <p>The bytes making out the class ID in correct order,
* i.e. big-endian.</p>
@ -64,6 +69,20 @@ public class ClassID
}
/**
* <p>Creates a {@link ClassID} from a human-readable representation of the Class ID in standard
* format <code>"{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"</code>.</p>
*
* @param externalForm representation of the Class ID represented by this object.
*/
public ClassID(String externalForm) {
bytes = new byte[LENGTH];
String clsStr = externalForm.replaceAll("[{}-]", "");
for (int i=0; i<clsStr.length(); i+=2) {
bytes[i/2] = (byte)Integer.parseInt(clsStr.substring(i, i+2), 16);
}
}
/** <p>The number of bytes occupied by this object in the byte
* stream.</p> */

View File

@ -64,7 +64,7 @@ public final class EmbeddedObjectRefSubRecord extends SubRecord {
// currently for testing only - needs review
EmbeddedObjectRefSubRecord() {
public EmbeddedObjectRefSubRecord() {
field_2_unknownFormulaData = new byte[] { 0x02, 0x6C, 0x6A, 0x16, 0x01, }; // just some sample data. These values vary a lot
field_6_unknown = EMPTY_BYTE_ARRAY;
field_4_ole_classname = null;
@ -334,4 +334,16 @@ public final class EmbeddedObjectRefSubRecord extends SubRecord {
sb.append("[/ftPictFmla]");
return sb.toString();
}
public void setUnknownFormulaData(byte[] formularData) {
field_2_unknownFormulaData = formularData;
}
public void setOleClassname(String oleClassname) {
field_4_ole_classname = oleClassname;
}
public void setStorageId(int storageId) {
field_5_stream_id = storageId;
}
}

View File

@ -0,0 +1,113 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record;
import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
/**
* The FtCf structure specifies the clipboard format of the picture-type Obj record containing this FtCf.
*/
public final class FtCfSubRecord extends SubRecord {
public final static short sid = 0x07;
public final static short length = 0x02;
/**
* Specifies the format of the picture is an enhanced metafile.
*/
public static short METAFILE_BIT = (short)0x0002;
/**
* Specifies the format of the picture is a bitmap.
*/
public static short BITMAP_BIT = (short)0x0009;
/**
* Specifies the picture is in an unspecified format that is
* neither and enhanced metafile nor a bitmap.
*/
public static short UNSPECIFIED_BIT = (short)0xFFFF;
private short flags = 0;
/**
* Construct a new <code>FtPioGrbitSubRecord</code> and
* fill its data with the default values
*/
public FtCfSubRecord() {
}
public FtCfSubRecord(LittleEndianInput in, int size) {
if (size != length) {
throw new RecordFormatException("Unexpected size (" + size + ")");
}
flags = in.readShort();
}
/**
* Convert this record to string.
* Used by BiffViewer and other utilities.
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[FtCf ]\n");
buffer.append(" size = ").append(length).append("\n");
buffer.append(" flags = ").append(HexDump.toHex(flags)).append("\n");
buffer.append("[/FtCf ]\n");
return buffer.toString();
}
/**
* Serialize the record data into the supplied array of bytes
*
* @param out the stream to serialize into
*/
public void serialize(LittleEndianOutput out) {
out.writeShort(sid);
out.writeShort(length);
out.writeShort(flags);
}
protected int getDataSize() {
return length;
}
/**
* @return id of this record.
*/
public short getSid()
{
return sid;
}
public Object clone() {
FtCfSubRecord rec = new FtCfSubRecord();
rec.flags = this.flags;
return rec;
}
public short getFlags() {
return flags;
}
public void setFlags(short flags) {
this.flags = flags;
}
}

View File

@ -0,0 +1,167 @@
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.hssf.record;
import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndianInput;
import org.apache.poi.util.LittleEndianOutput;
/**
* This structure appears as part of an Obj record that represents image display properties.
*/
public final class FtPioGrbitSubRecord extends SubRecord {
public final static short sid = 0x08;
public final static short length = 0x02;
/**
* A bit that specifies whether the picture's aspect ratio is preserved when rendered in
* different views (Normal view, Page Break Preview view, Page Layout view and printing).
*/
public static int AUTO_PICT_BIT = 1 << 0;
/**
* A bit that specifies whether the pictFmla field of the Obj record that contains
* this FtPioGrbit specifies a DDE reference.
*/
public static int DDE_BIT = 1 << 1;
/**
* A bit that specifies whether this object is expected to be updated on print to
* reflect the values in the cell associated with the object.
*/
public static int PRINT_CALC_BIT = 1 << 2;
/**
* A bit that specifies whether the picture is displayed as an icon.
*/
public static int ICON_BIT = 1 << 3;
/**
* A bit that specifies whether this object is an ActiveX control.
* It MUST NOT be the case that both fCtl and fDde are equal to 1.
*/
public static int CTL_BIT = 1 << 4;
/**
* A bit that specifies whether the object data are stored in an
* embedding storage (= 0) or in the controls stream (ctls) (= 1).
*/
public static int PRSTM_BIT = 1 << 5;
/**
* A bit that specifies whether this is a camera picture.
*/
public static int CAMERA_BIT = 1 << 7;
/**
* A bit that specifies whether this picture's size has been explicitly set.
* 0 = picture size has been explicitly set, 1 = has not been set
*/
public static int DEFAULT_SIZE_BIT = 1 << 8;
/**
* A bit that specifies whether the OLE server for the object is called
* to load the object's data automatically when the parent workbook is opened.
*/
public static int AUTO_LOAD_BIT = 1 << 9;
private short flags = 0;
/**
* Construct a new <code>FtPioGrbitSubRecord</code> and
* fill its data with the default values
*/
public FtPioGrbitSubRecord() {
}
public FtPioGrbitSubRecord(LittleEndianInput in, int size) {
if (size != length) {
throw new RecordFormatException("Unexpected size (" + size + ")");
}
flags = in.readShort();
}
/**
* Use one of the bitmasks MANUAL_ADVANCE_BIT ... CURSOR_VISIBLE_BIT
* @param bitmask
* @param enabled
*/
public void setFlagByBit(int bitmask, boolean enabled) {
if (enabled) {
flags |= bitmask;
} else {
flags &= (0xFFFF ^ bitmask);
}
}
public boolean getFlagByBit(int bitmask) {
return ((flags & bitmask) != 0);
}
/**
* Convert this record to string.
* Used by BiffViewer and other utilities.
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("[FtPioGrbit ]\n");
buffer.append(" size = ").append(length).append("\n");
buffer.append(" flags = ").append(HexDump.toHex(flags)).append("\n");
buffer.append("[/FtPioGrbit ]\n");
return buffer.toString();
}
/**
* Serialize the record data into the supplied array of bytes
*
* @param out the stream to serialize into
*/
public void serialize(LittleEndianOutput out) {
out.writeShort(sid);
out.writeShort(length);
out.writeShort(flags);
}
protected int getDataSize() {
return length;
}
/**
* @return id of this record.
*/
public short getSid()
{
return sid;
}
public Object clone() {
FtPioGrbitSubRecord rec = new FtPioGrbitSubRecord();
rec.flags = this.flags;
return rec;
}
public short getFlags() {
return flags;
}
public void setFlags(short flags) {
this.flags = flags;
}
}

View File

@ -59,6 +59,10 @@ public abstract class SubRecord {
return new LbsDataSubRecord(in, secondUShort, cmoOt);
case FtCblsSubRecord.sid:
return new FtCblsSubRecord(in, secondUShort);
case FtPioGrbitSubRecord.sid:
return new FtPioGrbitSubRecord(in, secondUShort);
case FtCfSubRecord.sid:
return new FtCfSubRecord(in, secondUShort);
}
return new UnknownSubRecord(in, sid, secondUShort);
}

View File

@ -17,27 +17,35 @@
package org.apache.poi.hssf.usermodel;
import java.io.FileNotFoundException;
import java.util.*;
import org.apache.poi.ddf.*;
import org.apache.poi.hssf.model.DrawingManager2;
import org.apache.poi.hssf.record.CommonObjectDataSubRecord;
import org.apache.poi.hssf.record.EmbeddedObjectRefSubRecord;
import org.apache.poi.hssf.record.EndSubRecord;
import org.apache.poi.hssf.record.EscherAggregate;
import org.apache.poi.hssf.record.FtCfSubRecord;
import org.apache.poi.hssf.record.FtPioGrbitSubRecord;
import org.apache.poi.hssf.record.NoteRecord;
import org.apache.poi.hssf.record.ObjRecord;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.ss.usermodel.Chart;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.StringUtil;
import org.apache.poi.util.Internal;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.Drawing;
import org.apache.poi.util.HexDump;
import org.apache.poi.util.Internal;
import org.apache.poi.util.StringUtil;
/**
* The patriarch is the toplevel container for shapes in a sheet. It does
* little other than act as a container for other shapes and groups.
*/
public final class HSSFPatriarch implements HSSFShapeContainer, Drawing {
private static POILogger log = POILogFactory.getLogger(HSSFPatriarch.class);
// private static POILogger log = POILogFactory.getLogger(HSSFPatriarch.class);
private final List<HSSFShape> _shapes = new ArrayList<HSSFShape>();
private final EscherSpgrRecord _spgrRecord;
@ -193,6 +201,87 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing {
return createPicture((HSSFClientAnchor) anchor, pictureIndex);
}
/**
* Adds a new OLE Package Shape
*
* @param anchor the client anchor describes how this picture is
* attached to the sheet.
* @param storageId the storageId returned by {@Link HSSFWorkbook.addOlePackage}
* @param pictureIndex the index of the picture (used as preview image) in the
* workbook collection of pictures.
*
* @return newly created shape
*/
public HSSFObjectData createObjectData(HSSFClientAnchor anchor, int storageId, int pictureIndex) {
ObjRecord obj = new ObjRecord();
CommonObjectDataSubRecord ftCmo = new CommonObjectDataSubRecord();
ftCmo.setObjectType(CommonObjectDataSubRecord.OBJECT_TYPE_PICTURE);
// ftCmo.setObjectId(oleShape.getShapeId()); ... will be set by onCreate(...)
ftCmo.setLocked(true);
ftCmo.setPrintable(true);
ftCmo.setAutofill(true);
ftCmo.setAutoline(true);
ftCmo.setReserved1(0);
ftCmo.setReserved2(0);
ftCmo.setReserved3(0);
obj.addSubRecord(ftCmo);
// FtCf (pictFormat)
FtCfSubRecord ftCf = new FtCfSubRecord();
HSSFPictureData pictData = getSheet().getWorkbook().getAllPictures().get(pictureIndex-1);
switch (pictData.getFormat()) {
case HSSFWorkbook.PICTURE_TYPE_WMF:
case HSSFWorkbook.PICTURE_TYPE_EMF:
// this needs patch #49658 to be applied to actually work
ftCf.setFlags(FtCfSubRecord.METAFILE_BIT);
break;
case HSSFWorkbook.PICTURE_TYPE_DIB:
case HSSFWorkbook.PICTURE_TYPE_PNG:
case HSSFWorkbook.PICTURE_TYPE_JPEG:
case HSSFWorkbook.PICTURE_TYPE_PICT:
ftCf.setFlags(FtCfSubRecord.BITMAP_BIT);
break;
}
obj.addSubRecord(ftCf);
// FtPioGrbit (pictFlags)
FtPioGrbitSubRecord ftPioGrbit = new FtPioGrbitSubRecord();
ftPioGrbit.setFlagByBit(FtPioGrbitSubRecord.AUTO_PICT_BIT, true);
obj.addSubRecord(ftPioGrbit);
EmbeddedObjectRefSubRecord ftPictFmla = new EmbeddedObjectRefSubRecord();
ftPictFmla.setUnknownFormulaData(new byte[]{2, 0, 0, 0, 0});
ftPictFmla.setOleClassname("Paket");
ftPictFmla.setStorageId(storageId);
obj.addSubRecord(ftPictFmla);
obj.addSubRecord(new EndSubRecord());
String entryName = "MBD"+HexDump.toHex(storageId);
DirectoryEntry oleRoot;
try {
DirectoryNode dn = _sheet.getWorkbook().getRootDirectory();
if (dn == null) throw new FileNotFoundException();
oleRoot = (DirectoryEntry)dn.getEntry(entryName);
} catch (FileNotFoundException e) {
throw new IllegalStateException("trying to add ole shape without actually adding data first - use HSSFWorkbook.addOlePackage first", e);
}
// create picture shape, which need to be minimal modified for oleshapes
HSSFPicture shape = new HSSFPicture(null, anchor);
shape.setPictureIndex(pictureIndex);
EscherContainerRecord spContainer = shape.getEscherContainer();
EscherSpRecord spRecord = spContainer.getChildById(EscherSpRecord.RECORD_ID);
spRecord.setFlags(spRecord.getFlags() | EscherSpRecord.FLAG_OLESHAPE);
HSSFObjectData oleShape = new HSSFObjectData(spContainer, obj, oleRoot);
addShape(oleShape);
onCreate(oleShape);
return oleShape;
}
/**
* Creates a polygon
*

View File

@ -18,15 +18,19 @@
package org.apache.poi.hssf.usermodel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import org.apache.commons.codec.digest.DigestUtils;
@ -36,6 +40,7 @@ import org.apache.poi.ddf.EscherBitmapBlip;
import org.apache.poi.ddf.EscherBlipRecord;
import org.apache.poi.ddf.EscherMetafileBlip;
import org.apache.poi.ddf.EscherRecord;
import org.apache.poi.hpsf.ClassID;
import org.apache.poi.hssf.OldExcelFormatException;
import org.apache.poi.hssf.model.DrawingManager2;
import org.apache.poi.hssf.model.HSSFFormulaParser;
@ -46,7 +51,11 @@ import org.apache.poi.hssf.record.*;
import org.apache.poi.hssf.record.aggregates.RecordAggregate.RecordVisitor;
import org.apache.poi.hssf.record.common.UnicodeString;
import org.apache.poi.hssf.util.CellReference;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.EntryUtils;
import org.apache.poi.poifs.filesystem.FilteringDirectoryNode;
import org.apache.poi.poifs.filesystem.Ole10Native;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.formula.FormulaShifter;
import org.apache.poi.ss.formula.FormulaType;
@ -57,10 +66,7 @@ import org.apache.poi.ss.formula.udf.UDFFinder;
import org.apache.poi.ss.usermodel.Row.MissingCellPolicy;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.ss.util.WorkbookUtil;
import org.apache.poi.util.Configurator;
import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger;
import org.apache.poi.util.*;
/**
@ -1190,15 +1196,15 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
if (preserveNodes) {
// Don't write out the old Workbook, we'll be doing our new one
excepts.add("Workbook");
// If the file had an "incorrect" name for the workbook stream,
// don't write the old one as we'll use the correct name shortly
for (String wrongName : WORKBOOK_DIR_ENTRY_NAMES) {
excepts.add(wrongName);
}
excepts.addAll(Arrays.asList(WORKBOOK_DIR_ENTRY_NAMES));
// Copy over all the other nodes to our new poifs
copyNodes(this.directory, fs.getRoot(), excepts);
EntryUtils.copyNodes(
new FilteringDirectoryNode(this.directory, excepts)
, new FilteringDirectoryNode(fs.getRoot(), excepts)
);
// YK: preserve StorageClsid, it is important for embedded workbooks,
// see Bugzilla 47920
@ -1623,7 +1629,7 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
break;
}
blipRecord.setRecordId( (short) ( EscherBitmapBlip.RECORD_ID_START + format ) );
blipRecord.setRecordId((short) (EscherBitmapBlip.RECORD_ID_START + format));
switch (format)
{
case PICTURE_TYPE_EMF:
@ -1713,6 +1719,65 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss
}
protected static Map<String,ClassID> getOleMap() {
Map<String,ClassID> olemap = new HashMap<String,ClassID>();
olemap.put("PowerPoint Document", ClassID.PPT_SHOW);
for (String str : WORKBOOK_DIR_ENTRY_NAMES) {
olemap.put(str, ClassID.XLS_WORKBOOK);
}
// ... to be continued
return olemap;
}
public int addOlePackage(POIFSFileSystem poiData, String label, String fileName, String command)
throws IOException {
DirectoryNode root = poiData.getRoot();
Map<String,ClassID> olemap = getOleMap();
for (Map.Entry<String,ClassID> entry : olemap.entrySet()) {
if (root.hasEntry(entry.getKey())) {
root.setStorageClsid(entry.getValue());
break;
}
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
poiData.writeFilesystem(bos);
return addOlePackage(bos.toByteArray(), label, fileName, command);
}
public int addOlePackage(byte[] oleData, String label, String fileName, String command)
throws IOException {
// check if we were created by POIFS otherwise create a new dummy POIFS for storing the package data
if (directory == null) {
directory = new POIFSFileSystem().getRoot();
preserveNodes = true;
}
// get free MBD-Node
int storageId = 0;
DirectoryEntry oleDir = null;
do {
String storageStr = "MBD"+ HexDump.toHex(++storageId);
if (!directory.hasEntry(storageStr)) {
oleDir = directory.createDirectory(storageStr);
oleDir.setStorageClsid(ClassID.OLE10_PACKAGE);
}
} while (oleDir == null);
// the following data was taken from an example libre office document
// beside this "\u0001Ole" record there were several other records, e.g. CompObj,
// OlePresXXX, but it seems, that they aren't neccessary
byte oleBytes[] = { 1, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
oleDir.createDocument("\u0001Ole", new ByteArrayInputStream(oleBytes));
Ole10Native oleNative = new Ole10Native(label, fileName, command, oleData);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
oleNative.writeOut(bos);
oleDir.createDocument(Ole10Native.OLE10_NATIVE, new ByteArrayInputStream(bos.toByteArray()));
return storageId;
}
/**
* Is the workbook protected with a password (not encrypted)?
*/

View File

@ -41,8 +41,10 @@ public class EntryUtils
DirectoryEntry newTarget = null;
if ( entry.isDirectoryEntry() )
{
DirectoryEntry dirEntry = (DirectoryEntry)entry;
newTarget = target.createDirectory( entry.getName() );
Iterator<Entry> entries = ( (DirectoryEntry) entry ).getEntries();
newTarget.setStorageClsid( dirEntry.getStorageClsid() );
Iterator<Entry> entries = dirEntry.getEntries();
while ( entries.hasNext() )
{

View File

@ -14,14 +14,16 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.poifs.filesystem;
import java.io.FileNotFoundException;
import java.io.IOException;
import org.apache.poi.util.HexDump;
import org.apache.poi.util.LittleEndian;
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 org.apache.poi.util.LittleEndianConsts;
import org.apache.poi.util.StringUtil;
@ -29,27 +31,27 @@ import org.apache.poi.util.StringUtil;
* Represents an Ole10Native record which is wrapped around certain binary
* files being embedded in OLE2 documents.
*
* @author Rainer Schwarze
*/
public class Ole10Native {
// (the fields as they appear in the raw record:)
private final int totalSize; // 4 bytes, total size of record not including this field
private short flags1; // 2 bytes, unknown, mostly [02 00]
private final String label; // ASCIIZ, stored in this field without the terminating zero
private final String fileName; // ASCIIZ, stored in this field without the terminating zero
private short flags2; // 2 bytes, unknown, mostly [00 00]
// private byte unknown1Length; // 1 byte, specifying the length of the following byte array (unknown1)
private byte[] unknown1; // see below
private byte[] unknown2; // 3 bytes, unknown, mostly [00 00 00]
private final String command; // ASCIIZ, stored in this field without the terminating zero
private final int dataSize; // 4 bytes (if space), size of following buffer
private final byte[] dataBuffer; // varying size, the actual native data
private short flags3; // some final flags? or zero terminators?, sometimes not there
public static final String OLE10_NATIVE = "\u0001Ole10Native";
/**
* Creates an instance of this class from an embedded OLE Object. The OLE Object is expected
* to include a stream &quot;{01}Ole10Native&quot; which contains the actual
* @author Rainer Schwarze
*/
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 &quot;{01}Ole10Native&quot; which contains the actual
* data relevant for this class.
*
* @param poifs POI Filesystem object
@ -87,12 +89,22 @@ public class Ole10Native {
directory.createDocumentInputStream(nativeEntry).read(data);
return new Ole10Native(data, 0, plain);
}
/**
* Creates an instance and fills the fields based on the data in the given buffer.
*
* @param data The buffer containing the Ole10Native record
}
/**
* 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
* @throws Ole10NativeException on invalid or unexcepted data format
*/
@ -117,61 +129,57 @@ public class Ole10Native {
totalSize = LittleEndian.getInt(data, ofs);
ofs += LittleEndianConsts.INT_SIZE;
if (plain) {
dataBuffer = new byte[totalSize-4];
System.arraycopy(data, 4, dataBuffer, 0, dataBuffer.length);
dataSize = totalSize - 4;
byte[] oleLabel = new byte[8];
System.arraycopy(dataBuffer, 0, oleLabel, 0, Math.min(dataBuffer.length, 8));
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);
fileName = label;
command = label;
} 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;
len = LittleEndian.getUByte(data, ofs);
unknown1 = new byte[len];
ofs += len;
len = 3;
unknown2 = new byte[len];
ofs += len;
len = getStringLength(data, ofs);
command = StringUtil.getFromCompressedUnicode(data, ofs, len - 1);
ofs += len;
if (totalSize + LittleEndianConsts.INT_SIZE - ofs > LittleEndianConsts.INT_SIZE) {
dataSize = LittleEndian.getInt(data, ofs);
ofs += LittleEndianConsts.INT_SIZE;
if (dataSize > totalSize || dataSize<0) {
throw new Ole10NativeException("Invalid Ole10Native");
}
dataBuffer = new byte[dataSize];
System.arraycopy(data, ofs, dataBuffer, 0, dataSize);
ofs += dataSize;
if (unknown1.length > 0) {
flags3 = LittleEndian.getShort(data, ofs);
ofs += LittleEndianConsts.SHORT_SIZE;
} else {
flags3 = 0;
}
} else {
throw new Ole10NativeException("Invalid Ole10Native");
}
}
}
} 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;
}
}
/*
* Helper - determine length of zero terminated string (ASCIIZ).
*/
@ -234,26 +242,17 @@ public class Ole10Native {
/**
* Returns unknown1 field - currently unknown.
*
* @return the unknown1
*/
public byte[] getUnknown1() {
return unknown1;
}
/**
* Returns the unknown2 field - currently being a byte[3] - mostly {0, 0, 0}.
*
* @return the unknown2
*/
public byte[] getUnknown2() {
return unknown2;
}
/**
* 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 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
*/
public String getCommand() {
@ -265,13 +264,13 @@ public class Ole10Native {
* embedded. To be sure, that no data has been embedded, check whether
* {@link #getDataBuffer()} returns <code>null</code>.
*
* @return the dataSize
*/
public int getDataSize() {
return dataSize;
}
/**
* @return the dataSize
*/
public int getDataSize() {
return dataBuffer.length;
}
/**
* Returns the buffer containing the embedded file's data, or <code>null</code>
* if no data was embedded. Note that an embedding may provide information about
* the data, but the actual data is not included. (So label, filename etc. are
@ -288,7 +287,89 @@ public class Ole10Native {
*
* @return the flags3
*/
public short getFlags3() {
return 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
* 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;
}
public void setDataBuffer(byte dataBuffer[]) {
this.dataBuffer = dataBuffer;
}
}

View File

@ -262,10 +262,7 @@ public final class TestHSSFPicture extends BaseTestPicture {
System.arraycopy(pictureDataWmf, 22, wmfNoHeader, 0, pictureDataWmf.length-22);
pictureDataOut = wb.getAllPictures().get(2).getData();
assertTrue(Arrays.equals(wmfNoHeader, pictureDataOut));
FileOutputStream fos = new FileOutputStream("vect.xls");
wb.write(fos);
fos.close();
}
}

View File

@ -17,11 +17,23 @@
package org.apache.poi.hssf.usermodel;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import junit.framework.TestCase;
import org.apache.poi.POIDataSamples;
import org.apache.poi.hssf.HSSFTestDataSamples;
import org.apache.poi.poifs.filesystem.DirectoryNode;
import org.apache.poi.poifs.filesystem.Ole10Native;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.usermodel.ClientAnchor;
import org.apache.poi.ss.usermodel.CreationHelper;
/**
*
@ -39,7 +51,7 @@ public final class TestOLE2Embeding extends TestCase {
public void testEmbeddedObjects() throws Exception {
HSSFWorkbook workbook = HSSFTestDataSamples.openSampleWorkbook("ole2-embedding.xls");
List objects = workbook.getAllEmbeddedObjects();
List<HSSFObjectData> objects = workbook.getAllEmbeddedObjects();
assertEquals("Wrong number of objects", 2, objects.size());
assertEquals("Wrong name for first object", "MBD06CAB431",
((HSSFObjectData)
@ -48,5 +60,101 @@ public final class TestOLE2Embeding extends TestCase {
((HSSFObjectData)
objects.get(1)).getDirectory().getName());
}
}
public void testReallyEmbedSomething() throws Exception {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet();
HSSFPatriarch patriarch = sheet.createDrawingPatriarch();
byte[] pictureData = HSSFTestDataSamples.getTestDataFileContent("logoKarmokar4.png");
byte[] picturePPT = POIDataSamples.getSlideShowInstance().readFile("clock.jpg");
int imgIdx = wb.addPicture(pictureData, HSSFWorkbook.PICTURE_TYPE_PNG);
POIFSFileSystem pptPoifs = getSamplePPT();
int pptIdx = wb.addOlePackage(pptPoifs, "Sample-PPT", "sample.ppt", "sample.ppt");
POIFSFileSystem xlsPoifs = getSampleXLS();
int imgPPT = wb.addPicture(picturePPT, HSSFWorkbook.PICTURE_TYPE_JPEG);
int xlsIdx = wb.addOlePackage(xlsPoifs, "Sample-XLS", "sample.xls", "sample.xls");
int txtIdx = wb.addOlePackage(getSampleTXT(), "Sample-TXT", "sample.txt", "sample.txt");
int rowoffset = 5;
int coloffset = 5;
CreationHelper ch = wb.getCreationHelper();
HSSFClientAnchor anchor = (HSSFClientAnchor)ch.createClientAnchor();
anchor.setAnchor((short)(2+coloffset), 1+rowoffset, 0, 0, (short)(3+coloffset), 5+rowoffset, 0, 0);
anchor.setAnchorType(ClientAnchor.DONT_MOVE_AND_RESIZE);
patriarch.createObjectData(anchor, pptIdx, imgPPT);
anchor = (HSSFClientAnchor)ch.createClientAnchor();
anchor.setAnchor((short)(5+coloffset), 1+rowoffset, 0, 0, (short)(6+coloffset), 5+rowoffset, 0, 0);
anchor.setAnchorType(ClientAnchor.DONT_MOVE_AND_RESIZE);
patriarch.createObjectData(anchor, xlsIdx, imgIdx);
anchor = (HSSFClientAnchor)ch.createClientAnchor();
anchor.setAnchor((short)(3+coloffset), 10+rowoffset, 0, 0, (short)(5+coloffset), 11+rowoffset, 0, 0);
anchor.setAnchorType(ClientAnchor.DONT_MOVE_AND_RESIZE);
patriarch.createObjectData(anchor, txtIdx, imgIdx);
anchor = (HSSFClientAnchor)ch.createClientAnchor();
anchor.setAnchor((short)(1+coloffset), -2+rowoffset, 0, 0, (short)(7+coloffset), 14+rowoffset, 0, 0);
anchor.setAnchorType(ClientAnchor.DONT_MOVE_AND_RESIZE);
HSSFSimpleShape circle = patriarch.createSimpleShape(anchor);
circle.setShapeType(HSSFSimpleShape.OBJECT_TYPE_OVAL);
circle.setNoFill(true);
if (false) {
FileOutputStream fos = new FileOutputStream("embed.xls");
wb.write(fos);
fos.close();
}
wb = HSSFTestDataSamples.writeOutAndReadBack(wb);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
HSSFObjectData od = wb.getAllEmbeddedObjects().get(0);
Ole10Native ole10 = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)od.getDirectory());
bos.reset();
pptPoifs.writeFilesystem(bos);
assertTrue(Arrays.equals(ole10.getDataBuffer(), bos.toByteArray()));
od = wb.getAllEmbeddedObjects().get(1);
ole10 = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)od.getDirectory());
bos.reset();
xlsPoifs.writeFilesystem(bos);
assertTrue(Arrays.equals(ole10.getDataBuffer(), bos.toByteArray()));
od = wb.getAllEmbeddedObjects().get(2);
ole10 = Ole10Native.createFromEmbeddedOleObject((DirectoryNode)od.getDirectory());
assertTrue(Arrays.equals(ole10.getDataBuffer(), getSampleTXT()));
}
static POIFSFileSystem getSamplePPT() throws IOException {
// scratchpad classes are not available, so we use something pre-cooked
InputStream is = POIDataSamples.getSlideShowInstance().openResourceAsStream("with_textbox.ppt");
POIFSFileSystem poifs = new POIFSFileSystem(is);
is.close();
return poifs;
}
static POIFSFileSystem getSampleXLS() throws IOException {
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet();
sheet.createRow(5).createCell(2).setCellValue("yo dawg i herd you like embeddet objekts, so we put a ole in your ole so you can save a file while you save a file");
ByteArrayOutputStream bos = new ByteArrayOutputStream();
wb.write(bos);
POIFSFileSystem poifs = new POIFSFileSystem(new ByteArrayInputStream(bos.toByteArray()));
return poifs;
}
static byte[] getSampleTXT() {
return "All your base are belong to us".getBytes();
}
}