<changes>
<release version="3.7-beta2" date="2010-??-??">
+ <action dev="POI-DEVELOPERS" type="add">47990 - Support for .msg attachments within a MAPIMessage .msg</action>
<action dev="POI-DEVELOPERS" type="fix">Improve handling and warnings when closing OPCPackage objects</action>
<action dev="POI-DEVELOPERS" type="fix">49702 - Correct XSSFWorkbook.getNumCellStyles to check the right styles list</action>
<action dev="POI-DEVELOPERS" type="add">49690 - Add WorkbookUtil, which provies a way of generating valid sheet names</action>
return _path;
}
+ /**
+ * @return the filesystem that this belongs to
+ */
+
+ public POIFSFileSystem getFileSystem()
+ {
+ return _filesystem;
+ }
+
/**
* open a document in the directory's entry's list of entries
*
public StringChunk attachFileName;
public StringChunk attachLongFileName;
public StringChunk attachMimeTag;
+ public DirectoryChunk attachmentDirectory;
+
/**
* This is in WMF Format. You'll probably want to pass it
* to Apache Batik to turn it into a SVG that you can
public void record(Chunk chunk) {
switch(chunk.getChunkId()) {
case ATTACH_DATA:
- attachData = (ByteChunk)chunk;
+ if(chunk instanceof ByteChunk) {
+ attachData = (ByteChunk)chunk;
+ } else if(chunk instanceof DirectoryChunk) {
+ attachmentDirectory = (DirectoryChunk)chunk;
+ } else {
+ System.err.println("Unexpected data chunk of type " + chunk);
+ }
break;
case ATTACH_EXTENSION:
attachExtension = (StringChunk)chunk;
--- /dev/null
+/* ====================================================================
+ 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.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.poi.hsmf.MAPIMessage;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+
+/**
+ * A Chunk that is just a placeholder in the
+ * MAPIMessage directory structure, which
+ * contains children.
+ * This is most commonly used with nested
+ * MAPIMessages
+ */
+public class DirectoryChunk extends Chunk {
+ private DirectoryNode dir;
+
+ public DirectoryChunk(DirectoryNode dir, String namePrefix, int chunkId, int type) {
+ super(namePrefix, chunkId, type);
+ this.dir = dir;
+ }
+
+ /**
+ * Returns the directory entry for this chunk.
+ * You can then use standard POIFS methods to
+ * enumerate the entries in it.
+ */
+ public DirectoryNode getDirectory() {
+ return dir;
+ }
+
+ /**
+ * Treats the directory as an embeded MAPIMessage
+ * (it normally is one), and returns a MAPIMessage
+ * object to process it with.
+ */
+ public MAPIMessage getAsEmbededMessage() throws IOException {
+ return new MAPIMessage(dir, dir.getFileSystem());
+ }
+
+ @Override
+ public void readValue(InputStream value) {
+ // DirectoryChunks have 0 byte contents
+ }
+
+ @Override
+ public void writeValue(OutputStream out) {
+ // DirectoryChunks have 0 byte contents
+ }
+}
public static final int LONG = 0x0003;
public static final int TIME = 0x0040;
public static final int BOOLEAN = 0x000B;
+ public static final int DIRECTORY = 0x000D;
public static String asFileEnding(int type) {
String str = Integer.toHexString(type).toUpperCase();
import org.apache.poi.hsmf.datatypes.Chunk;
import org.apache.poi.hsmf.datatypes.ChunkGroup;
import org.apache.poi.hsmf.datatypes.Chunks;
+import org.apache.poi.hsmf.datatypes.DirectoryChunk;
import org.apache.poi.hsmf.datatypes.MessageSubmissionChunk;
import org.apache.poi.hsmf.datatypes.NameIdChunks;
import org.apache.poi.hsmf.datatypes.RecipientChunks;
protected static void processChunks(DirectoryNode node, ChunkGroup grouping) {
for(Entry entry : node) {
if(entry instanceof DocumentNode) {
- process((DocumentNode)entry, grouping);
+ process(entry, grouping);
+ } else if(entry instanceof DirectoryNode) {
+ if(entry.getName().endsWith(Types.asFileEnding(Types.DIRECTORY))) {
+ process(entry, grouping);
+ }
}
}
}
/**
* Creates a chunk, and gives it to its parent group
*/
- protected static void process(DocumentNode entry, ChunkGroup grouping) {
+ protected static void process(Entry entry, ChunkGroup grouping) {
String entryName = entry.getName();
if(entryName.length() < 9) {
case Types.BINARY:
chunk = new ByteChunk(namePrefix, chunkId, type);
break;
+ case Types.DIRECTORY:
+ if(entry instanceof DirectoryNode) {
+ chunk = new DirectoryChunk((DirectoryNode)entry, namePrefix, chunkId, type);
+ }
+ break;
case Types.ASCII_STRING:
case Types.UNICODE_STRING:
chunk = new StringChunk(namePrefix, chunkId, type);
}
if(chunk != null) {
- try {
- DocumentInputStream inp = new DocumentInputStream(entry);
- chunk.readValue(inp);
- grouping.record(chunk);
- } catch(IOException e) {
- System.err.println("Error reading from part " + entry.getName() + " - " + e.toString());
- }
+ if(entry instanceof DocumentNode) {
+ try {
+ DocumentInputStream inp = new DocumentInputStream((DocumentNode)entry);
+ chunk.readValue(inp);
+ grouping.record(chunk);
+ } catch(IOException e) {
+ System.err.println("Error reading from part " + entry.getName() + " - " + e.toString());
+ }
+ } else {
+ grouping.record(chunk);
+ }
}
} catch(NumberFormatException e) {
// Name in the wrong format
* @author Nicolas Bureau
*/
public class TestFileWithAttachmentsRead extends TestCase {
- private MAPIMessage mapiMessage;
+ private MAPIMessage twoSimpleAttachments;
+ private MAPIMessage pdfMsgAttachments;
/**
* Initialize this test, load up the attachment_test_msg.msg mapi message.
*/
public TestFileWithAttachmentsRead() throws IOException {
POIDataSamples samples = POIDataSamples.getHSMFInstance();
- this.mapiMessage = new MAPIMessage(samples.openResourceAsStream("attachment_test_msg.msg"));
+ this.twoSimpleAttachments = new MAPIMessage(samples.openResourceAsStream("attachment_test_msg.msg"));
+ this.pdfMsgAttachments = new MAPIMessage(samples.openResourceAsStream("attachment_msg_pdf.msg"));
}
/**
*
*/
public void testRetrieveAttachments() {
- AttachmentChunks[] attachments = mapiMessage.getAttachmentFiles();
- int obtained = attachments.length;
- int expected = 2;
-
- TestCase.assertEquals(obtained, expected);
+ // Simple file
+ AttachmentChunks[] attachments = twoSimpleAttachments.getAttachmentFiles();
+ assertEquals(2, attachments.length);
+
+ // Other file
+ attachments = pdfMsgAttachments.getAttachmentFiles();
+ assertEquals(2, attachments.length);
}
/**
* Test to see if attachments are not empty.
*/
public void testReadAttachments() throws IOException {
- AttachmentChunks[] attachments = mapiMessage.getAttachmentFiles();
+ AttachmentChunks[] attachments = twoSimpleAttachments.getAttachmentFiles();
// Basic checks
for (AttachmentChunks attachment : attachments) {
AttachmentChunks attachment;
// Now check in detail
- attachment = mapiMessage.getAttachmentFiles()[0];
+ attachment = twoSimpleAttachments.getAttachmentFiles()[0];
assertEquals("TEST-U~1.DOC", attachment.attachFileName.toString());
assertEquals("test-unicode.doc", attachment.attachLongFileName.toString());
assertEquals(".doc", attachment.attachExtension.getValue());
assertEquals(null, attachment.attachMimeTag);
assertEquals(24064, attachment.attachData.getValue().length);
- attachment = mapiMessage.getAttachmentFiles()[1];
+ attachment = twoSimpleAttachments.getAttachmentFiles()[1];
assertEquals("pj1.txt", attachment.attachFileName.toString());
assertEquals("pj1.txt", attachment.attachLongFileName.toString());
assertEquals(".txt", attachment.attachExtension.getValue());
assertEquals(null, attachment.attachMimeTag);
assertEquals(89, attachment.attachData.getValue().length);
}
+
+ /**
+ * Test that we can handle both PDF and MSG attachments
+ */
+ public void testReadMsgAttachments() throws Exception {
+ AttachmentChunks[] attachments = pdfMsgAttachments.getAttachmentFiles();
+ assertEquals(2, attachments.length);
+
+ AttachmentChunks attachment;
+
+ // Second is a PDF
+ attachment = pdfMsgAttachments.getAttachmentFiles()[1];
+ assertEquals("smbprn~1.pdf", attachment.attachFileName.toString());
+ assertEquals("smbprn.00009008.KdcPjl.pdf", attachment.attachLongFileName.toString());
+ assertEquals(".pdf", attachment.attachExtension.getValue());
+ assertEquals(null, attachment.attachMimeTag);
+ assertEquals(null, attachment.attachmentDirectory);
+ assertEquals(13539, attachment.attachData.getValue().length);
+
+ // First in a nested message
+ attachment = pdfMsgAttachments.getAttachmentFiles()[0];
+ assertEquals("Test Attachment", attachment.attachFileName.toString());
+ assertEquals(null, attachment.attachLongFileName);
+ assertEquals(null, attachment.attachExtension);
+ assertEquals(null, attachment.attachMimeTag);
+ assertEquals(null, attachment.attachData);
+ assertNotNull(attachment.attachmentDirectory);
+
+ // Check we can see some bits of it
+ MAPIMessage nested = attachment.attachmentDirectory.getAsEmbededMessage();
+ assertEquals(1, nested.getRecipientNamesList().length);
+ assertEquals("Nick Booth", nested.getRecipientNames());
+ assertEquals("Test Attachment", nested.getConversationTopic());
+ }
}