reindent code - prepare for cleanups

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1773544 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Andreas Beeker 2016-12-10 23:35:12 +00:00
parent f27507244c
commit 6a8cb7493c
18 changed files with 2574 additions and 2574 deletions

View File

@ -34,134 +34,132 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
/** /**
* Collection of convenience chunks for standard parts of the MSG file attachment. * Collection of convenience chunks for standard parts of the MSG file
* attachment.
*/ */
public class AttachmentChunks implements ChunkGroup { public class AttachmentChunks implements ChunkGroup {
private static POILogger logger = POILogFactory.getLogger(AttachmentChunks.class); private static POILogger logger = POILogFactory.getLogger(AttachmentChunks.class);
public static final String PREFIX = "__attach_version1.0_#"; public static final String PREFIX = "__attach_version1.0_#";
public ByteChunk attachData;
public StringChunk attachExtension;
public StringChunk attachFileName;
public StringChunk attachLongFileName;
public StringChunk attachMimeTag;
public DirectoryChunk attachmentDirectory;
/**
* This is in WMF Format. You'll probably want to pass it
* to Apache Batik to turn it into a SVG that you can
* then display.
*/
public ByteChunk attachRenderingWMF;
/**
* What the POIFS name of this attachment is.
*/
private String poifsName;
/** Holds all the chunks that were found. */ public ByteChunk attachData;
private List<Chunk> allChunks = new ArrayList<Chunk>(); public StringChunk attachExtension;
public StringChunk attachFileName;
public StringChunk attachLongFileName;
public StringChunk attachMimeTag;
public DirectoryChunk attachmentDirectory;
/**
public AttachmentChunks(String poifsName) { * This is in WMF Format. You'll probably want to pass it to Apache Batik to
this.poifsName = poifsName; * turn it into a SVG that you can then display.
} */
public ByteChunk attachRenderingWMF;
/**
* Is this Attachment an embedded MAPI message?
*/
public boolean isEmbeddedMessage() {
return (attachmentDirectory != null);
}
/**
* Returns the embedded MAPI message, if the attachment
* is an embedded message, or null otherwise
*/
public MAPIMessage getEmbeddedMessage() throws IOException {
if (attachmentDirectory != null) {
return attachmentDirectory.getAsEmbededMessage();
}
return null;
}
/**
* Returns the embedded object, if the attachment is an
* object based embedding (image, document etc), or null
* if it's an embedded message
*/
public byte[] getEmbeddedAttachmentObject() {
if (attachData != null) {
return attachData.getValue();
}
return null;
}
public Chunk[] getAll() {
return allChunks.toArray(new Chunk[allChunks.size()]);
}
public Chunk[] getChunks() {
return getAll();
}
public String getPOIFSName() {
return poifsName;
}
/**
* Called by the parser whenever a chunk is found.
*/
public void record(Chunk chunk) {
// TODO: add further members for other properties like:
// - ATTACH_ADDITIONAL_INFO
// - ATTACH_CONTENT_BASE
// - ATTACH_CONTENT_LOCATION
// - ATTACH_DISPOSITION
// - ATTACH_ENCODING
// - ATTACH_FLAGS
// - ATTACH_LONG_PATHNAME
// - ATTACH_SIZE
final int chunkId = chunk.getChunkId();
if (chunkId == ATTACH_DATA.id) {
if(chunk instanceof ByteChunk) {
attachData = (ByteChunk)chunk;
} else if(chunk instanceof DirectoryChunk) {
attachmentDirectory = (DirectoryChunk)chunk;
} else {
logger.log(POILogger.ERROR, "Unexpected data chunk of type " + chunk);
}
} else if(chunkId == ATTACH_EXTENSION.id) {
attachExtension = (StringChunk)chunk;
} else if(chunkId == ATTACH_FILENAME.id) {
attachFileName = (StringChunk)chunk;
} else if(chunkId == ATTACH_LONG_FILENAME.id) {
attachLongFileName = (StringChunk)chunk;
} else if(chunkId == ATTACH_MIME_TAG.id) {
attachMimeTag = (StringChunk)chunk;
} else if(chunkId == ATTACH_RENDERING.id) {
attachRenderingWMF = (ByteChunk)chunk;
}
// And add to the main list /**
allChunks.add(chunk); * What the POIFS name of this attachment is.
} */
private String poifsName;
/** /** Holds all the chunks that were found. */
* Used to flag that all the chunks of the attachment private List<Chunk> allChunks = new ArrayList<Chunk>();
* have now been located.
*/
public void chunksComplete() {
// Currently, we don't need to do anything special once
// all the chunks have been located
}
public AttachmentChunks(String poifsName) {
this.poifsName = poifsName;
}
/** /**
* Orders by the attachment number. * Is this Attachment an embedded MAPI message?
*/ */
public static class AttachmentChunksSorter implements Comparator<AttachmentChunks>, Serializable { public boolean isEmbeddedMessage() {
public int compare(AttachmentChunks a, AttachmentChunks b) { return (attachmentDirectory != null);
return a.poifsName.compareTo(b.poifsName); }
}
} /**
* Returns the embedded MAPI message, if the attachment is an embedded
* message, or null otherwise
*/
public MAPIMessage getEmbeddedMessage() throws IOException {
if (attachmentDirectory != null) {
return attachmentDirectory.getAsEmbededMessage();
}
return null;
}
/**
* Returns the embedded object, if the attachment is an object based
* embedding (image, document etc), or null if it's an embedded message
*/
public byte[] getEmbeddedAttachmentObject() {
if (attachData != null) {
return attachData.getValue();
}
return null;
}
public Chunk[] getAll() {
return allChunks.toArray(new Chunk[allChunks.size()]);
}
public Chunk[] getChunks() {
return getAll();
}
public String getPOIFSName() {
return poifsName;
}
/**
* Called by the parser whenever a chunk is found.
*/
public void record(Chunk chunk) {
// TODO: add further members for other properties like:
// - ATTACH_ADDITIONAL_INFO
// - ATTACH_CONTENT_BASE
// - ATTACH_CONTENT_LOCATION
// - ATTACH_DISPOSITION
// - ATTACH_ENCODING
// - ATTACH_FLAGS
// - ATTACH_LONG_PATHNAME
// - ATTACH_SIZE
final int chunkId = chunk.getChunkId();
if (chunkId == ATTACH_DATA.id) {
if (chunk instanceof ByteChunk) {
attachData = (ByteChunk) chunk;
} else if (chunk instanceof DirectoryChunk) {
attachmentDirectory = (DirectoryChunk) chunk;
} else {
logger.log(POILogger.ERROR, "Unexpected data chunk of type " + chunk);
}
} else if (chunkId == ATTACH_EXTENSION.id) {
attachExtension = (StringChunk) chunk;
} else if (chunkId == ATTACH_FILENAME.id) {
attachFileName = (StringChunk) chunk;
} else if (chunkId == ATTACH_LONG_FILENAME.id) {
attachLongFileName = (StringChunk) chunk;
} else if (chunkId == ATTACH_MIME_TAG.id) {
attachMimeTag = (StringChunk) chunk;
} else if (chunkId == ATTACH_RENDERING.id) {
attachRenderingWMF = (ByteChunk) chunk;
}
// And add to the main list
allChunks.add(chunk);
}
/**
* Used to flag that all the chunks of the attachment have now been located.
*/
public void chunksComplete() {
// Currently, we don't need to do anything special once
// all the chunks have been located
}
/**
* Orders by the attachment number.
*/
public static class AttachmentChunksSorter
implements Comparator<AttachmentChunks>, Serializable {
public int compare(AttachmentChunks a, AttachmentChunks b) {
return a.poifsName.compareTo(b.poifsName);
}
}
} }

View File

@ -24,89 +24,87 @@ import org.apache.poi.hsmf.datatypes.Types.MAPIType;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
/** /**
* A Chunk that holds binary data, normally unparsed. * A Chunk that holds binary data, normally unparsed. Generally as we know how
* Generally as we know how to make sense of the * to make sense of the contents, we create a new Chunk class and add a special
* contents, we create a new Chunk class and add * case in the parser for them.
* a special case in the parser for them.
*/ */
public class ByteChunk extends Chunk { public class ByteChunk extends Chunk {
private byte[] value; private byte[] value;
/** /**
* Creates a Byte Chunk. * Creates a Byte Chunk.
*/ */
public ByteChunk(String namePrefix, int chunkId, MAPIType type) { public ByteChunk(String namePrefix, int chunkId, MAPIType type) {
super(namePrefix, chunkId, type); super(namePrefix, chunkId, type);
} }
/**
* Create a Byte Chunk, with the specified
* type.
*/
public ByteChunk(int chunkId, MAPIType type) {
super(chunkId, type);
}
public void readValue(InputStream value) throws IOException { /**
this.value = IOUtils.toByteArray(value); * Create a Byte Chunk, with the specified type.
} */
public ByteChunk(int chunkId, MAPIType type) {
super(chunkId, type);
}
public void writeValue(OutputStream out) throws IOException { public void readValue(InputStream value) throws IOException {
out.write(value); this.value = IOUtils.toByteArray(value);
} }
public byte[] getValue() { public void writeValue(OutputStream out) throws IOException {
return value; out.write(value);
} }
public void setValue(byte[] value) {
this.value = value; public byte[] getValue() {
} return value;
}
/**
* Returns the data in a debug-friendly string format public void setValue(byte[] value) {
*/ this.value = value;
public String toString() { }
return toDebugFriendlyString(value);
} /**
* Returns the data in a debug-friendly string format
/** */
* Formats the byte array in a debug-friendly way, public String toString() {
* showing all of a short array, and the start of a return toDebugFriendlyString(value);
* longer one. }
*/
protected static String toDebugFriendlyString(byte[] value) { /**
if (value == null) * Formats the byte array in a debug-friendly way, showing all of a short
return "(Null Byte Array)"; * array, and the start of a longer one.
*/
StringBuffer text = new StringBuffer(); protected static String toDebugFriendlyString(byte[] value) {
text.append("Bytes len=").append(value.length); if (value == null)
text.append(" ["); return "(Null Byte Array)";
int limit = Math.min(value.length, 16); StringBuffer text = new StringBuffer();
if (value.length > 16) { text.append("Bytes len=").append(value.length);
limit = 12; text.append(" [");
}
for (int i=0; i<limit; i++) { int limit = Math.min(value.length, 16);
if (i > 0) if (value.length > 16) {
text.append(','); limit = 12;
text.append(value[i]); }
} for (int i = 0; i < limit; i++) {
if (value.length > 16) { if (i > 0)
text.append(",...."); text.append(',');
} text.append(value[i]);
text.append("]"); }
return text.toString(); if (value.length > 16) {
} text.append(",....");
}
/** text.append("]");
* Returns the data, formatted as a string assuming it return text.toString();
* was a non-unicode string. }
* If your data isn't in fact stored as basically
* ASCII, don't expect this to return much of any /**
* sense.... * Returns the data, formatted as a string assuming it was a non-unicode
* @return the data formatted as a string * string. If your data isn't in fact stored as basically ASCII, don't
*/ * expect this to return much of any sense....
public String getAs7bitString() { *
return StringChunk.parseAs7BitData(value); * @return the data formatted as a string
} */
public String getAs7bitString() {
return StringChunk.parseAs7BitData(value);
}
} }

View File

@ -25,55 +25,59 @@ import java.util.Locale;
import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.hsmf.datatypes.Types.MAPIType;
public abstract class Chunk { public abstract class Chunk {
public static final String DEFAULT_NAME_PREFIX = "__substg1.0_"; public static final String DEFAULT_NAME_PREFIX = "__substg1.0_";
protected int chunkId;
protected MAPIType type;
protected String namePrefix;
protected Chunk(String namePrefix, int chunkId, MAPIType type) {
this.namePrefix = namePrefix;
this.chunkId = chunkId;
this.type = type;
}
protected Chunk(int chunkId, MAPIType type) {
this(DEFAULT_NAME_PREFIX, chunkId, type);
}
/** protected int chunkId;
* Gets the id of this chunk protected MAPIType type;
*/ protected String namePrefix;
public int getChunkId() {
return this.chunkId;
}
/** protected Chunk(String namePrefix, int chunkId, MAPIType type) {
* Gets the numeric type of this chunk. this.namePrefix = namePrefix;
*/ this.chunkId = chunkId;
public MAPIType getType() { this.type = type;
return this.type; }
}
/** protected Chunk(int chunkId, MAPIType type) {
* Creates a string to use to identify this chunk in the POI file system object. this(DEFAULT_NAME_PREFIX, chunkId, type);
*/ }
public String getEntryName() {
String type = this.type.asFileEnding();
String chunkId = Integer.toHexString(this.chunkId); /**
while(chunkId.length() < 4) chunkId = "0" + chunkId; * Gets the id of this chunk
*/
public int getChunkId() {
return this.chunkId;
}
return this.namePrefix + chunkId.toUpperCase(Locale.ROOT) /**
+ type.toUpperCase(Locale.ROOT); * Gets the numeric type of this chunk.
} */
public MAPIType getType() {
return this.type;
}
/** /**
* Writes the value of this chunk back out again. * Creates a string to use to identify this chunk in the POI file system
*/ * object.
public abstract void writeValue(OutputStream out) throws IOException; */
public String getEntryName() {
String type = this.type.asFileEnding();
/** String chunkId = Integer.toHexString(this.chunkId);
* Reads the value of this chunk using an InputStream while (chunkId.length() < 4)
*/ chunkId = "0" + chunkId;
public abstract void readValue(InputStream value) throws IOException;
return this.namePrefix
+ chunkId.toUpperCase(Locale.ROOT)
+ type.toUpperCase(Locale.ROOT);
}
/**
* Writes the value of this chunk back out again.
*/
public abstract void writeValue(OutputStream out) throws IOException;
/**
* Reads the value of this chunk using an InputStream
*/
public abstract void readValue(InputStream value) throws IOException;
} }

View File

@ -17,28 +17,26 @@
package org.apache.poi.hsmf.datatypes; package org.apache.poi.hsmf.datatypes;
/** /**
* A variable length {@link PropertyValue} that is * A variable length {@link PropertyValue} that is backed by a {@link Chunk}
* backed by a {@link Chunk}
* TODO Provide a way to link these up with the chunks * TODO Provide a way to link these up with the chunks
*/ */
public class ChunkBasedPropertyValue extends PropertyValue { public class ChunkBasedPropertyValue extends PropertyValue {
public ChunkBasedPropertyValue(MAPIProperty property, long flags, byte[] offsetData) { public ChunkBasedPropertyValue(MAPIProperty property, long flags, byte[] offsetData) {
super(property, flags, offsetData); super(property, flags, offsetData);
} }
@Override @Override
public Chunk getValue() { public Chunk getValue() {
// TODO Decode the value into an offset // TODO Decode the value into an offset
// TODO Look up the chunk based on that // TODO Look up the chunk based on that
return null; return null;
} }
/** /**
* Stores the offset of the chunk as the property value * Stores the offset of the chunk as the property value
*/ */
public void setValue(Chunk chunk) { public void setValue(Chunk chunk) {
// TODO // TODO
} }
} }

View File

@ -18,15 +18,13 @@
package org.apache.poi.hsmf.datatypes; package org.apache.poi.hsmf.datatypes;
/** /**
* A group of chunks, that are at the same point in the * A group of chunks, that are at the same point in the file structure.
* file structure.
*/ */
public interface ChunkGroup { public interface ChunkGroup {
/** /**
* Returns the chunks that make up the group. * Returns the chunks that make up the group. Should certainly contain all
* Should certainly contain all the interesting Chunks, * the interesting Chunks, but needn't always contain all of the Chunks.
* but needn't always contain all of the Chunks. */
*/
public Chunk[] getChunks(); public Chunk[] getChunks();
/** /**

View File

@ -21,18 +21,15 @@ import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
* A group of chunks which is indexable by {@link MAPIProperty} * A group of chunks which is indexable by {@link MAPIProperty} entries.
* entries.
*/ */
public interface ChunkGroupWithProperties extends ChunkGroup { public interface ChunkGroupWithProperties extends ChunkGroup {
/** /**
* Returns all the Properties contained in the Chunk, along * Returns all the Properties contained in the Chunk, along with their
* with their Values. * Values. Normally, each property will have one value, sometimes none, and
* Normally, each property will have one value, sometimes * rarely multiple (normally for Unknown etc). For fixed sized properties,
* none, and rarely multiple (normally for Unknown etc). * the value can be fetched straight from the {@link PropertyValue}. For
* For fixed sized properties, the value can be fetched * variable sized properties, you'll need to go via the chunk.
* straight from the {@link PropertyValue}. For variable */
* sized properties, you'll need to go via the chunk. public Map<MAPIProperty, List<PropertyValue>> getProperties();
*/
public Map<MAPIProperty,List<PropertyValue>> getProperties();
} }

View File

@ -26,169 +26,158 @@ import java.util.Map;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
/** /**
* Collection of convenience chunks for standard parts of the MSG file. * Collection of convenience chunks for standard parts of the MSG file.
* *
* Not all of these will be present in any given file. * Not all of these will be present in any given file.
* *
* A partial list is available at: * A partial list is available at:
* 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
* *
* TODO Deprecate the public Chunks in favour of Property Lookups * TODO Deprecate the public Chunks in favour of Property Lookups
*/ */
public final class Chunks implements ChunkGroupWithProperties { public final class Chunks implements ChunkGroupWithProperties {
private static POILogger logger = POILogFactory.getLogger(Chunks.class); private static POILogger logger = POILogFactory.getLogger(Chunks.class);
/** /**
* Holds all the chunks that were found, indexed by their MAPIProperty. * Holds all the chunks that were found, indexed by their MAPIProperty.
* Normally a property will have zero chunks (fixed sized) or one chunk * Normally a property will have zero chunks (fixed sized) or one chunk
* (variable size), but in some cases (eg Unknown) you may get more. * (variable size), but in some cases (eg Unknown) you may get more.
*/ */
private Map<MAPIProperty,List<Chunk>> allChunks = new HashMap<MAPIProperty,List<Chunk>>(); private Map<MAPIProperty, List<Chunk>> allChunks = new HashMap<MAPIProperty, List<Chunk>>();
/** Type of message that the MSG represents (ie. IPM.Note) */
public StringChunk messageClass;
/** BODY Chunk, for plain/text messages */
public StringChunk textBodyChunk;
/** BODY Html Chunk, for html messages */
public StringChunk htmlBodyChunkString;
public ByteChunk htmlBodyChunkBinary;
/** BODY Rtf Chunk, for Rtf (Rich) messages */
public ByteChunk rtfBodyChunk;
/** Subject link chunk, in plain/text */
public StringChunk subjectChunk;
/**
* Value that is in the TO field (not actually the addresses as they are
* stored in recip directory nodes
*/
public StringChunk displayToChunk;
/** Value that is in the FROM field */
public StringChunk displayFromChunk;
/** value that shows in the CC field */
public StringChunk displayCCChunk;
/** Value that shows in the BCC field */
public StringChunk displayBCCChunk;
/** Sort of like the subject line, but without the RE: and FWD: parts. */
public StringChunk conversationTopic;
/** Type of server that the message originated from (SMTP, etc). */
public StringChunk sentByServerType;
/** The email headers */
public StringChunk messageHeaders;
/** TODO */
public MessageSubmissionChunk submissionChunk;
/** TODO */
public StringChunk emailFromChunk;
/** The message ID */
public StringChunk messageId;
/** The message properties */
private MessagePropertiesChunk messageProperties;
public Map<MAPIProperty,List<PropertyValue>> getProperties() { /** Type of message that the MSG represents (ie. IPM.Note) */
if (messageProperties != null) { public StringChunk messageClass;
return messageProperties.getProperties(); /** BODY Chunk, for plain/text messages */
} public StringChunk textBodyChunk;
else return Collections.emptyMap(); /** BODY Html Chunk, for html messages */
} public StringChunk htmlBodyChunkString;
public Map<MAPIProperty, PropertyValue> getRawProperties() { public ByteChunk htmlBodyChunkBinary;
if (messageProperties != null) { /** BODY Rtf Chunk, for Rtf (Rich) messages */
return messageProperties.getRawProperties(); public ByteChunk rtfBodyChunk;
} /** Subject link chunk, in plain/text */
else return Collections.emptyMap(); public StringChunk subjectChunk;
} /**
* Value that is in the TO field (not actually the addresses as they are
public Map<MAPIProperty,List<Chunk>> getAll() { * stored in recip directory nodes
return allChunks; */
} public StringChunk displayToChunk;
public Chunk[] getChunks() { /** Value that is in the FROM field */
ArrayList<Chunk> chunks = new ArrayList<Chunk>(allChunks.size()); public StringChunk displayFromChunk;
for (List<Chunk> c : allChunks.values()) { /** value that shows in the CC field */
chunks.addAll(c); public StringChunk displayCCChunk;
} /** Value that shows in the BCC field */
return chunks.toArray(new Chunk[chunks.size()]); public StringChunk displayBCCChunk;
} /** Sort of like the subject line, but without the RE: and FWD: parts. */
public StringChunk conversationTopic;
/** /** Type of server that the message originated from (SMTP, etc). */
* Called by the parser whenever a chunk is found. public StringChunk sentByServerType;
*/ /** The email headers */
public void record(Chunk chunk) { public StringChunk messageHeaders;
// Work out what MAPIProperty this corresponds to /** TODO */
MAPIProperty prop = MAPIProperty.get(chunk.getChunkId()); public MessageSubmissionChunk submissionChunk;
/** TODO */
// Assign it for easy lookup, as best we can public StringChunk emailFromChunk;
if(prop == MAPIProperty.MESSAGE_CLASS) { /** The message ID */
messageClass = (StringChunk)chunk; public StringChunk messageId;
} /** The message properties */
else if(prop == MAPIProperty.INTERNET_MESSAGE_ID) { private MessagePropertiesChunk messageProperties;
messageId = (StringChunk)chunk;
} public Map<MAPIProperty, List<PropertyValue>> getProperties() {
else if(prop == MAPIProperty.MESSAGE_SUBMISSION_ID) { if (messageProperties != null) {
// TODO - parse return messageProperties.getProperties();
submissionChunk = (MessageSubmissionChunk)chunk; } else
} return Collections.emptyMap();
else if(prop == MAPIProperty.RECEIVED_BY_ADDRTYPE) { }
sentByServerType = (StringChunk)chunk;
} public Map<MAPIProperty, PropertyValue> getRawProperties() {
else if(prop == MAPIProperty.TRANSPORT_MESSAGE_HEADERS) { if (messageProperties != null) {
messageHeaders = (StringChunk)chunk; return messageProperties.getRawProperties();
} } else
return Collections.emptyMap();
else if(prop == MAPIProperty.CONVERSATION_TOPIC) { }
conversationTopic = (StringChunk)chunk;
} public Map<MAPIProperty, List<Chunk>> getAll() {
else if(prop == MAPIProperty.SUBJECT) { return allChunks;
subjectChunk = (StringChunk)chunk; }
}
else if(prop == MAPIProperty.ORIGINAL_SUBJECT) { public Chunk[] getChunks() {
// TODO ArrayList<Chunk> chunks = new ArrayList<Chunk>(allChunks.size());
} for (List<Chunk> c : allChunks.values()) {
chunks.addAll(c);
else if(prop == MAPIProperty.DISPLAY_TO) { }
displayToChunk = (StringChunk)chunk; return chunks.toArray(new Chunk[chunks.size()]);
} }
else if(prop == MAPIProperty.DISPLAY_CC) {
displayCCChunk = (StringChunk)chunk; /**
} * Called by the parser whenever a chunk is found.
else if(prop == MAPIProperty.DISPLAY_BCC) { */
displayBCCChunk = (StringChunk)chunk; public void record(Chunk chunk) {
} // Work out what MAPIProperty this corresponds to
MAPIProperty prop = MAPIProperty.get(chunk.getChunkId());
else if(prop == MAPIProperty.SENDER_EMAIL_ADDRESS) {
emailFromChunk = (StringChunk)chunk; // Assign it for easy lookup, as best we can
} if (prop == MAPIProperty.MESSAGE_CLASS) {
else if(prop == MAPIProperty.SENDER_NAME) { messageClass = (StringChunk) chunk;
displayFromChunk = (StringChunk)chunk; } else if (prop == MAPIProperty.INTERNET_MESSAGE_ID) {
} messageId = (StringChunk) chunk;
else if(prop == MAPIProperty.BODY) { } else if (prop == MAPIProperty.MESSAGE_SUBMISSION_ID) {
textBodyChunk = (StringChunk)chunk; // TODO - parse
} submissionChunk = (MessageSubmissionChunk) chunk;
else if(prop == MAPIProperty.BODY_HTML) { } else if (prop == MAPIProperty.RECEIVED_BY_ADDRTYPE) {
if(chunk instanceof StringChunk) { sentByServerType = (StringChunk) chunk;
htmlBodyChunkString = (StringChunk)chunk; } else if (prop == MAPIProperty.TRANSPORT_MESSAGE_HEADERS) {
} messageHeaders = (StringChunk) chunk;
if(chunk instanceof ByteChunk) { }
htmlBodyChunkBinary = (ByteChunk)chunk;
} else if (prop == MAPIProperty.CONVERSATION_TOPIC) {
} conversationTopic = (StringChunk) chunk;
else if(prop == MAPIProperty.RTF_COMPRESSED) { } else if (prop == MAPIProperty.SUBJECT) {
rtfBodyChunk = (ByteChunk)chunk; subjectChunk = (StringChunk) chunk;
} } else if (prop == MAPIProperty.ORIGINAL_SUBJECT) {
else if(chunk instanceof MessagePropertiesChunk) { // TODO
messageProperties = (MessagePropertiesChunk) chunk; }
}
else if (prop == MAPIProperty.DISPLAY_TO) {
// And add to the main list displayToChunk = (StringChunk) chunk;
if (allChunks.get(prop) == null) { } else if (prop == MAPIProperty.DISPLAY_CC) {
allChunks.put(prop, new ArrayList<Chunk>()); displayCCChunk = (StringChunk) chunk;
} } else if (prop == MAPIProperty.DISPLAY_BCC) {
allChunks.get(prop).add(chunk); displayBCCChunk = (StringChunk) chunk;
} }
public void chunksComplete() { else if (prop == MAPIProperty.SENDER_EMAIL_ADDRESS) {
if (messageProperties != null) { emailFromChunk = (StringChunk) chunk;
messageProperties.matchVariableSizedPropertiesToChunks(); } else if (prop == MAPIProperty.SENDER_NAME) {
} else { displayFromChunk = (StringChunk) chunk;
logger.log(POILogger.WARN, "Message didn't contain a root list of properties!"); } else if (prop == MAPIProperty.BODY) {
} textBodyChunk = (StringChunk) chunk;
} } else if (prop == MAPIProperty.BODY_HTML) {
if (chunk instanceof StringChunk) {
htmlBodyChunkString = (StringChunk) chunk;
}
if (chunk instanceof ByteChunk) {
htmlBodyChunkBinary = (ByteChunk) chunk;
}
} else if (prop == MAPIProperty.RTF_COMPRESSED) {
rtfBodyChunk = (ByteChunk) chunk;
} else if (chunk instanceof MessagePropertiesChunk) {
messageProperties = (MessagePropertiesChunk) chunk;
}
// And add to the main list
if (allChunks.get(prop) == null) {
allChunks.put(prop, new ArrayList<Chunk>());
}
allChunks.get(prop).add(chunk);
}
public void chunksComplete() {
if (messageProperties != null) {
messageProperties.matchVariableSizedPropertiesToChunks();
} else {
logger.log(POILogger.WARN,
"Message didn't contain a root list of properties!");
}
}
} }

View File

@ -25,33 +25,28 @@ import org.apache.poi.hsmf.datatypes.Types.MAPIType;
import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DirectoryNode;
/** /**
* A Chunk that is just a placeholder in the * A Chunk that is just a placeholder in the MAPIMessage directory structure,
* MAPIMessage directory structure, which * which contains children. This is most commonly used with nested MAPIMessages
* contains children.
* This is most commonly used with nested
* MAPIMessages
*/ */
public class DirectoryChunk extends Chunk { public class DirectoryChunk extends Chunk {
private DirectoryNode dir; private DirectoryNode dir;
public DirectoryChunk(DirectoryNode dir, String namePrefix, int chunkId, MAPIType type) { public DirectoryChunk(DirectoryNode dir, String namePrefix, int chunkId, MAPIType type) {
super(namePrefix, chunkId, type); super(namePrefix, chunkId, type);
this.dir = dir; this.dir = dir;
} }
/** /**
* Returns the directory entry for this chunk. * Returns the directory entry for this chunk. You can then use standard
* You can then use standard POIFS methods to * POIFS methods to enumerate the entries in it.
* enumerate the entries in it.
*/ */
public DirectoryNode getDirectory() { public DirectoryNode getDirectory() {
return dir; return dir;
} }
/** /**
* Treats the directory as an embeded MAPIMessage * Treats the directory as an embeded MAPIMessage (it normally is one), and
* (it normally is one), and returns a MAPIMessage * returns a MAPIMessage object to process it with.
* object to process it with.
*/ */
public MAPIMessage getAsEmbededMessage() throws IOException { public MAPIMessage getAsEmbededMessage() throws IOException {
return new MAPIMessage(dir); return new MAPIMessage(dir);

View File

@ -24,66 +24,68 @@ import java.io.OutputStream;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
/** /**
* A {@link PropertiesChunk} for a Message or Embedded-Message. * A {@link PropertiesChunk} for a Message or Embedded-Message. This has a 32
* This has a 32 byte header * byte header
*/ */
public class MessagePropertiesChunk extends PropertiesChunk { public class MessagePropertiesChunk extends PropertiesChunk {
private long nextRecipientId; private long nextRecipientId;
private long nextAttachmentId; private long nextAttachmentId;
private long recipientCount; private long recipientCount;
private long attachmentCount; private long attachmentCount;
public MessagePropertiesChunk(ChunkGroup parentGroup) { public MessagePropertiesChunk(ChunkGroup parentGroup) {
super(parentGroup); super(parentGroup);
} }
public long getNextRecipientId() {
return nextRecipientId;
}
public long getNextAttachmentId() {
return nextAttachmentId;
}
public long getRecipientCount() { public long getNextRecipientId() {
return recipientCount; return nextRecipientId;
} }
public long getAttachmentCount() {
return attachmentCount;
}
@Override public long getNextAttachmentId() {
public void readValue(InputStream stream) throws IOException { return nextAttachmentId;
// 8 bytes of reserved zeros }
LittleEndian.readLong(stream);
// Nexts and counts
nextRecipientId = LittleEndian.readUInt(stream);
nextAttachmentId = LittleEndian.readUInt(stream);
recipientCount = LittleEndian.readUInt(stream);
attachmentCount = LittleEndian.readUInt(stream);
// 8 bytes of reserved zeros
LittleEndian.readLong(stream);
// Now properties
readProperties(stream);
}
@Override public long getRecipientCount() {
public void writeValue(OutputStream out) throws IOException { return recipientCount;
// 8 bytes of reserved zeros }
out.write(new byte[8]);
public long getAttachmentCount() {
// Nexts and counts return attachmentCount;
LittleEndian.putUInt(nextRecipientId, out); }
LittleEndian.putUInt(nextAttachmentId, out);
LittleEndian.putUInt(recipientCount, out); @Override
LittleEndian.putUInt(attachmentCount, out); public void readValue(InputStream stream) throws IOException {
// 8 bytes of reserved zeros
// 8 bytes of reserved zeros LittleEndian.readLong(stream);
out.write(new byte[8]);
// Nexts and counts
// Now properties nextRecipientId = LittleEndian.readUInt(stream);
writeProperties(out); nextAttachmentId = LittleEndian.readUInt(stream);
} recipientCount = LittleEndian.readUInt(stream);
attachmentCount = LittleEndian.readUInt(stream);
// 8 bytes of reserved zeros
LittleEndian.readLong(stream);
// Now properties
readProperties(stream);
}
@Override
public void writeValue(OutputStream out) throws IOException {
// 8 bytes of reserved zeros
out.write(new byte[8]);
// Nexts and counts
LittleEndian.putUInt(nextRecipientId, out);
LittleEndian.putUInt(nextAttachmentId, out);
LittleEndian.putUInt(recipientCount, out);
LittleEndian.putUInt(attachmentCount, out);
// 8 bytes of reserved zeros
out.write(new byte[8]);
// Now properties
writeProperties(out);
}
} }

View File

@ -31,100 +31,106 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
/** /**
* A Chunk that holds the details given back by the * A Chunk that holds the details given back by the server at submission time.
* server at submission time. * This includes the date the message was given to the server, and an ID that's
* This includes the date the message was given to the * used if you want to cancel a message or similar
* server, and an ID that's used if you want to cancel
* a message or similar
*/ */
public class MessageSubmissionChunk extends Chunk { public class MessageSubmissionChunk extends Chunk {
private static POILogger logger = POILogFactory.getLogger(MessageSubmissionChunk.class); private static POILogger logger = POILogFactory
private String rawId; .getLogger(MessageSubmissionChunk.class);
private Calendar date; private String rawId;
private Calendar date;
private static final Pattern datePatern = private static final Pattern datePatern = Pattern
Pattern.compile("(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)Z?"); .compile("(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)(\\d\\d)Z?");
/** /**
* Creates a Byte Chunk. * Creates a Byte Chunk.
*/ */
public MessageSubmissionChunk(String namePrefix, int chunkId, MAPIType type) { public MessageSubmissionChunk(String namePrefix, int chunkId,
super(namePrefix, chunkId, type); MAPIType type) {
} super(namePrefix, chunkId, type);
}
/** /**
* Create a Byte Chunk, with the specified * Create a Byte Chunk, with the specified type.
* type. */
*/ public MessageSubmissionChunk(int chunkId, MAPIType type) {
public MessageSubmissionChunk(int chunkId, MAPIType type) { super(chunkId, type);
super(chunkId, type); }
}
public void readValue(InputStream value) throws IOException { public void readValue(InputStream value) throws IOException {
// Stored in the file as us-ascii // Stored in the file as us-ascii
byte[] data = IOUtils.toByteArray(value); byte[] data = IOUtils.toByteArray(value);
rawId = new String(data, Charset.forName("ASCII")); rawId = new String(data, Charset.forName("ASCII"));
// Now process the date
String[] parts = rawId.split(";");
for(String part : parts) {
if(part.startsWith("l=")) {
// Format of this bit appears to be l=<id>-<time>-<number>
// ID may contain hyphens.
String dateS = null; // Now process the date
final int numberPartBegin = part.lastIndexOf('-'); String[] parts = rawId.split(";");
if (numberPartBegin != -1) { for (String part : parts) {
final int datePartBegin = part.lastIndexOf('-', numberPartBegin-1); if (part.startsWith("l=")) {
if (datePartBegin != -1 && // Format of this bit appears to be l=<id>-<time>-<number>
// cannot extract date if only one hyphen is in the string... // ID may contain hyphens.
numberPartBegin > datePartBegin) {
dateS = part.substring(datePartBegin + 1, numberPartBegin); String dateS = null;
final int numberPartBegin = part.lastIndexOf('-');
if (numberPartBegin != -1) {
final int datePartBegin = part.lastIndexOf('-',
numberPartBegin - 1);
if (datePartBegin != -1 &&
// cannot extract date if only one hyphen is in the
// string...
numberPartBegin > datePartBegin) {
dateS = part.substring(datePartBegin + 1,
numberPartBegin);
}
}
if (dateS != null) {
// Should be yymmddhhmmssZ
Matcher m = datePatern.matcher(dateS);
if (m.matches()) {
date = LocaleUtil.getLocaleCalendar();
// work around issues with dates like 1989, which appear as "89" here
int year = Integer.parseInt(m.group(1));
date.set(Calendar.YEAR, year + (year > 80 ? 1900 : 2000));
// Java is 0 based
date.set(Calendar.MONTH, Integer.parseInt(m.group(2)) - 1);
date.set(Calendar.DATE, Integer.parseInt(m.group(3)));
date.set(Calendar.HOUR_OF_DAY,
Integer.parseInt(m.group(4)));
date.set(Calendar.MINUTE, Integer.parseInt(m.group(5)));
date.set(Calendar.SECOND, Integer.parseInt(m.group(6)));
date.clear(Calendar.MILLISECOND);
} else {
logger.log(POILogger.WARN,
"Warning - unable to make sense of date "
+ dateS);
}
} }
} }
if (dateS != null) { }
// Should be yymmddhhmmssZ }
Matcher m = datePatern.matcher(dateS);
if(m.matches()) {
date = LocaleUtil.getLocaleCalendar();
// work around issues with dates like 1989, which appear as "89" here public void writeValue(OutputStream out) throws IOException {
int year = Integer.parseInt(m.group(1)); byte[] data = rawId.getBytes(Charset.forName("ASCII"));
date.set(Calendar.YEAR, year + (year > 80 ? 1900 : 2000)); out.write(data);
}
date.set(Calendar.MONTH, Integer.parseInt(m.group(2)) - 1); // Java is 0 based /**
date.set(Calendar.DATE, Integer.parseInt(m.group(3))); * @return the date that the server accepted the message, as found from the
date.set(Calendar.HOUR_OF_DAY, Integer.parseInt(m.group(4))); * message ID it generated.
date.set(Calendar.MINUTE, Integer.parseInt(m.group(5))); *
date.set(Calendar.SECOND, Integer.parseInt(m.group(6))); */
date.clear(Calendar.MILLISECOND); public Calendar getAcceptedAtTime() {
} else { return date;
logger.log(POILogger.WARN, "Warning - unable to make sense of date " + dateS); }
}
}
}
}
}
public void writeValue(OutputStream out) throws IOException { /**
byte[] data = rawId.getBytes(Charset.forName("ASCII")); * @return the full ID that the server generated when it accepted the
out.write(data); * message.
} */
public String getSubmissionId() {
/** return rawId;
* @return the date that the server accepted the }
* message, as found from the message ID it generated.
*
*/
public Calendar getAcceptedAtTime() {
return date;
}
/**
* @return the full ID that the server generated when
* it accepted the message.
*/
public String getSubmissionId() {
return rawId;
}
} }

View File

@ -20,37 +20,35 @@ package org.apache.poi.hsmf.datatypes;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
/** /**
* Collection of convenience chunks for the * Collection of convenience chunks for the NameID part of an outlook file
* NameID part of an outlook file
*/ */
public final class NameIdChunks implements ChunkGroup { public final class NameIdChunks implements ChunkGroup {
public static final String NAME = "__nameid_version1.0"; public static final String NAME = "__nameid_version1.0";
/** Holds all the chunks that were found. */ /** Holds all the chunks that were found. */
private List<Chunk> allChunks = new ArrayList<Chunk>(); private List<Chunk> allChunks = new ArrayList<Chunk>();
public Chunk[] getAll() { public Chunk[] getAll() {
return allChunks.toArray(new Chunk[allChunks.size()]); return allChunks.toArray(new Chunk[allChunks.size()]);
} }
public Chunk[] getChunks() {
return getAll(); public Chunk[] getChunks() {
} return getAll();
}
/**
* Called by the parser whenever a chunk is found. /**
*/ * Called by the parser whenever a chunk is found.
public void record(Chunk chunk) { */
allChunks.add(chunk); public void record(Chunk chunk) {
} allChunks.add(chunk);
}
/**
* Used to flag that all the chunks of the NameID /**
* have now been located. * Used to flag that all the chunks of the NameID have now been located.
*/ */
public void chunksComplete() { public void chunksComplete() {
// Currently, we don't need to do anything special once // Currently, we don't need to do anything special once
// all the chunks have been located // all the chunks have been located
} }
} }

View File

@ -42,240 +42,238 @@ import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
/** /**
* <p>A Chunk which holds (single) fixed-length properties, and pointer * <p>
* to the variable length ones / multi-valued ones (which get their * A Chunk which holds (single) fixed-length properties, and pointer to the
* own chunk). * variable length ones / multi-valued ones (which get their own chunk).
* <p>There are two kinds of PropertiesChunks, which differ only in * <p>
* their headers. * There are two kinds of PropertiesChunks, which differ only in their headers.
*/ */
public abstract class PropertiesChunk extends Chunk { public abstract class PropertiesChunk extends Chunk {
public static final String NAME = "__properties_version1.0"; public static final String NAME = "__properties_version1.0";
/** For logging problems we spot with the file */
private POILogger logger = POILogFactory.getLogger(PropertiesChunk.class);
/** /** For logging problems we spot with the file */
* Holds properties, indexed by type. If a property is multi-valued, private POILogger logger = POILogFactory.getLogger(PropertiesChunk.class);
* or variable length, it will be held via a {@link ChunkBasedPropertyValue}.
*/
private Map<MAPIProperty, PropertyValue> properties =
new HashMap<MAPIProperty, PropertyValue>();
/** /**
* The ChunkGroup that these properties apply to. Used when * Holds properties, indexed by type. If a property is multi-valued, or
* matching chunks to variable sized and multi-valued properties * variable length, it will be held via a {@link ChunkBasedPropertyValue}.
*/ */
private ChunkGroup parentGroup; private Map<MAPIProperty, PropertyValue> properties = new HashMap<MAPIProperty, PropertyValue>();
/**
* Creates a Properties Chunk.
*/
protected PropertiesChunk(ChunkGroup parentGroup) {
super(NAME, -1, Types.UNKNOWN);
this.parentGroup = parentGroup;
}
@Override /**
public String getEntryName() { * The ChunkGroup that these properties apply to. Used when matching chunks
return NAME; * to variable sized and multi-valued properties
} */
private ChunkGroup parentGroup;
/**
* Returns all the properties in the chunk, without
* looking up any chunk-based values
*/
public Map<MAPIProperty, PropertyValue> getRawProperties() {
return properties;
}
/** /**
* <p>Returns all the properties in the chunk, along with their * Creates a Properties Chunk.
* values. */
* <p>Any chunk-based values will be looked up and returned as such protected PropertiesChunk(ChunkGroup parentGroup) {
*/ super(NAME, -1, Types.UNKNOWN);
public Map<MAPIProperty, List<PropertyValue>> getProperties() { this.parentGroup = parentGroup;
Map<MAPIProperty, List<PropertyValue>> props = }
new HashMap<MAPIProperty, List<PropertyValue>>(properties.size());
for (MAPIProperty prop : properties.keySet()) {
props.put(prop, getValues(prop));
}
return props;
}
/** @Override
* Returns all values for the given property, looking up chunk based public String getEntryName() {
* ones as required, of null if none exist return NAME;
*/ }
public List<PropertyValue> getValues(MAPIProperty property) {
PropertyValue val = properties.get(property);
if (val == null) {
return null;
}
if (val instanceof ChunkBasedPropertyValue) {
// ChunkBasedPropertyValue cval = (ChunkBasedPropertyValue)val;
// TODO Lookup
return Collections.emptyList();
} else {
return Collections.singletonList(val);
}
}
/** /**
* Returns the value / pointer to the value chunk of * Returns all the properties in the chunk, without looking up any
* the property, or null if none exists * chunk-based values
*/ */
public PropertyValue getRawValue(MAPIProperty property) { public Map<MAPIProperty, PropertyValue> getRawProperties() {
return properties.get(property); return properties;
} }
/**
* Called once the parent ChunkGroup has been populated, to match
* up the Chunks in it with our Variable Sized Properties.
*/
protected void matchVariableSizedPropertiesToChunks() {
// Index the Parent Group chunks for easy lookup
// TODO Is this the right way?
Map<Integer,Chunk> chunks = new HashMap<Integer, Chunk>();
for (Chunk chunk : parentGroup.getChunks()) {
chunks.put(chunk.chunkId, chunk);
}
// Loop over our values, looking for chunk based ones
for (PropertyValue val : properties.values()) {
if (val instanceof ChunkBasedPropertyValue) {
ChunkBasedPropertyValue cVal = (ChunkBasedPropertyValue)val;
Chunk chunk = chunks.get(cVal.getProperty().id);
//System.err.println(cVal.getProperty() + " = " + cVal + " -> " + HexDump.toHex(cVal.data));
// TODO Make sense of the raw offset value
if (chunk != null) {
cVal.setValue(chunk);
} else {
logger.log(POILogger.WARN, "No chunk found matching Property " + cVal);
}
}
}
}
protected void readProperties(InputStream value) throws IOException { /**
boolean going = true; * <p>
while (going) { * Returns all the properties in the chunk, along with their values.
try { * <p>
// Read in the header * Any chunk-based values will be looked up and returned as such
int typeID = LittleEndian.readUShort(value); */
int id = LittleEndian.readUShort(value); public Map<MAPIProperty, List<PropertyValue>> getProperties() {
long flags = LittleEndian.readUInt(value); Map<MAPIProperty, List<PropertyValue>> props =
new HashMap<MAPIProperty, List<PropertyValue>>(properties.size());
// Turn the Type and ID into helper objects for (MAPIProperty prop : properties.keySet()) {
MAPIType type = Types.getById(typeID); props.put(prop, getValues(prop));
MAPIProperty prop = MAPIProperty.get(id); }
return props;
// Wrap properties we don't know about as custom ones }
if (prop == MAPIProperty.UNKNOWN) {
prop = MAPIProperty.createCustom(id, type, "Unknown " + id); /**
} * Returns all values for the given property, looking up chunk based ones as
if (type == null) { * required, of null if none exist
logger.log(POILogger.WARN, "Invalid type found, expected ", prop.usualType, */
" but got ", typeID, " for property ", prop); public List<PropertyValue> getValues(MAPIProperty property) {
going = false; PropertyValue val = properties.get(property);
break; if (val == null) {
} return null;
}
// Sanity check the property's type against the value's type if (val instanceof ChunkBasedPropertyValue) {
if (prop.usualType != type) { // ChunkBasedPropertyValue cval = (ChunkBasedPropertyValue)val;
// Is it an allowed substitution? // TODO Lookup
if (type == Types.ASCII_STRING && prop.usualType == Types.UNICODE_STRING || return Collections.emptyList();
type == Types.UNICODE_STRING && prop.usualType == Types.ASCII_STRING) { } else {
// It's fine to go with the specified instead of the normal return Collections.singletonList(val);
} else if (prop.usualType == Types.UNKNOWN) { }
// We don't know what this property normally is, but it has come }
// through with a valid type, so use that
logger.log(POILogger.INFO, "Property definition for ", prop, /**
" is missing a type definition, found a value with type ", type); * Returns the value / pointer to the value chunk of the property, or null
* if none exists
*/
public PropertyValue getRawValue(MAPIProperty property) {
return properties.get(property);
}
/**
* Called once the parent ChunkGroup has been populated, to match up the
* Chunks in it with our Variable Sized Properties.
*/
protected void matchVariableSizedPropertiesToChunks() {
// Index the Parent Group chunks for easy lookup
// TODO Is this the right way?
Map<Integer, Chunk> chunks = new HashMap<Integer, Chunk>();
for (Chunk chunk : parentGroup.getChunks()) {
chunks.put(chunk.chunkId, chunk);
}
// Loop over our values, looking for chunk based ones
for (PropertyValue val : properties.values()) {
if (val instanceof ChunkBasedPropertyValue) {
ChunkBasedPropertyValue cVal = (ChunkBasedPropertyValue) val;
Chunk chunk = chunks.get(cVal.getProperty().id);
// System.err.println(cVal.getProperty() + " = " + cVal + " -> "
// + HexDump.toHex(cVal.data));
// TODO Make sense of the raw offset value
if (chunk != null) {
cVal.setValue(chunk);
} else { } else {
// Oh dear, something has gone wrong... logger.log(POILogger.WARN, "No chunk found matching Property " + cVal);
logger.log(POILogger.WARN, "Type mismatch, expected ", prop.usualType,
" but got ", type, " for property ", prop);
going = false;
break;
} }
} }
}
// TODO Detect if it is multi-valued, since if it is }
// then even fixed-length strings store their multiple
// values in another chunk (much as variable length ones)
// Work out how long the "data" is
// This might be the actual data, or just a pointer
// to another chunk which holds the data itself
boolean isPointer = false;
int length = type.getLength();
if (! type.isFixedLength()) {
isPointer = true;
length = 8;
}
// Grab the data block
byte[] data = new byte[length];
IOUtils.readFully(value, data);
// Skip over any padding
if (length < 8) {
byte[] padding = new byte[8-length];
IOUtils.readFully(value, padding);
}
// Wrap and store
PropertyValue propVal = null;
if (isPointer) {
// We'll match up the chunk later
propVal = new ChunkBasedPropertyValue(prop, flags, data);
}
else if (type == Types.NULL) {
propVal = new NullPropertyValue(prop, flags, data);
}
else if (type == Types.BOOLEAN) {
propVal = new BooleanPropertyValue(prop, flags, data);
}
else if (type == Types.SHORT) {
propVal = new ShortPropertyValue(prop, flags, data);
}
else if (type == Types.LONG) {
propVal = new LongPropertyValue(prop, flags, data);
}
else if (type == Types.LONG_LONG) {
propVal = new LongLongPropertyValue(prop, flags, data);
}
else if (type == Types.FLOAT) {
propVal = new FloatPropertyValue(prop, flags, data);
}
else if (type == Types.DOUBLE) {
propVal = new DoublePropertyValue(prop, flags, data);
}
else if (type == Types.CURRENCY) {
propVal = new CurrencyPropertyValue(prop, flags, data);
}
else if (type == Types.TIME) {
propVal = new TimePropertyValue(prop, flags, data);
}
// TODO Add in the rest of the types
else {
propVal = new PropertyValue(prop, flags, data);
}
if (properties.get(prop) != null) { protected void readProperties(InputStream value) throws IOException {
logger.log(POILogger.WARN, "Duplicate values found for " + prop); boolean going = true;
} while (going) {
properties.put(prop, propVal); try {
} catch (BufferUnderrunException e) { // Read in the header
// Invalid property, ended short int typeID = LittleEndian.readUShort(value);
going = false; int id = LittleEndian.readUShort(value);
} long flags = LittleEndian.readUInt(value);
}
}
protected void writeProperties(OutputStream out) throws IOException { // Turn the Type and ID into helper objects
// TODO MAPIType type = Types.getById(typeID);
} MAPIProperty prop = MAPIProperty.get(id);
// Wrap properties we don't know about as custom ones
if (prop == MAPIProperty.UNKNOWN) {
prop = MAPIProperty.createCustom(id, type, "Unknown " + id);
}
if (type == null) {
logger.log(POILogger.WARN, "Invalid type found, expected ",
prop.usualType, " but got ", typeID,
" for property ", prop);
going = false;
break;
}
// Sanity check the property's type against the value's type
if (prop.usualType != type) {
// Is it an allowed substitution?
if (type == Types.ASCII_STRING
&& prop.usualType == Types.UNICODE_STRING
|| type == Types.UNICODE_STRING
&& prop.usualType == Types.ASCII_STRING) {
// It's fine to go with the specified instead of the
// normal
} else if (prop.usualType == Types.UNKNOWN) {
// We don't know what this property normally is, but it
// has come
// through with a valid type, so use that
logger.log(POILogger.INFO, "Property definition for ", prop,
" is missing a type definition, found a value with type ", type);
} else {
// Oh dear, something has gone wrong...
logger.log(POILogger.WARN, "Type mismatch, expected ",
prop.usualType, " but got ", type, " for property ", prop);
going = false;
break;
}
}
// TODO Detect if it is multi-valued, since if it is
// then even fixed-length strings store their multiple
// values in another chunk (much as variable length ones)
// Work out how long the "data" is
// This might be the actual data, or just a pointer
// to another chunk which holds the data itself
boolean isPointer = false;
int length = type.getLength();
if (!type.isFixedLength()) {
isPointer = true;
length = 8;
}
// Grab the data block
byte[] data = new byte[length];
IOUtils.readFully(value, data);
// Skip over any padding
if (length < 8) {
byte[] padding = new byte[8 - length];
IOUtils.readFully(value, padding);
}
// Wrap and store
PropertyValue propVal = null;
if (isPointer) {
// We'll match up the chunk later
propVal = new ChunkBasedPropertyValue(prop, flags, data);
} else if (type == Types.NULL) {
propVal = new NullPropertyValue(prop, flags, data);
} else if (type == Types.BOOLEAN) {
propVal = new BooleanPropertyValue(prop, flags, data);
} else if (type == Types.SHORT) {
propVal = new ShortPropertyValue(prop, flags, data);
} else if (type == Types.LONG) {
propVal = new LongPropertyValue(prop, flags, data);
} else if (type == Types.LONG_LONG) {
propVal = new LongLongPropertyValue(prop, flags, data);
} else if (type == Types.FLOAT) {
propVal = new FloatPropertyValue(prop, flags, data);
} else if (type == Types.DOUBLE) {
propVal = new DoublePropertyValue(prop, flags, data);
} else if (type == Types.CURRENCY) {
propVal = new CurrencyPropertyValue(prop, flags, data);
} else if (type == Types.TIME) {
propVal = new TimePropertyValue(prop, flags, data);
}
// TODO Add in the rest of the types
else {
propVal = new PropertyValue(prop, flags, data);
}
if (properties.get(prop) != null) {
logger.log(POILogger.WARN,
"Duplicate values found for " + prop);
}
properties.put(prop, propVal);
} catch (BufferUnderrunException e) {
// Invalid property, ended short
going = false;
}
}
}
protected void writeProperties(OutputStream out) throws IOException {
// TODO
}
} }

View File

@ -24,212 +24,228 @@ import org.apache.poi.util.LittleEndian;
import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.LocaleUtil;
/** /**
* An instance of a {@link MAPIProperty} inside a {@link PropertiesChunk}. * An instance of a {@link MAPIProperty} inside a {@link PropertiesChunk}. Where
* Where the {@link Types} type is a fixed length one, this will contain the * the {@link Types} type is a fixed length one, this will contain the actual
* actual value. * value. Where the {@link Types} type is a variable length one, this will
* Where the {@link Types} type is a variable length one, this will contain * contain the length of the property, and the value will be in the associated
* the length of the property, and the value will be in the associated {@link Chunk}. * {@link Chunk}.
*/ */
public class PropertyValue { public class PropertyValue {
private MAPIProperty property; private MAPIProperty property;
private long flags; private long flags;
protected byte[] data; protected byte[] data;
public PropertyValue(MAPIProperty property, long flags, byte[] data) {
this.property = property;
this.flags = flags;
this.data = data;
}
public MAPIProperty getProperty() {
return property;
}
/** public PropertyValue(MAPIProperty property, long flags, byte[] data) {
* Get the raw value flags. this.property = property;
* TODO Also provide getters for the flag meanings this.flags = flags;
*/ this.data = data;
public long getFlags() {
return flags;
}
public Object getValue() {
return data;
}
public void setRawValue(byte[] value) {
this.data = value;
}
public String toString() {
Object v = getValue();
if (v == null)
return "(No value available)";
if (v instanceof byte[]) {
return ByteChunk.toDebugFriendlyString((byte[])v);
} else {
// Just use the normal toString on the value
return v.toString();
}
}
public static class NullPropertyValue extends PropertyValue {
public NullPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Void getValue() {
return null;
}
}
public static class BooleanPropertyValue extends PropertyValue {
public BooleanPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Boolean getValue() {
short val = LittleEndian.getShort(data);
return val > 0;
}
public void setValue(boolean value) {
if (data.length != 2) {
data = new byte[2];
}
if (value) {
LittleEndian.putShort(data, 0, (short)1);
}
}
}
public static class ShortPropertyValue extends PropertyValue {
public ShortPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Short getValue() {
return LittleEndian.getShort(data);
}
public void setValue(short value) {
if (data.length != 2) {
data = new byte[2];
}
LittleEndian.putShort(data, 0, value);
}
}
public static class LongPropertyValue extends PropertyValue {
public LongPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Integer getValue() {
return LittleEndian.getInt(data);
}
public void setValue(int value) {
if (data.length != 4) {
data = new byte[4];
}
LittleEndian.putInt(data, 0, value);
}
} }
public static class LongLongPropertyValue extends PropertyValue {
public LongLongPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Long getValue() {
return LittleEndian.getLong(data);
}
public void setValue(long value) {
if (data.length != 8) {
data = new byte[8];
}
LittleEndian.putLong(data, 0, value);
}
}
public static class FloatPropertyValue extends PropertyValue {
public FloatPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Float getValue() {
return LittleEndian.getFloat(data);
}
public void setValue(float value) {
if (data.length != 4) {
data = new byte[4];
}
LittleEndian.putFloat(data, 0, value);
}
}
public static class DoublePropertyValue extends PropertyValue {
public DoublePropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Double getValue() {
return LittleEndian.getDouble(data);
}
public void setValue(double value) {
if (data.length != 8) {
data = new byte[8];
}
LittleEndian.putDouble(data, 0, value);
}
}
/**
* signed 64-bit integer that represents a base ten decimal,
* with four digits to the right of the decimal point
*/
public static class CurrencyPropertyValue extends PropertyValue {
private static final BigInteger SHIFT = BigInteger.valueOf(10000);
public CurrencyPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public BigInteger getValue() {
long unshifted = LittleEndian.getLong(data);
return BigInteger.valueOf(unshifted).divide(SHIFT);
}
public void setValue(BigInteger value) {
if (data.length != 8) {
data = new byte[8];
}
long shifted = value.multiply(SHIFT).longValue();
LittleEndian.putLong(data, 0, shifted);
}
}
/**
* 64-bit integer specifying the number of 100ns periods since Jan 1, 1601
*/
public static class TimePropertyValue extends PropertyValue {
private static final long OFFSET = 1000L * 60L * 60L * 24L * (365L * 369L + 89L);
public TimePropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Calendar getValue() {
long time = LittleEndian.getLong(data);
time = (time / 10 / 1000) - OFFSET;
Calendar timeC = LocaleUtil.getLocaleCalendar(); public MAPIProperty getProperty() {
timeC.setTimeInMillis(time); return property;
}
return timeC; /**
} * Get the raw value flags. TODO Also provide getters for the flag meanings
public void setValue(Calendar value) { */
if (data.length != 8) { public long getFlags() {
data = new byte[8]; return flags;
} }
long time = value.getTimeInMillis();
time = (time + OFFSET) *10*1000; public Object getValue() {
LittleEndian.putLong(data, 0, time); return data;
} }
}
public void setRawValue(byte[] value) {
this.data = value;
}
public String toString() {
Object v = getValue();
if (v == null)
return "(No value available)";
if (v instanceof byte[]) {
return ByteChunk.toDebugFriendlyString((byte[]) v);
} else {
// Just use the normal toString on the value
return v.toString();
}
}
public static class NullPropertyValue extends PropertyValue {
public NullPropertyValue(MAPIProperty property, long flags,
byte[] data) {
super(property, flags, data);
}
public Void getValue() {
return null;
}
}
public static class BooleanPropertyValue extends PropertyValue {
public BooleanPropertyValue(MAPIProperty property, long flags,
byte[] data) {
super(property, flags, data);
}
public Boolean getValue() {
short val = LittleEndian.getShort(data);
return val > 0;
}
public void setValue(boolean value) {
if (data.length != 2) {
data = new byte[2];
}
if (value) {
LittleEndian.putShort(data, 0, (short) 1);
}
}
}
public static class ShortPropertyValue extends PropertyValue {
public ShortPropertyValue(MAPIProperty property, long flags,
byte[] data) {
super(property, flags, data);
}
public Short getValue() {
return LittleEndian.getShort(data);
}
public void setValue(short value) {
if (data.length != 2) {
data = new byte[2];
}
LittleEndian.putShort(data, 0, value);
}
}
public static class LongPropertyValue extends PropertyValue {
public LongPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Integer getValue() {
return LittleEndian.getInt(data);
}
public void setValue(int value) {
if (data.length != 4) {
data = new byte[4];
}
LittleEndian.putInt(data, 0, value);
}
}
public static class LongLongPropertyValue extends PropertyValue {
public LongLongPropertyValue(MAPIProperty property, long flags,
byte[] data) {
super(property, flags, data);
}
public Long getValue() {
return LittleEndian.getLong(data);
}
public void setValue(long value) {
if (data.length != 8) {
data = new byte[8];
}
LittleEndian.putLong(data, 0, value);
}
}
public static class FloatPropertyValue extends PropertyValue {
public FloatPropertyValue(MAPIProperty property, long flags,
byte[] data) {
super(property, flags, data);
}
public Float getValue() {
return LittleEndian.getFloat(data);
}
public void setValue(float value) {
if (data.length != 4) {
data = new byte[4];
}
LittleEndian.putFloat(data, 0, value);
}
}
public static class DoublePropertyValue extends PropertyValue {
public DoublePropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Double getValue() {
return LittleEndian.getDouble(data);
}
public void setValue(double value) {
if (data.length != 8) {
data = new byte[8];
}
LittleEndian.putDouble(data, 0, value);
}
}
/**
* signed 64-bit integer that represents a base ten decimal, with four
* digits to the right of the decimal point
*/
public static class CurrencyPropertyValue extends PropertyValue {
private static final BigInteger SHIFT = BigInteger.valueOf(10000);
public CurrencyPropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public BigInteger getValue() {
long unshifted = LittleEndian.getLong(data);
return BigInteger.valueOf(unshifted).divide(SHIFT);
}
public void setValue(BigInteger value) {
if (data.length != 8) {
data = new byte[8];
}
long shifted = value.multiply(SHIFT).longValue();
LittleEndian.putLong(data, 0, shifted);
}
}
/**
* 64-bit integer specifying the number of 100ns periods since Jan 1, 1601
*/
public static class TimePropertyValue extends PropertyValue {
private static final long OFFSET = 1000L * 60L * 60L * 24L
* (365L * 369L + 89L);
public TimePropertyValue(MAPIProperty property, long flags, byte[] data) {
super(property, flags, data);
}
public Calendar getValue() {
long time = LittleEndian.getLong(data);
time = (time / 10 / 1000) - OFFSET;
Calendar timeC = LocaleUtil.getLocaleCalendar();
timeC.setTimeInMillis(time);
return timeC;
}
public void setValue(Calendar value) {
if (data.length != 8) {
data = new byte[8];
}
long time = value.getTimeInMillis();
time = (time + OFFSET) * 10 * 1000;
LittleEndian.putLong(data, 0, time);
}
}
} }

View File

@ -27,209 +27,199 @@ import java.util.Map;
import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogFactory;
import org.apache.poi.util.POILogger; import org.apache.poi.util.POILogger;
/** /**
* Collection of convenience chunks for the * Collection of convenience chunks for the Recip(ient) part of an outlook file.
* Recip(ient) part of an outlook file.
* *
* If a message has multiple recipients, there will be * If a message has multiple recipients, there will be several of these.
* several of these.
*/ */
public final class RecipientChunks implements ChunkGroupWithProperties { public final class RecipientChunks implements ChunkGroupWithProperties {
private static POILogger logger = POILogFactory.getLogger(RecipientChunks.class); private static POILogger logger = POILogFactory.getLogger(RecipientChunks.class);
public static final String PREFIX = "__recip_version1.0_#"; public static final String PREFIX = "__recip_version1.0_#";
public static final MAPIProperty RECIPIENT_NAME = MAPIProperty.DISPLAY_NAME; public static final MAPIProperty RECIPIENT_NAME = MAPIProperty.DISPLAY_NAME;
public static final MAPIProperty DELIVERY_TYPE = MAPIProperty.ADDRTYPE; public static final MAPIProperty DELIVERY_TYPE = MAPIProperty.ADDRTYPE;
public static final MAPIProperty RECIPIENT_EMAIL_ADDRESS = MAPIProperty.EMAIL_ADDRESS; public static final MAPIProperty RECIPIENT_EMAIL_ADDRESS = MAPIProperty.EMAIL_ADDRESS;
public static final MAPIProperty RECIPIENT_SEARCH = MAPIProperty.SEARCH_KEY; public static final MAPIProperty RECIPIENT_SEARCH = MAPIProperty.SEARCH_KEY;
public static final MAPIProperty RECIPIENT_SMTP_ADDRESS = MAPIProperty.SMTP_ADDRESS; public static final MAPIProperty RECIPIENT_SMTP_ADDRESS = MAPIProperty.SMTP_ADDRESS;
public static final MAPIProperty RECIPIENT_DISPLAY_NAME = MAPIProperty.RECIPIENT_DISPLAY_NAME; public static final MAPIProperty RECIPIENT_DISPLAY_NAME = MAPIProperty.RECIPIENT_DISPLAY_NAME;
/** Our 0 based position in the list of recipients */ /** Our 0 based position in the list of recipients */
public int recipientNumber; public int recipientNumber;
/** TODO */ /** TODO */
public ByteChunk recipientSearchChunk; public ByteChunk recipientSearchChunk;
/** /**
* The "name", which could be their name if an * The "name", which could be their name if an internal person, or their
* internal person, or their email address * email address if an external person
* if an external person */
*/ public StringChunk recipientNameChunk;
public StringChunk recipientNameChunk; /**
/** * The email address of the recipient, which could be in SMTP or SEARCH
* The email address of the recipient, which * format, but isn't always present...
* could be in SMTP or SEARCH format, but */
* isn't always present... public StringChunk recipientEmailChunk;
*/ /**
public StringChunk recipientEmailChunk; * The smtp destination email address of the recipient, but isn't always
/** * present...
* The smtp destination email address of */
* the recipient, but isn't always present... public StringChunk recipientSMTPChunk;
*/ /**
public StringChunk recipientSMTPChunk; * Normally EX or SMTP. Will generally affect where the email address ends
/** * up.
* Normally EX or SMTP. Will generally affect */
* where the email address ends up. public StringChunk deliveryTypeChunk;
*/ /**
public StringChunk deliveryTypeChunk; * The display name of the recipient. Normally seems to hold the same value
/** * as in recipientNameChunk
* The display name of the recipient. */
* Normally seems to hold the same value public StringChunk recipientDisplayNameChunk;
* as in recipientNameChunk /**
*/ * Holds the fixed sized properties, and the pointers to the data of
public StringChunk recipientDisplayNameChunk; * variable sized ones
/** */
* Holds the fixed sized properties, and the private PropertiesChunk recipientProperties;
* pointers to the data of variable sized ones
*/ public RecipientChunks(String name) {
private PropertiesChunk recipientProperties; recipientNumber = -1;
int splitAt = name.lastIndexOf('#');
public RecipientChunks(String name) { if (splitAt > -1) {
recipientNumber = -1; String number = name.substring(splitAt + 1);
int splitAt = name.lastIndexOf('#'); try {
if(splitAt > -1) { recipientNumber = Integer.parseInt(number, 16);
String number = name.substring(splitAt+1); } catch (NumberFormatException e) {
try { logger.log(POILogger.ERROR,
recipientNumber = Integer.parseInt(number, 16); "Invalid recipient number in name " + name);
} catch(NumberFormatException e) {
logger.log(POILogger.ERROR, "Invalid recipient number in name " + name);
}
}
}
/**
* Tries to find their name,
* in whichever chunk holds it.
*/
public String getRecipientName() {
if(recipientNameChunk != null) {
return recipientNameChunk.getValue();
}
if(recipientDisplayNameChunk != null) {
return recipientDisplayNameChunk.getValue();
}
// Can't find it
return null;
}
/**
* Tries to find their email address, in
* whichever chunk holds it given the
* delivery type.
*/
public String getRecipientEmailAddress() {
// If we have this, it really has the email
if(recipientSMTPChunk != null) {
return recipientSMTPChunk.getValue();
}
// This might be a real email, or might be
// in CN=... format
if(recipientEmailChunk != null) {
String email = recipientEmailChunk.getValue();
int cne = email.indexOf("/CN=");
if(cne == -1) {
// Normal smtp address
return email;
} else {
// /O=..../CN=em@ail
return email.substring(cne+4);
}
}
// Might be in the name field, check there
if(recipientNameChunk != null) {
String name = recipientNameChunk.getValue();
if(name.indexOf('@') > -1) {
// Strip leading and trailing quotes if needed
if(name.startsWith("'") && name.endsWith("'")) {
return name.substring(1, name.length()-1);
} }
return name; }
} }
}
// Check the search chunk, see if it's
// encoded as a SMTP destination in there.
if(recipientSearchChunk != null) {
String search = recipientSearchChunk.getAs7bitString();
if(search.indexOf("SMTP:") != -1) {
return search.substring(search.indexOf("SMTP:") + 5);
}
}
// Can't find it
return null;
}
/** Holds all the chunks that were found. */
private List<Chunk> allChunks = new ArrayList<Chunk>();
public Map<MAPIProperty,List<PropertyValue>> getProperties() { /**
if (recipientProperties != null) { * Tries to find their name, in whichever chunk holds it.
return recipientProperties.getProperties(); */
} public String getRecipientName() {
else return Collections.emptyMap(); if (recipientNameChunk != null) {
} return recipientNameChunk.getValue();
public Chunk[] getAll() { }
return allChunks.toArray(new Chunk[allChunks.size()]); if (recipientDisplayNameChunk != null) {
} return recipientDisplayNameChunk.getValue();
public Chunk[] getChunks() { }
return getAll();
}
/**
* Called by the parser whenever a chunk is found.
*/
public void record(Chunk chunk) {
if(chunk.getChunkId() == RECIPIENT_SEARCH.id) {
// TODO - parse
recipientSearchChunk = (ByteChunk)chunk;
}
else if(chunk.getChunkId() == RECIPIENT_NAME.id) {
recipientDisplayNameChunk = (StringChunk)chunk;
}
else if(chunk.getChunkId() == RECIPIENT_DISPLAY_NAME.id) {
recipientNameChunk = (StringChunk)chunk;
}
else if(chunk.getChunkId() == RECIPIENT_EMAIL_ADDRESS.id) {
recipientEmailChunk = (StringChunk)chunk;
}
else if(chunk.getChunkId() == RECIPIENT_SMTP_ADDRESS.id) {
recipientSMTPChunk = (StringChunk)chunk;
}
else if(chunk.getChunkId() == DELIVERY_TYPE.id) {
deliveryTypeChunk = (StringChunk)chunk;
}
else if(chunk instanceof PropertiesChunk) {
recipientProperties = (PropertiesChunk) chunk;
}
// And add to the main list // Can't find it
allChunks.add(chunk); return null;
} }
public void chunksComplete() {
if (recipientProperties != null) {
recipientProperties.matchVariableSizedPropertiesToChunks();
} else {
logger.log(POILogger.WARN, "Recipeints Chunk didn't contain a list of properties!");
}
}
/** /**
* Orders by the recipient number. * Tries to find their email address, in whichever chunk holds it given the
*/ * delivery type.
public static class RecipientChunksSorter implements Comparator<RecipientChunks>, Serializable { */
public int compare(RecipientChunks a, RecipientChunks b) { public String getRecipientEmailAddress() {
if(a.recipientNumber < b.recipientNumber) // If we have this, it really has the email
return -1; if (recipientSMTPChunk != null) {
if(a.recipientNumber > b.recipientNumber) return recipientSMTPChunk.getValue();
return +1; }
return 0;
} // This might be a real email, or might be
} // in CN=... format
if (recipientEmailChunk != null) {
String email = recipientEmailChunk.getValue();
int cne = email.indexOf("/CN=");
if (cne == -1) {
// Normal smtp address
return email;
} else {
// /O=..../CN=em@ail
return email.substring(cne + 4);
}
}
// Might be in the name field, check there
if (recipientNameChunk != null) {
String name = recipientNameChunk.getValue();
if (name.indexOf('@') > -1) {
// Strip leading and trailing quotes if needed
if (name.startsWith("'") && name.endsWith("'")) {
return name.substring(1, name.length() - 1);
}
return name;
}
}
// Check the search chunk, see if it's
// encoded as a SMTP destination in there.
if (recipientSearchChunk != null) {
String search = recipientSearchChunk.getAs7bitString();
if (search.indexOf("SMTP:") != -1) {
return search.substring(search.indexOf("SMTP:") + 5);
}
}
// Can't find it
return null;
}
/** Holds all the chunks that were found. */
private List<Chunk> allChunks = new ArrayList<Chunk>();
public Map<MAPIProperty, List<PropertyValue>> getProperties() {
if (recipientProperties != null) {
return recipientProperties.getProperties();
} else
return Collections.emptyMap();
}
public Chunk[] getAll() {
return allChunks.toArray(new Chunk[allChunks.size()]);
}
public Chunk[] getChunks() {
return getAll();
}
/**
* Called by the parser whenever a chunk is found.
*/
public void record(Chunk chunk) {
if (chunk.getChunkId() == RECIPIENT_SEARCH.id) {
// TODO - parse
recipientSearchChunk = (ByteChunk) chunk;
} else if (chunk.getChunkId() == RECIPIENT_NAME.id) {
recipientDisplayNameChunk = (StringChunk) chunk;
} else if (chunk.getChunkId() == RECIPIENT_DISPLAY_NAME.id) {
recipientNameChunk = (StringChunk) chunk;
} else if (chunk.getChunkId() == RECIPIENT_EMAIL_ADDRESS.id) {
recipientEmailChunk = (StringChunk) chunk;
} else if (chunk.getChunkId() == RECIPIENT_SMTP_ADDRESS.id) {
recipientSMTPChunk = (StringChunk) chunk;
} else if (chunk.getChunkId() == DELIVERY_TYPE.id) {
deliveryTypeChunk = (StringChunk) chunk;
} else if (chunk instanceof PropertiesChunk) {
recipientProperties = (PropertiesChunk) chunk;
}
// And add to the main list
allChunks.add(chunk);
}
public void chunksComplete() {
if (recipientProperties != null) {
recipientProperties.matchVariableSizedPropertiesToChunks();
} else {
logger.log(POILogger.WARN, "Recipeints Chunk didn't contain a list of properties!");
}
}
/**
* Orders by the recipient number.
*/
public static class RecipientChunksSorter
implements Comparator<RecipientChunks>, Serializable {
public int compare(RecipientChunks a, RecipientChunks b) {
if (a.recipientNumber < b.recipientNumber)
return -1;
if (a.recipientNumber > b.recipientNumber)
return +1;
return 0;
}
}
} }

View File

@ -24,30 +24,29 @@ import java.io.OutputStream;
import org.apache.poi.util.LittleEndian; import org.apache.poi.util.LittleEndian;
/** /**
* A {@link PropertiesChunk} for a Storage Properties, such as * A {@link PropertiesChunk} for a Storage Properties, such as Attachments and
* Attachments and Recipients. * Recipients. This only has a 8 byte header
* This only has a 8 byte header
*/ */
public class StoragePropertiesChunk extends PropertiesChunk { public class StoragePropertiesChunk extends PropertiesChunk {
public StoragePropertiesChunk(ChunkGroup parentGroup) { public StoragePropertiesChunk(ChunkGroup parentGroup) {
super(parentGroup); super(parentGroup);
} }
@Override
public void readValue(InputStream stream) throws IOException {
// 8 bytes of reserved zeros
LittleEndian.readLong(stream);
// Now properties
readProperties(stream);
}
@Override @Override
public void writeValue(OutputStream out) throws IOException { public void readValue(InputStream stream) throws IOException {
// 8 bytes of reserved zeros // 8 bytes of reserved zeros
out.write(new byte[8]); LittleEndian.readLong(stream);
// Now properties // Now properties
writeProperties(out); readProperties(stream);
} }
@Override
public void writeValue(OutputStream out) throws IOException {
// 8 bytes of reserved zeros
out.write(new byte[8]);
// Now properties
writeProperties(out);
}
} }

View File

@ -30,120 +30,119 @@ import org.apache.poi.util.StringUtil;
* A Chunk made up of a single string. * A Chunk made up of a single string.
*/ */
public class StringChunk extends Chunk { public class StringChunk extends Chunk {
private static final String DEFAULT_ENCODING = "CP1252"; private static final String DEFAULT_ENCODING = "CP1252";
private String encoding7Bit = DEFAULT_ENCODING; private String encoding7Bit = DEFAULT_ENCODING;
private byte[] rawValue; private byte[] rawValue;
private String value; private String value;
/** /**
* Creates a String Chunk. * Creates a String Chunk.
*/ */
public StringChunk(String namePrefix, int chunkId, MAPIType type) { public StringChunk(String namePrefix, int chunkId, MAPIType type) {
super(namePrefix, chunkId, type); super(namePrefix, chunkId, type);
} }
/** /**
* Create a String Chunk, with the specified * Create a String Chunk, with the specified type.
* type. */
*/ public StringChunk(int chunkId, MAPIType type) {
public StringChunk(int chunkId, MAPIType type) { super(chunkId, type);
super(chunkId, type); }
}
/** /**
* Returns the Encoding that will be used to * Returns the Encoding that will be used to decode any "7 bit" (non
* decode any "7 bit" (non unicode) data. * unicode) data. Most files default to CP1252
* Most files default to CP1252 */
*/ public String get7BitEncoding() {
public String get7BitEncoding() { return encoding7Bit;
return encoding7Bit; }
}
/** /**
* Sets the Encoding that will be used to * Sets the Encoding that will be used to decode any "7 bit" (non unicode)
* decode any "7 bit" (non unicode) data. * data. This doesn't appear to be stored anywhere specific in the file, so
* This doesn't appear to be stored anywhere * you may need to guess by looking at headers etc
* specific in the file, so you may need */
* to guess by looking at headers etc public void set7BitEncoding(String encoding) {
*/ this.encoding7Bit = encoding;
public void set7BitEncoding(String encoding) {
this.encoding7Bit = encoding;
// Re-read the String if we're a 7 bit one // Re-read the String if we're a 7 bit one
if(type == Types.ASCII_STRING) { if (type == Types.ASCII_STRING) {
parseString(); parseString();
} }
} }
public void readValue(InputStream value) throws IOException { public void readValue(InputStream value) throws IOException {
rawValue = IOUtils.toByteArray(value); rawValue = IOUtils.toByteArray(value);
parseString(); parseString();
} }
private void parseString() {
String tmpValue;
if (type == Types.ASCII_STRING) {
tmpValue = parseAs7BitData(rawValue, encoding7Bit);
} else if (type == Types.UNICODE_STRING) {
tmpValue = StringUtil.getFromUnicodeLE(rawValue);
} else {
throw new IllegalArgumentException("Invalid type " + type + " for String Chunk");
}
// Clean up private void parseString() {
this.value = tmpValue.replace("\0", ""); String tmpValue;
} if (type == Types.ASCII_STRING) {
tmpValue = parseAs7BitData(rawValue, encoding7Bit);
} else if (type == Types.UNICODE_STRING) {
tmpValue = StringUtil.getFromUnicodeLE(rawValue);
} else {
throw new IllegalArgumentException("Invalid type " + type + " for String Chunk");
}
public void writeValue(OutputStream out) throws IOException { // Clean up
out.write(rawValue); this.value = tmpValue.replace("\0", "");
} }
private void storeString() {
if (type == Types.ASCII_STRING) {
rawValue = value.getBytes(Charset.forName(encoding7Bit));
} else if (type == Types.UNICODE_STRING) {
rawValue = StringUtil.getToUnicodeLE(value);
} else {
throw new IllegalArgumentException("Invalid type " + type + " for String Chunk");
}
}
/** public void writeValue(OutputStream out) throws IOException {
* Returns the Text value of the chunk out.write(rawValue);
*/ }
public String getValue() {
return this.value;
}
public byte[] getRawValue() { private void storeString() {
return this.rawValue; if (type == Types.ASCII_STRING) {
} rawValue = value.getBytes(Charset.forName(encoding7Bit));
} else if (type == Types.UNICODE_STRING) {
rawValue = StringUtil.getToUnicodeLE(value);
} else {
throw new IllegalArgumentException("Invalid type " + type + " for String Chunk");
}
}
public void setValue(String str) { /**
this.value = str; * Returns the Text value of the chunk
storeString(); */
} public String getValue() {
return this.value;
}
public String toString() { public byte[] getRawValue() {
return this.value; return this.rawValue;
} }
/** public void setValue(String str) {
* Parses as non-unicode, supposedly 7 bit CP1252 data this.value = str;
* and returns the string that that yields. storeString();
*/ }
protected static String parseAs7BitData(byte[] data) {
return parseAs7BitData(data, DEFAULT_ENCODING); public String toString() {
} return this.value;
/** }
* Parses as non-unicode, supposedly 7 bit data
* and returns the string that that yields. /**
*/ * Parses as non-unicode, supposedly 7 bit CP1252 data and returns the
protected static String parseAs7BitData(byte[] data, String encoding) { * string that that yields.
// Handle any encoding aliases, where outlook describes it differently */
if ("ansi".equals(encoding)) { protected static String parseAs7BitData(byte[] data) {
encoding = DEFAULT_ENCODING; return parseAs7BitData(data, DEFAULT_ENCODING);
} }
// Decode /**
return new String(data, Charset.forName(encoding)); * Parses as non-unicode, supposedly 7 bit data and returns the string that
} * that yields.
*/
protected static String parseAs7BitData(byte[] data, String encoding) {
// Handle any encoding aliases, where outlook describes it differently
if ("ansi".equals(encoding)) {
encoding = DEFAULT_ENCODING;
}
// Decode
return new String(data, Charset.forName(encoding));
}
} }

View File

@ -23,158 +23,169 @@ import java.util.Map;
/** /**
* The types list and details are available from * The types list and details are available from
* http://msdn.microsoft.com/en-us/library/microsoft.exchange.data.contenttypes.tnef.tnefpropertytype%28v=EXCHG.140%29.aspx * http://msdn.microsoft.com/en-us/library/microsoft.exchange.data.contenttypes.tnef.tnefpropertytype%28v=EXCHG.140%29.aspx
*/ */
public final class Types { public final class Types {
private static Map<Integer, MAPIType> builtInTypes = new HashMap<Integer, MAPIType>(); private static Map<Integer, MAPIType> builtInTypes = new HashMap<Integer, MAPIType>();
private static Map<Integer, MAPIType> customTypes = new HashMap<Integer, Types.MAPIType>(); private static Map<Integer, MAPIType> customTypes = new HashMap<Integer, Types.MAPIType>();
/** Unspecified */ /** Unspecified */
public static final MAPIType UNSPECIFIED = new MAPIType(0x0000, "Unspecified", -1); public static final MAPIType UNSPECIFIED = new MAPIType(0x0000,
/** Unknown */ "Unspecified", -1);
public static final MAPIType UNKNOWN = new MAPIType(-1, "Unknown", -1); /** Unknown */
public static final MAPIType UNKNOWN = new MAPIType(-1, "Unknown", -1);
/** Null - NULL property value */ /** Null - NULL property value */
public static final MAPIType NULL = new MAPIType(0x0001, "Null", 0); public static final MAPIType NULL = new MAPIType(0x0001, "Null", 0);
/** I2 - signed 16-bit value */ /** I2 - signed 16-bit value */
public static final MAPIType SHORT = new MAPIType(0x0002, "Short", 2); public static final MAPIType SHORT = new MAPIType(0x0002, "Short", 2);
/** Long - signed 32-bit value */ /** Long - signed 32-bit value */
public static final MAPIType LONG = new MAPIType(0x0003, "Long", 4); public static final MAPIType LONG = new MAPIType(0x0003, "Long", 4);
/** R4 - 4-byte floating point value */ /** R4 - 4-byte floating point value */
public static final MAPIType FLOAT = new MAPIType(0x0004, "Float", 4); public static final MAPIType FLOAT = new MAPIType(0x0004, "Float", 4);
/** Double - floating point double */ /** Double - floating point double */
public static final MAPIType DOUBLE = new MAPIType(0x0005, "Double", 8); public static final MAPIType DOUBLE = new MAPIType(0x0005, "Double", 8);
/** Currency - signed 64-bit integer that represents a base ten decimal with four digits to the right of the decimal point */ /**
public static final MAPIType CURRENCY = new MAPIType(0x0006, "Currency", 8); * Currency - signed 64-bit integer that represents a base ten decimal with
/** AppTime - application time value */ * four digits to the right of the decimal point
public static final MAPIType APP_TIME = new MAPIType(0x0007, "Application Time", 8); */
/** Error - 32-bit error value */ public static final MAPIType CURRENCY = new MAPIType(0x0006, "Currency", 8);
public static final MAPIType ERROR = new MAPIType(0x000A, "Error", 4); /** AppTime - application time value */
/** Boolean - 16-bit Boolean value. '0' is false. Non-zero is true */ public static final MAPIType APP_TIME = new MAPIType(0x0007, "Application Time", 8);
public static final MAPIType BOOLEAN = new MAPIType(0x000B, "Boolean", 2); /** Error - 32-bit error value */
/** Object/Directory - embedded object in a property */ public static final MAPIType ERROR = new MAPIType(0x000A, "Error", 4);
public static final MAPIType DIRECTORY = new MAPIType(0x000D, "Directory", -1); /** Boolean - 16-bit Boolean value. '0' is false. Non-zero is true */
/** I8 - 8-byte signed integer */ public static final MAPIType BOOLEAN = new MAPIType(0x000B, "Boolean", 2);
public static final MAPIType LONG_LONG = new MAPIType(0x0014, "Long Long", 8); /** Object/Directory - embedded object in a property */
/** SysTime - FILETIME 64-bit integer specifying the number of 100ns periods since Jan 1, 1601 */ public static final MAPIType DIRECTORY = new MAPIType(0x000D, "Directory", -1);
public static final MAPIType TIME = new MAPIType(0x0040, "Time", 8); /** I8 - 8-byte signed integer */
/** ClassId - OLE GUID */ public static final MAPIType LONG_LONG = new MAPIType(0x0014, "Long Long", 8);
public static final MAPIType CLS_ID = new MAPIType(0x0048, "CLS ID GUID", 16); /**
* SysTime - FILETIME 64-bit integer specifying the number of 100ns periods
* since Jan 1, 1601
*/
public static final MAPIType TIME = new MAPIType(0x0040, "Time", 8);
/** ClassId - OLE GUID */
public static final MAPIType CLS_ID = new MAPIType(0x0048, "CLS ID GUID", 16);
/** Binary - counted byte array */ /** Binary - counted byte array */
public static final MAPIType BINARY = new MAPIType(0x0102, "Binary", -1); public static final MAPIType BINARY = new MAPIType(0x0102, "Binary", -1);
/** /**
* An 8-bit string, probably in CP1252, but don't quote us... * An 8-bit string, probably in CP1252, but don't quote us... Normally used
* Normally used for everything before Outlook 3.0, and some * for everything before Outlook 3.0, and some fields in Outlook 3.0.
* fields in Outlook 3.0. */
*/ public static final MAPIType ASCII_STRING = new MAPIType(0x001E, "ASCII String", -1);
public static final MAPIType ASCII_STRING = new MAPIType(0x001E, "ASCII String", -1); /** A string, from Outlook 3.0 onwards. Normally unicode */
/** A string, from Outlook 3.0 onwards. Normally unicode */ public static final MAPIType UNICODE_STRING = new MAPIType(0x001F, "Unicode String", -1);
public static final MAPIType UNICODE_STRING = new MAPIType(0x001F, "Unicode String", -1);
/** MultiValued - Value part contains multiple values */ /** MultiValued - Value part contains multiple values */
public static final int MULTIVALUED_FLAG = 0x1000; public static final int MULTIVALUED_FLAG = 0x1000;
public static final class MAPIType { public static final class MAPIType {
private final int id; private final int id;
private final String name; private final String name;
private final int length; private final int length;
/**
* Creates a standard, built-in type
*/
private MAPIType(int id, String name, int length) {
this.id = id;
this.name = name;
this.length = length;
builtInTypes.put(id, this);
}
/**
* Creates a custom type
*/
private MAPIType(int id, int length) {
this.id = id;
this.name = asCustomName(id);
this.length = length;
customTypes.put(id, this);
}
/**
* Returns the length, in bytes, of values of this type, or
* -1 if it is a variable length type.
*/
public int getLength() {
return length;
}
/**
* Is this type a fixed-length type, or a variable-length one?
*/
public boolean isFixedLength() {
return (length != -1);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String toString() {
return id + " / 0x" + asFileEnding() + " - " + name + " @ " + length;
}
/**
* Return the 4 character hex encoded version,
* as used in file endings
*/
public String asFileEnding() {
return Types.asFileEnding(id);
}
}
public static MAPIType getById(int typeId) {
return builtInTypes.get(typeId);
}
public static String asFileEnding(int type) { /**
String str = Integer.toHexString(type).toUpperCase(Locale.ROOT); * Creates a standard, built-in type
while(str.length() < 4) { */
str = "0" + str; private MAPIType(int id, String name, int length) {
} this.id = id;
return str; this.name = name;
} this.length = length;
public static String asName(int typeId) { builtInTypes.put(id, this);
MAPIType type = builtInTypes.get(typeId); }
if (type != null) {
return type.name; /**
} * Creates a custom type
return asCustomName(typeId); */
} private MAPIType(int id, int length) {
private static String asCustomName(int typeId) { this.id = id;
return "0x" + Integer.toHexString(typeId); this.name = asCustomName(id);
} this.length = length;
customTypes.put(id, this);
public static MAPIType createCustom(int typeId) { }
// Check they're not being silly, and asking for a built-in one...
if (getById(typeId) != null) { /**
return getById(typeId); * Returns the length, in bytes, of values of this type, or -1 if it is
} * a variable length type.
*/
// Try to get an existing definition of this public int getLength() {
MAPIType type = customTypes.get(typeId); return length;
}
// If none, do a thread-safe creation
if (type == null) { /**
synchronized (customTypes) { * Is this type a fixed-length type, or a variable-length one?
type = customTypes.get(typeId); */
if (type == null) { public boolean isFixedLength() {
type = new MAPIType(typeId, -1); return (length != -1);
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String toString() {
return id + " / 0x" + asFileEnding() + " - " + name + " @ "
+ length;
}
/**
* Return the 4 character hex encoded version, as used in file endings
*/
public String asFileEnding() {
return Types.asFileEnding(id);
}
}
public static MAPIType getById(int typeId) {
return builtInTypes.get(typeId);
}
public static String asFileEnding(int type) {
String str = Integer.toHexString(type).toUpperCase(Locale.ROOT);
while (str.length() < 4) {
str = "0" + str;
}
return str;
}
public static String asName(int typeId) {
MAPIType type = builtInTypes.get(typeId);
if (type != null) {
return type.name;
}
return asCustomName(typeId);
}
private static String asCustomName(int typeId) {
return "0x" + Integer.toHexString(typeId);
}
public static MAPIType createCustom(int typeId) {
// Check they're not being silly, and asking for a built-in one...
if (getById(typeId) != null) {
return getById(typeId);
}
// Try to get an existing definition of this
MAPIType type = customTypes.get(typeId);
// If none, do a thread-safe creation
if (type == null) {
synchronized (customTypes) {
type = customTypes.get(typeId);
if (type == null) {
type = new MAPIType(typeId, -1);
}
} }
} }
}
return type;
return type; }
}
} }