From 19a885a5d7440d4faad11588ec0d0303dbacd7fc Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Sat, 16 May 2009 17:59:42 +0000 Subject: [PATCH] Apply changes from BUREAU Nicolas from patch #47183 - attachment support for hsmf git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@775501 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 1 + src/documentation/content/xdocs/status.xml | 1 + .../src/org/apache/poi/hsmf/MAPIMessage.java | 10 ++ .../poi/hsmf/datatypes/AttachmentChunks.java | 45 +++++++++ .../apache/poi/hsmf/datatypes/ByteChunk.java | 60 ++++++++++++ .../poi/hsmf/parsers/POIFSChunkParser.java | 59 +++++++++++- .../org/apache/poi/hsmf/AllTests.java | 1 + .../poi/hsmf/data/attachment_test_msg.msg | Bin 0 -> 52224 bytes .../model/TestFileWithAttachmentsRead.java | 86 ++++++++++++++++++ 9 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 src/scratchpad/src/org/apache/poi/hsmf/datatypes/AttachmentChunks.java create mode 100644 src/scratchpad/src/org/apache/poi/hsmf/datatypes/ByteChunk.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hsmf/data/attachment_test_msg.msg create mode 100644 src/scratchpad/testcases/org/apache/poi/hsmf/model/TestFileWithAttachmentsRead.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index cdb6bdfe8..34b132702 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -37,6 +37,7 @@ + 47183 - Attachment support for HSMF 47154 - Handle the cell format @ as the same as General 47048 - Fixed evaluation of defined names with the 'complex' flag set 46953 - More tweaks to PageSettingsBlock parsing logic in Sheet constructor diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 2008d00b3..bfbefcfab 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 47183 - Attachment support for HSMF 47154 - Handle the cell format @ as the same as General 47048 - Fixed evaluation of defined names with the 'complex' flag set 46953 - More tweaks to PageSettingsBlock parsing logic in Sheet constructor diff --git a/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java b/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java index 46bebadcc..760745e90 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; +import java.util.Map; import org.apache.poi.hsmf.datatypes.Chunk; import org.apache.poi.hsmf.datatypes.Chunks; @@ -159,4 +160,13 @@ public class MAPIMessage { public String getMessageClass() throws ChunkNotFoundException { return getStringFromChunk(chunks.messageClass); } + + /** + * Gets the message attachments. + * + * @return a map containing attachment name (String) and data (ByteArrayInputStream) + */ + public Map getAttachmentFiles() { + return this.chunkParser.getAttachmentList(); + } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/AttachmentChunks.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/AttachmentChunks.java new file mode 100644 index 000000000..549e2d194 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/AttachmentChunks.java @@ -0,0 +1,45 @@ +/* ==================================================================== + 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.hsmf.datatypes; + +/** + * Collection of convenence chunks for standard parts of the MSG file attachment. + */ +public class AttachmentChunks { + + public static final String namePrefix = "__attach_version1.0_#"; + + /* String parts of Outlook Messages Attachments that are currently known */ + + public ByteChunk attachData; + public StringChunk attachExtension; + public StringChunk attachFileName; + public StringChunk attachLongFileName; + public StringChunk attachMimeTag; + + private AttachmentChunks(boolean newStringType) { + attachData = new ByteChunk(0x3701, 0x0102); + attachExtension = new StringChunk(0x3703, newStringType); + attachFileName = new StringChunk(0x3704, newStringType); + attachLongFileName = new StringChunk(0x3707, newStringType); + attachMimeTag = new StringChunk(0x370E, newStringType); + } + + public static AttachmentChunks getInstance(boolean newStringType) { + return new AttachmentChunks(newStringType); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/ByteChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/ByteChunk.java new file mode 100644 index 000000000..2ecb3f08b --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/ByteChunk.java @@ -0,0 +1,60 @@ +/* ==================================================================== + 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.hsmf.datatypes; + +import java.io.ByteArrayOutputStream; + +/** + * A Chunk made up of a ByteArrayOutputStream. + */ + +public class ByteChunk extends Chunk { + + private ByteArrayOutputStream value; + + /** + * Creates a Byte Chunk, for either the old + * or new style of string chunk types. + */ + public ByteChunk(int chunkId, boolean newStyleString) { + this(chunkId, getStringType(newStyleString)); + } + private static int getStringType(boolean newStyleString) { + if(newStyleString) + return Types.NEW_STRING; + return Types.OLD_STRING; + } + + /** + * Create a Byte Chunk, with the specified + * type. + */ + public ByteChunk(int chunkId, int type) { + this.chunkId = chunkId; + this.type = type; + } + + public ByteArrayOutputStream getValueByteArray() { + return this.value; + } + + public void setValue(ByteArrayOutputStream value) { + this.value = value; + } + + +} diff --git a/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java b/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java index 108a28b4c..500428505 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java @@ -17,12 +17,16 @@ package org.apache.poi.hsmf.parsers; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.apache.poi.hsmf.datatypes.AttachmentChunks; import org.apache.poi.hsmf.datatypes.Chunk; import org.apache.poi.hsmf.datatypes.Chunks; import org.apache.poi.hsmf.datatypes.Types; @@ -89,14 +93,30 @@ public class POIFSChunkParser { * appropriate for the chunks we find in the file. */ public Chunks identifyChunks() { + return Chunks.getInstance(this.isNewChunkVersion(this.directoryMap)); + } + + /** + * Returns a list of the standard chunk types, as + * appropriate for the chunks we find in the file attachment. + */ + private AttachmentChunks identifyAttachmentChunks(Map attachmentMap) { + return AttachmentChunks.getInstance(this.isNewChunkVersion(attachmentMap)); + } + + /** + * Return chunk version of the map in parameter + */ + private boolean isNewChunkVersion(Map map) { // Are they of the old or new type of strings? boolean hasOldStrings = false; boolean hasNewStrings = false; String oldStringEnd = Types.asFileEnding(Types.OLD_STRING); String newStringEnd = Types.asFileEnding(Types.NEW_STRING); - for(Iterator i = directoryMap.keySet().iterator(); i.hasNext();) { + for(Iterator i = map.keySet().iterator(); i.hasNext();) { String entry = (String)i.next(); + if(entry.endsWith( oldStringEnd )) { hasOldStrings = true; } @@ -108,9 +128,9 @@ public class POIFSChunkParser { if(hasOldStrings && hasNewStrings) { throw new IllegalStateException("Your file contains string chunks of both the old and new types. Giving up"); } else if(hasNewStrings) { - return Chunks.getInstance(true); + return true; } - return Chunks.getInstance(false); + return false; } /** @@ -165,6 +185,39 @@ public class POIFSChunkParser { return getDocumentNode(this.directoryMap, chunk); } + /** + * + * @return a map containing attachment name (String) and data (ByteArrayInputStream) + */ + public Map getAttachmentList() { + Map attachments = new HashMap(); + List attachmentList = new ArrayList(); + for(Iterator i = directoryMap.keySet().iterator(); i.hasNext();) { + String entry = (String)i.next(); + + if(entry.startsWith(AttachmentChunks.namePrefix)) { + String attachmentIdString = entry.replace(AttachmentChunks.namePrefix, ""); + try { + int attachmentId = Integer.parseInt(attachmentIdString); + attachmentList.add((HashMap)directoryMap.get(entry)); + } catch (NumberFormatException nfe) { + System.err.println("Invalid attachment id"); + } + } + } + for (Iterator iterator = attachmentList.iterator(); iterator.hasNext();) { + HashMap AttachmentChunkMap = (HashMap) iterator.next(); + AttachmentChunks attachmentChunks = this.identifyAttachmentChunks(AttachmentChunkMap); + try { + Chunk fileName = this.getDocumentNode(AttachmentChunkMap, attachmentChunks.attachLongFileName); + Chunk content = this.getDocumentNode(AttachmentChunkMap, attachmentChunks.attachData); + attachments.put(fileName.toString(), new ByteArrayInputStream(content.getValueByteArray().toByteArray())); + } catch (ChunkNotFoundException e) { + System.err.println("Invalid attachment chunk"); + } + } + return attachments; + } /** * Processes an iterator returned by a POIFS call to getRoot().getEntries() diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/AllTests.java b/src/scratchpad/testcases/org/apache/poi/hsmf/AllTests.java index e117ab89e..d6f072dfe 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/AllTests.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/AllTests.java @@ -35,6 +35,7 @@ public class AllTests suite.addTestSuite(org.apache.poi.hsmf.model.TestSimpleFileRead.class); suite.addTestSuite(org.apache.poi.hsmf.model.TestOutlook30FileRead.class); suite.addTestSuite(org.apache.poi.hsmf.model.TestChunkData.class); + suite.addTestSuite(org.apache.poi.hsmf.model.TestFileWithAttachmentsRead.class); return suite; } diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/data/attachment_test_msg.msg b/src/scratchpad/testcases/org/apache/poi/hsmf/data/attachment_test_msg.msg new file mode 100644 index 0000000000000000000000000000000000000000..ea82d915ed0357ccc3e473873fa2c43efb988d0b GIT binary patch literal 52224 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 0) { + fileContent.write(fileStream.read()); + } + String obtained = new String(fileContent.toByteArray(), "UTF-8"); + assertTrue(obtained.trim().length() > 0); + } + } + +}