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
This commit is contained in:
Nick Burch 2011-01-12 18:14:49 +00:00
parent ee734212a9
commit e1b8144c18
4 changed files with 129 additions and 17 deletions

View File

@ -28,6 +28,7 @@ import org.apache.poi.hsmf.datatypes.Types;
import org.apache.poi.util.HexDump; import org.apache.poi.util.HexDump;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.StringUtil;
/** /**
* A pure-MAPI attribute which applies to a {@link HMEFMessage} * A pure-MAPI attribute which applies to a {@link HMEFMessage}
@ -103,9 +104,31 @@ public class MAPIAttribute {
// MAPI property, grab the details of it // MAPI property, grab the details of it
MAPIProperty prop = MAPIProperty.get(id); MAPIProperty prop = MAPIProperty.get(id);
if(id >= 0x8000 && id <= 0xFFFF) { if(id >= 0x8000 && id <= 0xFFFF) {
// TODO byte[] guid = new byte[16];
System.err.println("Not yet implemented for id " + id); IOUtils.readFully(inp, guid);
break; 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) // Now read in the value(s)
@ -117,6 +140,7 @@ public class MAPIAttribute {
int len = getLength(type, inp); int len = getLength(type, inp);
byte[] data = new byte[len]; byte[] data = new byte[len];
IOUtils.readFully(inp, data); IOUtils.readFully(inp, data);
skipToBoundary(len, inp);
// Create // Create
MAPIAttribute attr; MAPIAttribute attr;
@ -126,12 +150,6 @@ public class MAPIAttribute {
attr = new MAPIAttribute(prop, type, data); attr = new MAPIAttribute(prop, type, data);
} }
attrs.add(attr); 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); 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);
}
}
} }

View File

@ -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/microsoft.exchange.data.contenttypes.tnef.tnefpropertyid%28v=EXCHG.140%29.aspx
* http://msdn.microsoft.com/en-us/library/ms526356%28v=exchg.10%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<Integer, MAPIProperty> attributes = new HashMap<Integer, MAPIProperty>(); private static Map<Integer, MAPIProperty> attributes = new HashMap<Integer, MAPIProperty>();
public static final MAPIProperty AB_DEFAULT_DIR = public static final MAPIProperty AB_DEFAULT_DIR =
@ -1021,6 +1021,8 @@ public final class MAPIProperty {
new MAPIProperty(-1, -1, "Unknown", null); new MAPIProperty(-1, -1, "Unknown", null);
// 0x8??? ones are outlook specific, and not standard MAPI // 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.name = name;
this.mapiProperty = mapiProperty; this.mapiProperty = mapiProperty;
// Store it for lookup // If it isn't unknown or custom, store it for lookup
if(attributes.containsKey(id)) { if(id == -1 || (id >= ID_FIRST_CUSTOM && id <= ID_LAST_CUSTOM)
throw new IllegalArgumentException( || (this instanceof CustomMAPIProperty)) {
"Duplicate MAPI Property with ID " + id + " : " + // Custom/Unknown, skip
toString() + " vs " + attributes.get(id).toString() } 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() { public String toString() {
StringBuffer str = new StringBuffer(); StringBuffer str = new StringBuffer();
@ -1057,6 +1064,7 @@ public final class MAPIProperty {
} }
return str.toString(); return str.toString();
} }
public static MAPIProperty get(int id) { public static MAPIProperty get(int id) {
MAPIProperty attr = attributes.get(id); MAPIProperty attr = attributes.get(id);
if(attr != null) { if(attr != null) {
@ -1068,4 +1076,14 @@ public final class MAPIProperty {
public static Collection<MAPIProperty> getAll() { public static Collection<MAPIProperty> getAll() {
return Collections.unmodifiableCollection( attributes.values() ); 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);
}
}
} }

View File

@ -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<MAPIProperty> 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<MAPIProperty> all = MAPIProperty.getAll();
assertEquals(false, all.contains(c1));
assertEquals(false, all.contains(c2a));
assertEquals(false, all.contains(c2b));
}
}

View File

@ -43,4 +43,9 @@ public final class TestTypes extends TestCase {
assertEquals("0102", Types.asFileEnding(0x0102)); assertEquals("0102", Types.asFileEnding(0x0102));
assertEquals("FEDC", Types.asFileEnding(0xfedc)); assertEquals("FEDC", Types.asFileEnding(0xfedc));
} }
public void testName() {
assertEquals("ASCII String", Types.asName(Types.ASCII_STRING));
assertEquals("Boolean", Types.asName(Types.BOOLEAN));
}
} }