From e1b8144c18b7d7bc8070b0b81f3a5d56c1817900 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Wed, 12 Jan 2011 18:14:49 +0000 Subject: [PATCH] Add support for custom MAPI Properties (0x8000 and above, plus unknown lower ones) git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1058262 13f79535-47bb-0310-9956-ffa450edef68 --- .../org/apache/poi/hmef/MAPIAttribute.java | 44 ++++++++++--- .../poi/hsmf/datatypes/MAPIProperty.java | 34 +++++++--- .../poi/hsmf/datatypes/TestMAPIProperty.java | 63 +++++++++++++++++++ .../apache/poi/hsmf/datatypes/TestTypes.java | 5 ++ 4 files changed, 129 insertions(+), 17 deletions(-) create mode 100644 src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestMAPIProperty.java diff --git a/src/scratchpad/src/org/apache/poi/hmef/MAPIAttribute.java b/src/scratchpad/src/org/apache/poi/hmef/MAPIAttribute.java index badf25ce71..3014f48d2d 100644 --- a/src/scratchpad/src/org/apache/poi/hmef/MAPIAttribute.java +++ b/src/scratchpad/src/org/apache/poi/hmef/MAPIAttribute.java @@ -28,6 +28,7 @@ import org.apache.poi.hsmf.datatypes.Types; import org.apache.poi.util.HexDump; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.StringUtil; /** * A pure-MAPI attribute which applies to a {@link HMEFMessage} @@ -103,9 +104,31 @@ public class MAPIAttribute { // MAPI property, grab the details of it MAPIProperty prop = MAPIProperty.get(id); if(id >= 0x8000 && id <= 0xFFFF) { - // TODO - System.err.println("Not yet implemented for id " + id); - break; + byte[] guid = new byte[16]; + IOUtils.readFully(inp, guid); + int mptype = LittleEndian.readInt(inp); + + // Get the name of it + String name; + if(mptype == 0) { + // It's based on a normal one + int mpid = LittleEndian.readInt(inp); + MAPIProperty base = MAPIProperty.get(mpid); + name = base.name; + } else { + // Custom name was stored + int mplen = LittleEndian.readInt(inp); + byte[] mpdata = new byte[mplen]; + IOUtils.readFully(inp, mpdata); + name = StringUtil.getFromUnicodeLE(mpdata, 0, (mplen/2)-1); + skipToBoundary(mplen, inp); + } + + // Now create + prop = MAPIProperty.createCustom(id, type, name); + } + if(prop == MAPIProperty.UNKNOWN) { + prop = MAPIProperty.createCustom(id, type, "(unknown " + Integer.toHexString(id) + ")"); } // Now read in the value(s) @@ -117,6 +140,7 @@ public class MAPIAttribute { int len = getLength(type, inp); byte[] data = new byte[len]; IOUtils.readFully(inp, data); + skipToBoundary(len, inp); // Create MAPIAttribute attr; @@ -126,12 +150,6 @@ public class MAPIAttribute { attr = new MAPIAttribute(prop, type, data); } attrs.add(attr); - - // Data is always padded out to a 4 byte boundary - if(len % 4 != 0) { - byte[] padding = new byte[4 - (len % 4)]; - IOUtils.readFully(inp, padding); - } } } @@ -167,4 +185,12 @@ public class MAPIAttribute { throw new IllegalArgumentException("Unknown type " + type); } } + private static void skipToBoundary(int length, InputStream inp) throws IOException { + // Data is always padded out to a 4 byte boundary + if(length % 4 != 0) { + int skip = 4 - (length % 4); + byte[] padding = new byte[skip]; + IOUtils.readFully(inp, padding); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java index d5301c9282..d5b274a498 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java @@ -37,7 +37,7 @@ import java.util.Map; * http://msdn.microsoft.com/en-us/library/microsoft.exchange.data.contenttypes.tnef.tnefpropertyid%28v=EXCHG.140%29.aspx * http://msdn.microsoft.com/en-us/library/ms526356%28v=exchg.10%29.aspx */ -public final class MAPIProperty { +public class MAPIProperty { private static Map attributes = new HashMap(); public static final MAPIProperty AB_DEFAULT_DIR = @@ -1021,6 +1021,8 @@ public final class MAPIProperty { new MAPIProperty(-1, -1, "Unknown", null); // 0x8??? ones are outlook specific, and not standard MAPI + private static final int ID_FIRST_CUSTOM = 0x8000; + private static final int ID_LAST_CUSTOM = 0xFFFE; /* --------------------------------------------------------------------- */ @@ -1035,14 +1037,19 @@ public final class MAPIProperty { this.name = name; this.mapiProperty = mapiProperty; - // Store it for lookup - if(attributes.containsKey(id)) { - throw new IllegalArgumentException( - "Duplicate MAPI Property with ID " + id + " : " + - toString() + " vs " + attributes.get(id).toString() - ); + // If it isn't unknown or custom, store it for lookup + if(id == -1 || (id >= ID_FIRST_CUSTOM && id <= ID_LAST_CUSTOM) + || (this instanceof CustomMAPIProperty)) { + // Custom/Unknown, skip + } else { + if(attributes.containsKey(id)) { + throw new IllegalArgumentException( + "Duplicate MAPI Property with ID " + id + " : " + + toString() + " vs " + attributes.get(id).toString() + ); + } + attributes.put(id, this); } - attributes.put(id, this); } public String toString() { StringBuffer str = new StringBuffer(); @@ -1057,6 +1064,7 @@ public final class MAPIProperty { } return str.toString(); } + public static MAPIProperty get(int id) { MAPIProperty attr = attributes.get(id); if(attr != null) { @@ -1068,4 +1076,14 @@ public final class MAPIProperty { public static Collection getAll() { return Collections.unmodifiableCollection( attributes.values() ); } + + public static MAPIProperty createCustom(int id, int type, String name) { + return new CustomMAPIProperty(id, type, name, null); + } + + private static class CustomMAPIProperty extends MAPIProperty { + private CustomMAPIProperty(int id, int usualType, String name, String mapiProperty) { + super(id, usualType, name, mapiProperty); + } + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestMAPIProperty.java b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestMAPIProperty.java new file mode 100644 index 0000000000..8304d40dc1 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestMAPIProperty.java @@ -0,0 +1,63 @@ +/* ==================================================================== + 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.util.Collection; +import java.util.List; + +import junit.framework.TestCase; + +/** + * Checks various MAPIProperty related logic + */ +public final class TestMAPIProperty extends TestCase { + public void testGet() throws Exception { + assertEquals(MAPIProperty.DISPLAY_NAME, MAPIProperty.get(MAPIProperty.DISPLAY_NAME.id)); + assertEquals(MAPIProperty.DISPLAY_BCC, MAPIProperty.get(MAPIProperty.DISPLAY_BCC.id)); + assertNotSame(MAPIProperty.DISPLAY_BCC, MAPIProperty.get(MAPIProperty.DISPLAY_CC.id)); + } + + public void testGetAll() throws Exception { + Collection all = MAPIProperty.getAll(); + assertEquals(true, all.contains(MAPIProperty.DISPLAY_NAME)); + assertEquals(true, all.contains(MAPIProperty.DISPLAY_CC)); + + // Won't contain custom + assertEquals(false, all.contains(MAPIProperty.createCustom(1, 1, ""))); + + // Won't contain unknown + assertEquals(false, all.contains(MAPIProperty.UNKNOWN)); + } + + public void testCustom() throws Exception { + MAPIProperty c1 = MAPIProperty.createCustom(1, 1, ""); + MAPIProperty c2a = MAPIProperty.createCustom(2, 1, "2"); + MAPIProperty c2b = MAPIProperty.createCustom(2, 1, "2"); + + // New object each time + assertNotSame(c1, c2a); + assertNotSame(c1, c2b); + assertNotSame(c2a, c2b); + + // Won't be in all list + Collection all = MAPIProperty.getAll(); + assertEquals(false, all.contains(c1)); + assertEquals(false, all.contains(c2a)); + assertEquals(false, all.contains(c2b)); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestTypes.java b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestTypes.java index 0a2de38ac3..152ca4dee0 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestTypes.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestTypes.java @@ -43,4 +43,9 @@ public final class TestTypes extends TestCase { assertEquals("0102", Types.asFileEnding(0x0102)); assertEquals("FEDC", Types.asFileEnding(0xfedc)); } + + public void testName() { + assertEquals("ASCII String", Types.asName(Types.ASCII_STRING)); + assertEquals("Boolean", Types.asName(Types.BOOLEAN)); + } }