diff options
author | Yegor Kozlov <yegor@apache.org> | 2013-10-13 07:39:40 +0000 |
---|---|---|
committer | Yegor Kozlov <yegor@apache.org> | 2013-10-13 07:39:40 +0000 |
commit | f6cb7f14acf47450b536174202a05ce793a2cd52 (patch) | |
tree | ebba94f3cbad64f6038ee79826ebd37a70cb6431 /src/java/org | |
parent | 1ffc66625a01158ff5411d7dcdac45352ca786cc (diff) | |
download | poi-f6cb7f14acf47450b536174202a05ce793a2cd52.tar.gz poi-f6cb7f14acf47450b536174202a05ce793a2cd52.zip |
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
Diffstat (limited to 'src/java/org')
9 files changed, 689 insertions, 137 deletions
diff --git a/src/java/org/apache/poi/hpsf/ClassID.java b/src/java/org/apache/poi/hpsf/ClassID.java index dd623b8b77..9fab2227a8 100644 --- a/src/java/org/apache/poi/hpsf/ClassID.java +++ b/src/java/org/apache/poi/hpsf/ClassID.java @@ -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> */ diff --git a/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java b/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java index 77e9fd86d6..88fb10005c 100644 --- a/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java +++ b/src/java/org/apache/poi/hssf/record/EmbeddedObjectRefSubRecord.java @@ -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; + } } diff --git a/src/java/org/apache/poi/hssf/record/FtCfSubRecord.java b/src/java/org/apache/poi/hssf/record/FtCfSubRecord.java new file mode 100644 index 0000000000..95438334e1 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/FtCfSubRecord.java @@ -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; + } +}
\ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/record/FtPioGrbitSubRecord.java b/src/java/org/apache/poi/hssf/record/FtPioGrbitSubRecord.java new file mode 100644 index 0000000000..8562a05352 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/FtPioGrbitSubRecord.java @@ -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; + } +}
\ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/record/SubRecord.java b/src/java/org/apache/poi/hssf/record/SubRecord.java index fd54acc0a7..35b62c36c5 100644 --- a/src/java/org/apache/poi/hssf/record/SubRecord.java +++ b/src/java/org/apache/poi/hssf/record/SubRecord.java @@ -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); } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java index c508c51ed0..6d61b22d56 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPatriarch.java @@ -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; @@ -194,6 +202,87 @@ public final class HSSFPatriarch implements HSSFShapeContainer, Drawing { } /** + * 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 * * @param anchor the client anchor describes how this group is attached diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 9e42beab98..2ab0f6b2c5 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -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)? */ diff --git a/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java b/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java index 60f2b8d38c..4bce0641ab 100644 --- a/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java +++ b/src/java/org/apache/poi/poifs/filesystem/EntryUtils.java @@ -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() ) { diff --git a/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java b/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java index fbc58ab0c4..2c950da3ff 100644 --- a/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java +++ b/src/java/org/apache/poi/poifs/filesystem/Ole10Native.java @@ -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 "{01}Ole10Native" 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 "{01}Ole10Native" 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; + } +} |