4

我正在使用 Apache POI - HSMF 从 Outlook 的 msg 文件中提取附件。除了嵌套消息外,它工作正常。如果一个味精附加到另一个味精,我可以得到文件。如果消息是嵌套的,我会得到信息,但我需要文件。

MAPIMessage msg = new MAPIMessage(fileName)
for(AttachmentChunks attachment : msg.getAttachmentFiles()) {
    if(attachment.attachmentDirectory!=null){
        MAPIMessage nestedMsg attachment.attachmentDirectory.getAsEmbededMessage();
        // now save nestedMsg as a msg-file
    }
}

是否可以将嵌套的消息文件保存为常规的 msg 文件?

4

3 回答 3

4

促进对答案的评论。我可以告诉您如何将嵌入的 Outlook 消息提取到一个新文件中,然后 Apache POI 将愉快地打开该文件。我不太确定的是,嵌入的邮件是否包含 Outlook 期望在独立邮件中找到的所有内容,所以我不能保证生成的文件会在没有问题的情况下使用 Outlook 打开......

首先,在 Outlook 中嵌入资源。根据它的类型,它可能存储在常规字节块中,也可能存储在某种其他类型的特殊块中(例如压缩 RTF),或者它可能是文件中的自包含子目录。嵌入式消息以后一种方式存储。

如果您想提取嵌入的邮件,您需要创建一个新的 OLE2 文件容器,使用POIFSFileSystem(所有 Outlook 邮件都存储在 OLE2 容器中)。然后,您需要将源 OLE2 容器中嵌入消息目录的内容复制到新容器的根目录中。最后,将 POIFSFileSystem 写入一个新文件,您的提取就完成了!

您可能想要执行以下操作:

 MAPIMessage msg = new MAPIMessage(new NPOIFSFileSytem(new File("test.msg")));
 if (msg.attachmentChunks != null) {
    int number = 0;
    for (AttachmentChunk att : msg.attachmentChunks) {
        if (att.attachmentDirectory != null) {
           number++;
           POIFSFileSystem newMsg = new POIFSFileSystem();
           EntryUtils.copyNodes( att.attachmentDirectory, newMsg.getRoot() );
           FileOutputStream out = new FileOutputStream("embedded-" + number + ".msg");
           newMsg.write(out);
           out.close();
        }
    }
 }

如果 Outlook 有问题,请尝试在 Outlook 中打开源文件,将嵌入的消息保存到新文件,然后使用org.apache.poi.poifs.dev.POIFSLister和之org.apache.poi.poifs.dev.POIFSDump类的方法比较 Outlook 提取文件和 POI 提取文件,看看是否能发现任何变化这种前景确实....

于 2012-11-25T19:43:48.177 回答
4

里面的代码TestExtractEmbeddedMSG.java有点过时了。可以进行单元测试,但进一步调查提取附加的 MSG 文件会更简单一些。

基本上嵌入式MSG文件的提取需要特殊处理,因为嵌入式MSG不是一个简单的BLOB,它只是整个结构化存储容器中的一个子目录。该子目录包含制作嵌入式 MSG 的所有内容,除了我目前知道的两件事:

  • 子目录不包含任何“名称 id”属性条目(由其属性集 id 限定范围的非固定 MAPI 属性的映射),它为整个结构化存储全局定义一次
  • 属性流的二进制格式与顶级格式略有不同,它遗漏了 8 个字节的保留数据(我不知道微软这样做的原因,但它是根据文档)。

为了将嵌入式 MSG 子目录转换为顶级子目录,需要将顶级“名称 id”属性复制到新的 MSG 中(这可以选择优化,仅嵌入真正引用的条目MSG 被复制,但所有“名称 id”条目都需要解析并使用真正引用的条目重新构建),并且必须使用额外的 8 字节保留数据重新构建属性流。

这是我现在的做法。 rootmsg是顶层MSGattachedmsg的文件系统根,是嵌入式MSG的文件系统根,可以通过调用getDirectory对象MAPIMessage获得。MAPIMessage可以通过调用对象来检索嵌入getEmbeddedMessageAttachmentChunks对象。

构建结构化存储文件系统:

  private static POIFSFileSystem rebuildMessageToStream(DirectoryNode rootmsg, DirectoryNode attachedmsg) throws Exception
  {
    //
    // Create new MSG file system and copy all entries.
    //
    POIFSFileSystem newDoc = new POIFSFileSystem();
    //
    // Copy nameid entries from root message.
    //
    if (rootmsg != null) {
      for (Entry entry : rootmsg) {
        if (entry.getName().startsWith(NameIdChunks.NAME)) {
          EntryUtils.copyNodeRecursively(entry, newDoc.getRoot());
        }
      }
    }
    //
    // Copy entries from origin message.
    //
    for (Entry entry : attachedmsg) {
      if (entry.getName().startsWith(PropertiesChunk.NAME) && entry.isDocumentEntry()) {
        if (rootmsg != null) {
          //
          // Rebuild properties stream: Add additional 8 reserved bytes
          // to convert embedded message properties stream to root message properties stream.
          //
          // See MessagePropertiesChunk.writeHeaderData
          //
          DocumentEntry d = (DocumentEntry)entry;
          DocumentInputStream dstream = new DocumentInputStream(d);
          ByteArrayOutputStream rootps = new ByteArrayOutputStream(d.getSize() + 8);
          //
          // Copy first 8 bytes of reserved zeros plus 16 bytes for recipient/attachment counter.
          //
          byte[] data = new byte[24];
          dstream.readFully(data);
          rootps.write(data);
          //
          // Additional 8 bytes of reserved zeros.
          //
          rootps.write(new byte[8]);
          //
          // Properties (remaining data).
          //
          IOUtils.copy(dstream, rootps);
          //
          // Create properties stream entry.
          //
          newDoc.getRoot().createDocument(entry.getName(), new ByteArrayInputStream(rootps.toByteArray()));
          dstream.close();
        }
        else {
          //
          // Copy properties stream unmodified.
          //
          EntryUtils.copyNodeRecursively(entry, newDoc.getRoot());
        }
      }
      else {
        //
        // Copy other entry.
        //
        EntryUtils.copyNodeRecursively(entry, newDoc.getRoot());
      }
    }
    return newDoc;
  }

序列化结构化存储:

        try (POIFSFileSystem extractedAttachedMsg = rebuildMessageToStream(rootmsg, attachedmsg)) {
            try (ByteArrayOutputStream extractedAttachedMsgOut = new ByteArrayOutputStream()) {
                extractedAttachedMsg.writeFilesystem(extractedAttachedMsgOut);
                byte[] extratedAttachedMsgRaw = extractedAttachedMsgOut.toByteArray();
                // this byte array can be persisted to disk and opened in MS Outlook
            }
        }
于 2020-06-30T14:28:49.033 回答
2

我为此添加了一些必要的功能到 POI 和一个单元测试,它提取可以在 Outlook 中打开的嵌入式 MSG。命名 id 属性的处理可能不完整(这与验证我的增强功能的单元测试无关)。

https://svn.apache.org/viewvc/poi/trunk/src/scratchpad/testcases/org/apache/poi/hsmf/TestExtractEmbeddedMSG.java?view=markup

单元测试示例:

获取附加的味精

MAPIMessage attachedMsg = attachments[0].getEmbeddedMessage();

重建附加味精的方法

private POIFSFileSystem rebuildFromAttached(MAPIMessage attachedMsg) throws IOException {
    // Create new MSG and copy properties.
    POIFSFileSystem newDoc = new POIFSFileSystem();
    MessagePropertiesChunk topLevelChunk = new MessagePropertiesChunk(null);
    // Copy attachments and recipients.
    int recipientscount = 0;
    int attachmentscount = 0;
    for (Entry entry : attachedMsg.getDirectory()) {
        if (entry.getName().startsWith(RecipientChunks.PREFIX)) {
            recipientscount++;
            DirectoryEntry newDir = newDoc.createDirectory(entry.getName());
            for (Entry e : ((DirectoryEntry) entry)) {
                EntryUtils.copyNodeRecursively(e, newDir);
            }
        } else if (entry.getName().startsWith(AttachmentChunks.PREFIX)) {
            attachmentscount++;
            DirectoryEntry newDir = newDoc.createDirectory(entry.getName());
            for (Entry e : ((DirectoryEntry) entry)) {
                EntryUtils.copyNodeRecursively(e, newDir);
            }
        }
    }
    // Copy properties from properties stream.
    MessagePropertiesChunk mpc = attachedMsg.getMainChunks().getMessageProperties();
    for (Map.Entry<MAPIProperty, PropertyValue> p : mpc.getRawProperties().entrySet()) {
        PropertyValue val = p.getValue();
        if (!(val instanceof ChunkBasedPropertyValue)) {
            MAPIType type = val.getActualType();
            if (type != null && type != Types.UNKNOWN) {
                topLevelChunk.setProperty(val);
            }
        }
    }
    // Create nameid entries.
    DirectoryEntry nameid = newDoc.getRoot().createDirectory(NameIdChunks.NAME);
    // GUID stream
    nameid.createDocument(PropertiesChunk.DEFAULT_NAME_PREFIX + "00020102", new ByteArrayInputStream(new byte[0]));
    // Entry stream
    nameid.createDocument(PropertiesChunk.DEFAULT_NAME_PREFIX + "00030102", new ByteArrayInputStream(new byte[0]));
    // String stream
    nameid.createDocument(PropertiesChunk.DEFAULT_NAME_PREFIX + "00040102", new ByteArrayInputStream(new byte[0]));
    // Base properties.
    // Attachment/Recipient counter.
    topLevelChunk.setAttachmentCount(attachmentscount);
    topLevelChunk.setRecipientCount(recipientscount);
    topLevelChunk.setNextAttachmentId(attachmentscount);
    topLevelChunk.setNextRecipientId(recipientscount);
    // Unicode string format.
    byte[] storeSupportMaskData = new byte[4];
    PropertyValue.LongPropertyValue storeSupportPropertyValue = new PropertyValue.LongPropertyValue(MAPIProperty.STORE_SUPPORT_MASK,
            MessagePropertiesChunk.PROPERTIES_FLAG_READABLE | MessagePropertiesChunk.PROPERTIES_FLAG_WRITEABLE,
            storeSupportMaskData);
    storeSupportPropertyValue.setValue(0x00040000);
    topLevelChunk.setProperty(storeSupportPropertyValue);
    topLevelChunk.setProperty(new PropertyValue(MAPIProperty.HASATTACH,
            MessagePropertiesChunk.PROPERTIES_FLAG_READABLE | MessagePropertiesChunk.PROPERTIES_FLAG_WRITEABLE,
            attachmentscount == 0 ? new byte[] { 0 } : new byte[] { 1 }));
    // Copy properties from MSG file system.
    for (Chunk chunk : attachedMsg.getMainChunks().getChunks()) {
        if (!(chunk instanceof MessagePropertiesChunk)) {
            String entryName = chunk.getEntryName();
            String entryType = entryName.substring(entryName.length() - 4);
            int iType = Integer.parseInt(entryType, 16);
            MAPIType type = Types.getById(iType);
            if (type != null && type != Types.UNKNOWN) {
                MAPIProperty mprop = MAPIProperty.createCustom(chunk.getChunkId(), type, chunk.getEntryName());
                ByteArrayOutputStream data = new ByteArrayOutputStream();
                chunk.writeValue(data);
                PropertyValue pval = new PropertyValue(mprop, MessagePropertiesChunk.PROPERTIES_FLAG_READABLE
                        | MessagePropertiesChunk.PROPERTIES_FLAG_WRITEABLE, data.toByteArray(), type);
                topLevelChunk.setProperty(pval);
            }
        }
    }
    topLevelChunk.writeProperties(newDoc.getRoot());
    return newDoc;
}

重建附加的味精

            try (POIFSFileSystem extractedAttachedMsg = rebuildFromAttached(attachedMsg)) {
                try (ByteArrayOutputStream extractedAttachedMsgOut = new ByteArrayOutputStream()) {
                    extractedAttachedMsg.writeFilesystem(extractedAttachedMsgOut);
                    byte[] extratedAttachedMsgRaw = extractedAttachedMsgOut.toByteArray();
                    // this byte array can be persisted to disk and opened in MS Outlook
                }
            }

问候, 多米尼克

于 2018-04-05T14:51:41.017 回答