Start to decode the MAPI Properties in the TNEF stream for HMEF
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1058226 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
5f669d2a4d
commit
fd16797edd
@ -26,12 +26,21 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
public final class Attachment {
|
public final class Attachment {
|
||||||
private final List<Attribute> attributes = new ArrayList<Attribute>();
|
private final List<Attribute> attributes = new ArrayList<Attribute>();
|
||||||
|
private final List<MAPIAttribute> mapiAttributes = new ArrayList<MAPIAttribute>();
|
||||||
|
|
||||||
protected void addAttribute(Attribute attr) {
|
protected void addAttribute(Attribute attr) {
|
||||||
attributes.add(attr);
|
attributes.add(attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void addAttribute(MAPIAttribute attr) {
|
||||||
|
mapiAttributes.add(attr);
|
||||||
|
}
|
||||||
|
|
||||||
public List<Attribute> getAttributes() {
|
public List<Attribute> getAttributes() {
|
||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<MAPIAttribute> getMAPIAttributes() {
|
||||||
|
return mapiAttributes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ public final class HMEFMessage {
|
|||||||
|
|
||||||
private int fileId;
|
private int fileId;
|
||||||
private List<Attribute> messageAttributes = new ArrayList<Attribute>();
|
private List<Attribute> messageAttributes = new ArrayList<Attribute>();
|
||||||
|
private List<MAPIAttribute> mapiAttributes = new ArrayList<MAPIAttribute>();
|
||||||
private List<Attachment> attachments = new ArrayList<Attachment>();
|
private List<Attachment> attachments = new ArrayList<Attachment>();
|
||||||
|
|
||||||
public HMEFMessage(InputStream inp) throws IOException {
|
public HMEFMessage(InputStream inp) throws IOException {
|
||||||
@ -54,6 +55,24 @@ public final class HMEFMessage {
|
|||||||
|
|
||||||
// Now begin processing the contents
|
// Now begin processing the contents
|
||||||
process(inp, 0);
|
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 {
|
private void process(InputStream inp, int lastLevel) throws IOException {
|
||||||
|
170
src/scratchpad/src/org/apache/poi/hmef/MAPIAttribute.java
Normal file
170
src/scratchpad/src/org/apache/poi/hmef/MAPIAttribute.java
Normal file
@ -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.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.poi.hmef.Attribute;
|
import org.apache.poi.hmef.Attribute;
|
||||||
import org.apache.poi.hmef.HMEFMessage;
|
import org.apache.poi.hmef.HMEFMessage;
|
||||||
|
import org.apache.poi.hmef.MAPIAttribute;
|
||||||
import org.apache.poi.util.HexDump;
|
import org.apache.poi.util.HexDump;
|
||||||
import org.apache.poi.util.LittleEndian;
|
import org.apache.poi.util.LittleEndian;
|
||||||
|
|
||||||
@ -104,6 +106,14 @@ public final class HMEFDumper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
System.out.println();
|
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;
|
public static final int UNICODE_STRING = 0x001F;
|
||||||
|
|
||||||
/** MultiValued - Value part contains multiple values */
|
/** 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) {
|
public static String asFileEnding(int type) {
|
||||||
|
Loading…
Reference in New Issue
Block a user