git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1058226 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_8_BETA1
@@ -26,12 +26,21 @@ import java.util.List; | |||
*/ | |||
public final class Attachment { | |||
private final List<Attribute> attributes = new ArrayList<Attribute>(); | |||
private final List<MAPIAttribute> mapiAttributes = new ArrayList<MAPIAttribute>(); | |||
protected void addAttribute(Attribute attr) { | |||
attributes.add(attr); | |||
} | |||
protected void addAttribute(MAPIAttribute attr) { | |||
mapiAttributes.add(attr); | |||
} | |||
public List<Attribute> getAttributes() { | |||
return attributes; | |||
} | |||
public List<MAPIAttribute> getMAPIAttributes() { | |||
return mapiAttributes; | |||
} | |||
} |
@@ -37,6 +37,7 @@ public final class HMEFMessage { | |||
private int fileId; | |||
private List<Attribute> messageAttributes = new ArrayList<Attribute>(); | |||
private List<MAPIAttribute> mapiAttributes = new ArrayList<MAPIAttribute>(); | |||
private List<Attachment> attachments = new ArrayList<Attachment>(); | |||
public HMEFMessage(InputStream inp) throws IOException { | |||
@@ -54,6 +55,24 @@ public final class HMEFMessage { | |||
// Now begin processing the contents | |||
process(inp, 0); | |||
// Finally expand out the MAPI Attributes | |||
for(Attribute attr : messageAttributes) { | |||
if(attr.getId() == Attribute.ID_MAPIPROPERTIES) { | |||
mapiAttributes.addAll( | |||
MAPIAttribute.create(attr) | |||
); | |||
} | |||
} | |||
for(Attachment attachment : attachments) { | |||
for(Attribute attr : attachment.getAttributes()) { | |||
if(attr.getId() == Attribute.ID_MAPIPROPERTIES) { | |||
attachment.getMAPIAttributes().addAll( | |||
MAPIAttribute.create(attr) | |||
); | |||
} | |||
} | |||
} | |||
} | |||
private void process(InputStream inp, int lastLevel) throws IOException { |
@@ -0,0 +1,170 @@ | |||
/* ==================================================================== | |||
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.hmef; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.ArrayList; | |||
import java.util.List; | |||
import org.apache.poi.hsmf.datatypes.MAPIProperty; | |||
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; | |||
/** | |||
* A pure-MAPI attribute which applies to a {@link HMEFMessage} | |||
* or one of its {@link Attachment}s. | |||
*/ | |||
public class MAPIAttribute { | |||
private final MAPIProperty property; | |||
private final int type; | |||
private final byte[] data; | |||
/** | |||
* Constructs a single new attribute from | |||
* the contents of the stream | |||
*/ | |||
public MAPIAttribute(MAPIProperty property, int type, byte[] data) { | |||
this.property = property; | |||
this.type = type; | |||
this.data = data; | |||
} | |||
public MAPIProperty getProperty() { | |||
return property; | |||
} | |||
public int getType() { | |||
return type; | |||
} | |||
public byte[] getData() { | |||
return data; | |||
} | |||
public String toString() { | |||
return property.toString() + " " + HexDump.toHex(data); | |||
} | |||
/** | |||
* Parses a MAPI Properties TNEF Attribute, and returns | |||
* the list of MAPI Attributes contained within it | |||
*/ | |||
public static List<MAPIAttribute> create(Attribute parent) throws IOException { | |||
if(parent.getId() != Attribute.ID_MAPIPROPERTIES) { | |||
throw new IllegalArgumentException( | |||
"Can only create from a MAPIProperty attribute, " + | |||
"instead received a " + parent.getId() + " one" | |||
); | |||
} | |||
ByteArrayInputStream inp = new ByteArrayInputStream(parent.getData()); | |||
// First up, get the number of attributes | |||
int count = LittleEndian.readInt(inp); | |||
List<MAPIAttribute> attrs = new ArrayList<MAPIAttribute>(); | |||
// Now, read each one in in turn | |||
for(int i=0; i<count; i++) { | |||
int typeAndMV = LittleEndian.readUShort(inp); | |||
int id = LittleEndian.readUShort(inp); | |||
// Is it either Multi-Valued or Variable-Length? | |||
boolean isMV = false; | |||
boolean isVL = false; | |||
int type = typeAndMV; | |||
if( (typeAndMV & Types.MULTIVALUED_FLAG) > 0 ) { | |||
isMV = true; | |||
type -= Types.MULTIVALUED_FLAG; | |||
} | |||
if(type == Types.ASCII_STRING || type == Types.UNICODE_STRING || | |||
type == Types.BINARY || type == Types.DIRECTORY) { | |||
isVL = true; | |||
} | |||
// If it's a named property, rather than a standard | |||
// MAPI property, grab the details of it | |||
MAPIProperty prop = MAPIProperty.get(id); | |||
if(id >= 0x8000 && id <= 0xFFFF) { | |||
// TODO | |||
throw new UnsupportedOperationException("Not yet implemented for id " + id); | |||
} | |||
// Now read in the value(s) | |||
int values = 1; | |||
if(isMV || isVL) { | |||
values = LittleEndian.readInt(inp); | |||
} | |||
for(int j=0; j<values; j++) { | |||
int len = getLength(type, inp); | |||
byte[] data = new byte[len]; | |||
IOUtils.readFully(inp, data); | |||
// Create | |||
MAPIAttribute attr; | |||
if(type == Types.UNICODE_STRING || type == Types.ASCII_STRING) { | |||
attr = new MAPIStringAttribute(prop, type, data); | |||
} else { | |||
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[len % 4]; | |||
IOUtils.readFully(inp, padding); | |||
} | |||
} | |||
break; | |||
} | |||
// All done | |||
return attrs; | |||
} | |||
private static int getLength(int type, InputStream inp) throws IOException { | |||
switch(type) { | |||
case Types.NULL: | |||
return 0; | |||
case Types.BOOLEAN: | |||
case Types.SHORT: | |||
return 2; | |||
case Types.LONG: | |||
case Types.FLOAT: | |||
case Types.ERROR: | |||
return 4; | |||
case Types.LONG_LONG: | |||
case Types.DOUBLE: | |||
case Types.APP_TIME: | |||
case Types.TIME: | |||
case Types.CURRENCY: | |||
return 8; | |||
case Types.CLS_ID: | |||
return 16; | |||
case Types.ASCII_STRING: | |||
case Types.UNICODE_STRING: | |||
case Types.DIRECTORY: | |||
case Types.BINARY: | |||
// Need to read the length, as it varies | |||
return LittleEndian.readInt(inp); | |||
default: | |||
throw new IllegalArgumentException("Unknown type " + type); | |||
} | |||
} | |||
} |
@@ -0,0 +1,71 @@ | |||
/* ==================================================================== | |||
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.hmef; | |||
import java.io.ByteArrayInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.io.UnsupportedEncodingException; | |||
import java.util.ArrayList; | |||
import java.util.HashMap; | |||
import java.util.List; | |||
import java.util.Map; | |||
import org.apache.poi.util.HexDump; | |||
import org.apache.poi.util.IOUtils; | |||
import org.apache.poi.util.LittleEndian; | |||
import org.apache.poi.util.StringUtil; | |||
import org.apache.poi.hmef.Attribute.AttributeID; | |||
import org.apache.poi.hsmf.datatypes.MAPIProperty; | |||
import org.apache.poi.hsmf.datatypes.Types; | |||
/** | |||
* A pure-MAPI attribute holding a String, which applies | |||
* to a {@link HMEFMessage} or one of its {@link Attachment}s. | |||
*/ | |||
public final class MAPIStringAttribute extends MAPIAttribute { | |||
private static final String CODEPAGE = "CP1252"; | |||
private final String data; | |||
public MAPIStringAttribute(MAPIProperty property, int type, byte[] data) { | |||
super(property, type, data); | |||
String tmpData = null; | |||
if(type == Types.ASCII_STRING) { | |||
try { | |||
tmpData = new String(data, CODEPAGE); | |||
} catch(UnsupportedEncodingException e) { | |||
throw new RuntimeException("JVM Broken - core encoding " + CODEPAGE + " missing"); | |||
} | |||
} else if(type == Types.UNICODE_STRING) { | |||
tmpData = StringUtil.getFromUnicodeLE(data); | |||
} else { | |||
throw new IllegalArgumentException("Not a string type " + type); | |||
} | |||
// Strip off the null terminator if present | |||
if(tmpData.endsWith("\0")) { | |||
tmpData = tmpData.substring(0, tmpData.length()-1); | |||
} | |||
this.data = tmpData; | |||
} | |||
public String toString() { | |||
return getProperty().toString() + " " + data; | |||
} | |||
} |
@@ -20,9 +20,11 @@ package org.apache.poi.hmef.dev; | |||
import java.io.FileInputStream; | |||
import java.io.IOException; | |||
import java.io.InputStream; | |||
import java.util.List; | |||
import org.apache.poi.hmef.Attribute; | |||
import org.apache.poi.hmef.HMEFMessage; | |||
import org.apache.poi.hmef.MAPIAttribute; | |||
import org.apache.poi.util.HexDump; | |||
import org.apache.poi.util.LittleEndian; | |||
@@ -104,6 +106,14 @@ public final class HMEFDumper { | |||
} | |||
} | |||
System.out.println(); | |||
if(attr.getId() == Attribute.ID_MAPIPROPERTIES) { | |||
List<MAPIAttribute> attrs = MAPIAttribute.create(attr); | |||
for(MAPIAttribute ma : attrs) { | |||
System.out.println(indent + indent + ma); | |||
} | |||
System.out.println(); | |||
} | |||
} | |||
} | |||
} |
@@ -65,7 +65,7 @@ public final class Types { | |||
public static final int UNICODE_STRING = 0x001F; | |||
/** MultiValued - Value part contains multiple values */ | |||
public static final int MULTIVALUED_FLAT = 0x1000; | |||
public static final int MULTIVALUED_FLAG = 0x1000; | |||
public static String asFileEnding(int type) { |