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:
parent
f27507244c
commit
6a8cb7493c
@ -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 ByteChunk attachData;
|
||||||
public StringChunk attachExtension;
|
public StringChunk attachExtension;
|
||||||
public StringChunk attachFileName;
|
public StringChunk attachFileName;
|
||||||
public StringChunk attachLongFileName;
|
public StringChunk attachLongFileName;
|
||||||
public StringChunk attachMimeTag;
|
public StringChunk attachMimeTag;
|
||||||
public DirectoryChunk attachmentDirectory;
|
public DirectoryChunk attachmentDirectory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is in WMF Format. You'll probably want to pass it
|
* This is in WMF Format. You'll probably want to pass it to Apache Batik to
|
||||||
* to Apache Batik to turn it into a SVG that you can
|
* turn it into a SVG that you can then display.
|
||||||
* then display.
|
*/
|
||||||
*/
|
public ByteChunk attachRenderingWMF;
|
||||||
public ByteChunk attachRenderingWMF;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* What the POIFS name of this attachment is.
|
* What the POIFS name of this attachment is.
|
||||||
*/
|
*/
|
||||||
private String poifsName;
|
private String poifsName;
|
||||||
|
|
||||||
/** 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 AttachmentChunks(String poifsName) {
|
||||||
|
this.poifsName = poifsName;
|
||||||
|
}
|
||||||
|
|
||||||
public AttachmentChunks(String poifsName) {
|
/**
|
||||||
this.poifsName = poifsName;
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this Attachment an embedded MAPI message?
|
* Returns the embedded object, if the attachment is an object based
|
||||||
*/
|
* embedding (image, document etc), or null if it's an embedded message
|
||||||
public boolean isEmbeddedMessage() {
|
*/
|
||||||
return (attachmentDirectory != null);
|
public byte[] getEmbeddedAttachmentObject() {
|
||||||
}
|
if (attachData != null) {
|
||||||
/**
|
return attachData.getValue();
|
||||||
* Returns the embedded MAPI message, if the attachment
|
}
|
||||||
* is an embedded message, or null otherwise
|
return null;
|
||||||
*/
|
}
|
||||||
public MAPIMessage getEmbeddedMessage() throws IOException {
|
|
||||||
if (attachmentDirectory != null) {
|
|
||||||
return attachmentDirectory.getAsEmbededMessage();
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public Chunk[] getAll() {
|
||||||
* Returns the embedded object, if the attachment is an
|
return allChunks.toArray(new Chunk[allChunks.size()]);
|
||||||
* 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() {
|
public Chunk[] getChunks() {
|
||||||
return allChunks.toArray(new Chunk[allChunks.size()]);
|
return getAll();
|
||||||
}
|
}
|
||||||
public Chunk[] getChunks() {
|
|
||||||
return getAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPOIFSName() {
|
public String getPOIFSName() {
|
||||||
return poifsName;
|
return poifsName;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the parser whenever a chunk is found.
|
* Called by the parser whenever a chunk is found.
|
||||||
*/
|
*/
|
||||||
public void record(Chunk chunk) {
|
public void record(Chunk chunk) {
|
||||||
// TODO: add further members for other properties like:
|
// TODO: add further members for other properties like:
|
||||||
// - ATTACH_ADDITIONAL_INFO
|
// - ATTACH_ADDITIONAL_INFO
|
||||||
// - ATTACH_CONTENT_BASE
|
// - ATTACH_CONTENT_BASE
|
||||||
// - ATTACH_CONTENT_LOCATION
|
// - ATTACH_CONTENT_LOCATION
|
||||||
// - ATTACH_DISPOSITION
|
// - ATTACH_DISPOSITION
|
||||||
// - ATTACH_ENCODING
|
// - ATTACH_ENCODING
|
||||||
// - ATTACH_FLAGS
|
// - ATTACH_FLAGS
|
||||||
// - ATTACH_LONG_PATHNAME
|
// - ATTACH_LONG_PATHNAME
|
||||||
// - ATTACH_SIZE
|
// - ATTACH_SIZE
|
||||||
final int chunkId = chunk.getChunkId();
|
final int chunkId = chunk.getChunkId();
|
||||||
if (chunkId == ATTACH_DATA.id) {
|
if (chunkId == ATTACH_DATA.id) {
|
||||||
if(chunk instanceof ByteChunk) {
|
if (chunk instanceof ByteChunk) {
|
||||||
attachData = (ByteChunk)chunk;
|
attachData = (ByteChunk) chunk;
|
||||||
} else if(chunk instanceof DirectoryChunk) {
|
} else if (chunk instanceof DirectoryChunk) {
|
||||||
attachmentDirectory = (DirectoryChunk)chunk;
|
attachmentDirectory = (DirectoryChunk) chunk;
|
||||||
} else {
|
} else {
|
||||||
logger.log(POILogger.ERROR, "Unexpected data chunk of type " + chunk);
|
logger.log(POILogger.ERROR, "Unexpected data chunk of type " + chunk);
|
||||||
}
|
}
|
||||||
} else if(chunkId == ATTACH_EXTENSION.id) {
|
} else if (chunkId == ATTACH_EXTENSION.id) {
|
||||||
attachExtension = (StringChunk)chunk;
|
attachExtension = (StringChunk) chunk;
|
||||||
} else if(chunkId == ATTACH_FILENAME.id) {
|
} else if (chunkId == ATTACH_FILENAME.id) {
|
||||||
attachFileName = (StringChunk)chunk;
|
attachFileName = (StringChunk) chunk;
|
||||||
} else if(chunkId == ATTACH_LONG_FILENAME.id) {
|
} else if (chunkId == ATTACH_LONG_FILENAME.id) {
|
||||||
attachLongFileName = (StringChunk)chunk;
|
attachLongFileName = (StringChunk) chunk;
|
||||||
} else if(chunkId == ATTACH_MIME_TAG.id) {
|
} else if (chunkId == ATTACH_MIME_TAG.id) {
|
||||||
attachMimeTag = (StringChunk)chunk;
|
attachMimeTag = (StringChunk) chunk;
|
||||||
} else if(chunkId == ATTACH_RENDERING.id) {
|
} else if (chunkId == ATTACH_RENDERING.id) {
|
||||||
attachRenderingWMF = (ByteChunk)chunk;
|
attachRenderingWMF = (ByteChunk) chunk;
|
||||||
}
|
}
|
||||||
|
|
||||||
// And add to the main list
|
// And add to the main list
|
||||||
allChunks.add(chunk);
|
allChunks.add(chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to flag that all the chunks of the attachment
|
* Used to flag that all the chunks of the attachment have now been located.
|
||||||
* 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
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
/**
|
* Orders by the attachment number.
|
||||||
* Orders by the attachment number.
|
*/
|
||||||
*/
|
public static class AttachmentChunksSorter
|
||||||
public static class AttachmentChunksSorter implements Comparator<AttachmentChunks>, Serializable {
|
implements Comparator<AttachmentChunks>, Serializable {
|
||||||
public int compare(AttachmentChunks a, AttachmentChunks b) {
|
public int compare(AttachmentChunks a, AttachmentChunks b) {
|
||||||
return a.poifsName.compareTo(b.poifsName);
|
return a.poifsName.compareTo(b.poifsName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
* Returns the data in a debug-friendly string format
|
return value;
|
||||||
*/
|
}
|
||||||
public String toString() {
|
|
||||||
return toDebugFriendlyString(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public void setValue(byte[] value) {
|
||||||
* Formats the byte array in a debug-friendly way,
|
this.value = value;
|
||||||
* showing all of a short array, and the start of a
|
}
|
||||||
* longer one.
|
|
||||||
*/
|
|
||||||
protected static String toDebugFriendlyString(byte[] value) {
|
|
||||||
if (value == null)
|
|
||||||
return "(Null Byte Array)";
|
|
||||||
|
|
||||||
StringBuffer text = new StringBuffer();
|
/**
|
||||||
text.append("Bytes len=").append(value.length);
|
* Returns the data in a debug-friendly string format
|
||||||
text.append(" [");
|
*/
|
||||||
|
public String toString() {
|
||||||
|
return toDebugFriendlyString(value);
|
||||||
|
}
|
||||||
|
|
||||||
int limit = Math.min(value.length, 16);
|
/**
|
||||||
if (value.length > 16) {
|
* Formats the byte array in a debug-friendly way, showing all of a short
|
||||||
limit = 12;
|
* array, and the start of a longer one.
|
||||||
}
|
*/
|
||||||
for (int i=0; i<limit; i++) {
|
protected static String toDebugFriendlyString(byte[] value) {
|
||||||
if (i > 0)
|
if (value == null)
|
||||||
text.append(',');
|
return "(Null Byte Array)";
|
||||||
text.append(value[i]);
|
|
||||||
}
|
|
||||||
if (value.length > 16) {
|
|
||||||
text.append(",....");
|
|
||||||
}
|
|
||||||
text.append("]");
|
|
||||||
return text.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
StringBuffer text = new StringBuffer();
|
||||||
* Returns the data, formatted as a string assuming it
|
text.append("Bytes len=").append(value.length);
|
||||||
* was a non-unicode string.
|
text.append(" [");
|
||||||
* If your data isn't in fact stored as basically
|
|
||||||
* ASCII, don't expect this to return much of any
|
int limit = Math.min(value.length, 16);
|
||||||
* sense....
|
if (value.length > 16) {
|
||||||
* @return the data formatted as a string
|
limit = 12;
|
||||||
*/
|
}
|
||||||
public String getAs7bitString() {
|
for (int i = 0; i < limit; i++) {
|
||||||
return StringChunk.parseAs7BitData(value);
|
if (i > 0)
|
||||||
}
|
text.append(',');
|
||||||
|
text.append(value[i]);
|
||||||
|
}
|
||||||
|
if (value.length > 16) {
|
||||||
|
text.append(",....");
|
||||||
|
}
|
||||||
|
text.append("]");
|
||||||
|
return text.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the data, formatted as a string assuming it 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....
|
||||||
|
*
|
||||||
|
* @return the data formatted as a string
|
||||||
|
*/
|
||||||
|
public String getAs7bitString() {
|
||||||
|
return StringChunk.parseAs7BitData(value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 int chunkId;
|
||||||
protected MAPIType type;
|
protected MAPIType type;
|
||||||
protected String namePrefix;
|
protected String namePrefix;
|
||||||
|
|
||||||
protected Chunk(String namePrefix, int chunkId, MAPIType type) {
|
protected Chunk(String namePrefix, int chunkId, MAPIType type) {
|
||||||
this.namePrefix = namePrefix;
|
this.namePrefix = namePrefix;
|
||||||
this.chunkId = chunkId;
|
this.chunkId = chunkId;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
protected Chunk(int chunkId, MAPIType type) {
|
|
||||||
this(DEFAULT_NAME_PREFIX, chunkId, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
protected Chunk(int chunkId, MAPIType type) {
|
||||||
* Gets the id of this chunk
|
this(DEFAULT_NAME_PREFIX, chunkId, type);
|
||||||
*/
|
}
|
||||||
public int getChunkId() {
|
|
||||||
return this.chunkId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the numeric type of this chunk.
|
* Gets the id of this chunk
|
||||||
*/
|
*/
|
||||||
public MAPIType getType() {
|
public int getChunkId() {
|
||||||
return this.type;
|
return this.chunkId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a string to use to identify this chunk in the POI file system object.
|
* Gets the numeric type of this chunk.
|
||||||
*/
|
*/
|
||||||
public String getEntryName() {
|
public MAPIType getType() {
|
||||||
String type = this.type.asFileEnding();
|
return this.type;
|
||||||
|
}
|
||||||
|
|
||||||
String chunkId = Integer.toHexString(this.chunkId);
|
/**
|
||||||
while(chunkId.length() < 4) chunkId = "0" + chunkId;
|
* Creates a string to use to identify this chunk in the POI file system
|
||||||
|
* object.
|
||||||
|
*/
|
||||||
|
public String getEntryName() {
|
||||||
|
String type = this.type.asFileEnding();
|
||||||
|
|
||||||
return this.namePrefix + chunkId.toUpperCase(Locale.ROOT)
|
String chunkId = Integer.toHexString(this.chunkId);
|
||||||
+ type.toUpperCase(Locale.ROOT);
|
while (chunkId.length() < 4)
|
||||||
}
|
chunkId = "0" + chunkId;
|
||||||
|
|
||||||
/**
|
return this.namePrefix
|
||||||
* Writes the value of this chunk back out again.
|
+ chunkId.toUpperCase(Locale.ROOT)
|
||||||
*/
|
+ type.toUpperCase(Locale.ROOT);
|
||||||
public abstract void writeValue(OutputStream out) throws IOException;
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads the value of this chunk using an InputStream
|
* Writes the value of this chunk back out again.
|
||||||
*/
|
*/
|
||||||
public abstract void readValue(InputStream value) throws IOException;
|
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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
@ -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) */
|
/** Type of message that the MSG represents (ie. IPM.Note) */
|
||||||
public StringChunk messageClass;
|
public StringChunk messageClass;
|
||||||
/** BODY Chunk, for plain/text messages */
|
/** BODY Chunk, for plain/text messages */
|
||||||
public StringChunk textBodyChunk;
|
public StringChunk textBodyChunk;
|
||||||
/** BODY Html Chunk, for html messages */
|
/** BODY Html Chunk, for html messages */
|
||||||
public StringChunk htmlBodyChunkString;
|
public StringChunk htmlBodyChunkString;
|
||||||
public ByteChunk htmlBodyChunkBinary;
|
public ByteChunk htmlBodyChunkBinary;
|
||||||
/** BODY Rtf Chunk, for Rtf (Rich) messages */
|
/** BODY Rtf Chunk, for Rtf (Rich) messages */
|
||||||
public ByteChunk rtfBodyChunk;
|
public ByteChunk rtfBodyChunk;
|
||||||
/** Subject link chunk, in plain/text */
|
/** Subject link chunk, in plain/text */
|
||||||
public StringChunk subjectChunk;
|
public StringChunk subjectChunk;
|
||||||
/**
|
/**
|
||||||
* Value that is in the TO field (not actually the addresses as they are
|
* Value that is in the TO field (not actually the addresses as they are
|
||||||
* stored in recip directory nodes
|
* stored in recip directory nodes
|
||||||
*/
|
*/
|
||||||
public StringChunk displayToChunk;
|
public StringChunk displayToChunk;
|
||||||
/** Value that is in the FROM field */
|
/** Value that is in the FROM field */
|
||||||
public StringChunk displayFromChunk;
|
public StringChunk displayFromChunk;
|
||||||
/** value that shows in the CC field */
|
/** value that shows in the CC field */
|
||||||
public StringChunk displayCCChunk;
|
public StringChunk displayCCChunk;
|
||||||
/** Value that shows in the BCC field */
|
/** Value that shows in the BCC field */
|
||||||
public StringChunk displayBCCChunk;
|
public StringChunk displayBCCChunk;
|
||||||
/** Sort of like the subject line, but without the RE: and FWD: parts. */
|
/** Sort of like the subject line, but without the RE: and FWD: parts. */
|
||||||
public StringChunk conversationTopic;
|
public StringChunk conversationTopic;
|
||||||
/** Type of server that the message originated from (SMTP, etc). */
|
/** Type of server that the message originated from (SMTP, etc). */
|
||||||
public StringChunk sentByServerType;
|
public StringChunk sentByServerType;
|
||||||
/** The email headers */
|
/** The email headers */
|
||||||
public StringChunk messageHeaders;
|
public StringChunk messageHeaders;
|
||||||
/** TODO */
|
/** TODO */
|
||||||
public MessageSubmissionChunk submissionChunk;
|
public MessageSubmissionChunk submissionChunk;
|
||||||
/** TODO */
|
/** TODO */
|
||||||
public StringChunk emailFromChunk;
|
public StringChunk emailFromChunk;
|
||||||
/** The message ID */
|
/** The message ID */
|
||||||
public StringChunk messageId;
|
public StringChunk messageId;
|
||||||
/** The message properties */
|
/** The message properties */
|
||||||
private MessagePropertiesChunk messageProperties;
|
private MessagePropertiesChunk messageProperties;
|
||||||
|
|
||||||
public Map<MAPIProperty,List<PropertyValue>> getProperties() {
|
public Map<MAPIProperty, List<PropertyValue>> getProperties() {
|
||||||
if (messageProperties != null) {
|
if (messageProperties != null) {
|
||||||
return messageProperties.getProperties();
|
return messageProperties.getProperties();
|
||||||
}
|
} else
|
||||||
else return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
public Map<MAPIProperty, PropertyValue> getRawProperties() {
|
|
||||||
if (messageProperties != null) {
|
|
||||||
return messageProperties.getRawProperties();
|
|
||||||
}
|
|
||||||
else return Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<MAPIProperty,List<Chunk>> getAll() {
|
public Map<MAPIProperty, PropertyValue> getRawProperties() {
|
||||||
return allChunks;
|
if (messageProperties != null) {
|
||||||
}
|
return messageProperties.getRawProperties();
|
||||||
public Chunk[] getChunks() {
|
} else
|
||||||
ArrayList<Chunk> chunks = new ArrayList<Chunk>(allChunks.size());
|
return Collections.emptyMap();
|
||||||
for (List<Chunk> c : allChunks.values()) {
|
}
|
||||||
chunks.addAll(c);
|
|
||||||
}
|
|
||||||
return chunks.toArray(new Chunk[chunks.size()]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public Map<MAPIProperty, List<Chunk>> getAll() {
|
||||||
* Called by the parser whenever a chunk is found.
|
return allChunks;
|
||||||
*/
|
}
|
||||||
public void record(Chunk chunk) {
|
|
||||||
// Work out what MAPIProperty this corresponds to
|
|
||||||
MAPIProperty prop = MAPIProperty.get(chunk.getChunkId());
|
|
||||||
|
|
||||||
// Assign it for easy lookup, as best we can
|
public Chunk[] getChunks() {
|
||||||
if(prop == MAPIProperty.MESSAGE_CLASS) {
|
ArrayList<Chunk> chunks = new ArrayList<Chunk>(allChunks.size());
|
||||||
messageClass = (StringChunk)chunk;
|
for (List<Chunk> c : allChunks.values()) {
|
||||||
}
|
chunks.addAll(c);
|
||||||
else if(prop == MAPIProperty.INTERNET_MESSAGE_ID) {
|
}
|
||||||
messageId = (StringChunk)chunk;
|
return chunks.toArray(new Chunk[chunks.size()]);
|
||||||
}
|
}
|
||||||
else if(prop == MAPIProperty.MESSAGE_SUBMISSION_ID) {
|
|
||||||
// TODO - parse
|
|
||||||
submissionChunk = (MessageSubmissionChunk)chunk;
|
|
||||||
}
|
|
||||||
else if(prop == MAPIProperty.RECEIVED_BY_ADDRTYPE) {
|
|
||||||
sentByServerType = (StringChunk)chunk;
|
|
||||||
}
|
|
||||||
else if(prop == MAPIProperty.TRANSPORT_MESSAGE_HEADERS) {
|
|
||||||
messageHeaders = (StringChunk)chunk;
|
|
||||||
}
|
|
||||||
|
|
||||||
else if(prop == MAPIProperty.CONVERSATION_TOPIC) {
|
/**
|
||||||
conversationTopic = (StringChunk)chunk;
|
* Called by the parser whenever a chunk is found.
|
||||||
}
|
*/
|
||||||
else if(prop == MAPIProperty.SUBJECT) {
|
public void record(Chunk chunk) {
|
||||||
subjectChunk = (StringChunk)chunk;
|
// Work out what MAPIProperty this corresponds to
|
||||||
}
|
MAPIProperty prop = MAPIProperty.get(chunk.getChunkId());
|
||||||
else if(prop == MAPIProperty.ORIGINAL_SUBJECT) {
|
|
||||||
// TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
else if(prop == MAPIProperty.DISPLAY_TO) {
|
// Assign it for easy lookup, as best we can
|
||||||
displayToChunk = (StringChunk)chunk;
|
if (prop == MAPIProperty.MESSAGE_CLASS) {
|
||||||
}
|
messageClass = (StringChunk) chunk;
|
||||||
else if(prop == MAPIProperty.DISPLAY_CC) {
|
} else if (prop == MAPIProperty.INTERNET_MESSAGE_ID) {
|
||||||
displayCCChunk = (StringChunk)chunk;
|
messageId = (StringChunk) chunk;
|
||||||
}
|
} else if (prop == MAPIProperty.MESSAGE_SUBMISSION_ID) {
|
||||||
else if(prop == MAPIProperty.DISPLAY_BCC) {
|
// TODO - parse
|
||||||
displayBCCChunk = (StringChunk)chunk;
|
submissionChunk = (MessageSubmissionChunk) chunk;
|
||||||
}
|
} else if (prop == MAPIProperty.RECEIVED_BY_ADDRTYPE) {
|
||||||
|
sentByServerType = (StringChunk) chunk;
|
||||||
|
} else if (prop == MAPIProperty.TRANSPORT_MESSAGE_HEADERS) {
|
||||||
|
messageHeaders = (StringChunk) chunk;
|
||||||
|
}
|
||||||
|
|
||||||
else if(prop == MAPIProperty.SENDER_EMAIL_ADDRESS) {
|
else if (prop == MAPIProperty.CONVERSATION_TOPIC) {
|
||||||
emailFromChunk = (StringChunk)chunk;
|
conversationTopic = (StringChunk) chunk;
|
||||||
}
|
} else if (prop == MAPIProperty.SUBJECT) {
|
||||||
else if(prop == MAPIProperty.SENDER_NAME) {
|
subjectChunk = (StringChunk) chunk;
|
||||||
displayFromChunk = (StringChunk)chunk;
|
} else if (prop == MAPIProperty.ORIGINAL_SUBJECT) {
|
||||||
}
|
// TODO
|
||||||
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
|
else if (prop == MAPIProperty.DISPLAY_TO) {
|
||||||
if (allChunks.get(prop) == null) {
|
displayToChunk = (StringChunk) chunk;
|
||||||
allChunks.put(prop, new ArrayList<Chunk>());
|
} else if (prop == MAPIProperty.DISPLAY_CC) {
|
||||||
}
|
displayCCChunk = (StringChunk) chunk;
|
||||||
allChunks.get(prop).add(chunk);
|
} else if (prop == MAPIProperty.DISPLAY_BCC) {
|
||||||
}
|
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!");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -25,11 +25,8 @@ 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;
|
||||||
@ -40,18 +37,16 @@ public class DirectoryChunk extends Chunk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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);
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -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() {
|
public long getNextRecipientId() {
|
||||||
return nextRecipientId;
|
return nextRecipientId;
|
||||||
}
|
}
|
||||||
public long getNextAttachmentId() {
|
|
||||||
return nextAttachmentId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRecipientCount() {
|
public long getNextAttachmentId() {
|
||||||
return recipientCount;
|
return nextAttachmentId;
|
||||||
}
|
}
|
||||||
public long getAttachmentCount() {
|
|
||||||
return attachmentCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
public long getRecipientCount() {
|
||||||
public void readValue(InputStream stream) throws IOException {
|
return recipientCount;
|
||||||
// 8 bytes of reserved zeros
|
}
|
||||||
LittleEndian.readLong(stream);
|
|
||||||
|
|
||||||
// Nexts and counts
|
public long getAttachmentCount() {
|
||||||
nextRecipientId = LittleEndian.readUInt(stream);
|
return attachmentCount;
|
||||||
nextAttachmentId = LittleEndian.readUInt(stream);
|
}
|
||||||
recipientCount = LittleEndian.readUInt(stream);
|
|
||||||
attachmentCount = LittleEndian.readUInt(stream);
|
|
||||||
|
|
||||||
// 8 bytes of reserved zeros
|
@Override
|
||||||
LittleEndian.readLong(stream);
|
public void readValue(InputStream stream) throws IOException {
|
||||||
|
// 8 bytes of reserved zeros
|
||||||
|
LittleEndian.readLong(stream);
|
||||||
|
|
||||||
// Now properties
|
// Nexts and counts
|
||||||
readProperties(stream);
|
nextRecipientId = LittleEndian.readUInt(stream);
|
||||||
}
|
nextAttachmentId = LittleEndian.readUInt(stream);
|
||||||
|
recipientCount = LittleEndian.readUInt(stream);
|
||||||
|
attachmentCount = LittleEndian.readUInt(stream);
|
||||||
|
|
||||||
@Override
|
// 8 bytes of reserved zeros
|
||||||
public void writeValue(OutputStream out) throws IOException {
|
LittleEndian.readLong(stream);
|
||||||
// 8 bytes of reserved zeros
|
|
||||||
out.write(new byte[8]);
|
|
||||||
|
|
||||||
// Nexts and counts
|
// Now properties
|
||||||
LittleEndian.putUInt(nextRecipientId, out);
|
readProperties(stream);
|
||||||
LittleEndian.putUInt(nextAttachmentId, out);
|
}
|
||||||
LittleEndian.putUInt(recipientCount, out);
|
|
||||||
LittleEndian.putUInt(attachmentCount, out);
|
|
||||||
|
|
||||||
// 8 bytes of reserved zeros
|
@Override
|
||||||
out.write(new byte[8]);
|
public void writeValue(OutputStream out) throws IOException {
|
||||||
|
// 8 bytes of reserved zeros
|
||||||
|
out.write(new byte[8]);
|
||||||
|
|
||||||
// Now properties
|
// Nexts and counts
|
||||||
writeProperties(out);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
// Now process the date
|
||||||
String[] parts = rawId.split(";");
|
String[] parts = rawId.split(";");
|
||||||
for(String part : parts) {
|
for (String part : parts) {
|
||||||
if(part.startsWith("l=")) {
|
if (part.startsWith("l=")) {
|
||||||
// Format of this bit appears to be l=<id>-<time>-<number>
|
// Format of this bit appears to be l=<id>-<time>-<number>
|
||||||
// ID may contain hyphens.
|
// ID may contain hyphens.
|
||||||
|
|
||||||
String dateS = null;
|
String dateS = null;
|
||||||
final int numberPartBegin = part.lastIndexOf('-');
|
final int numberPartBegin = part.lastIndexOf('-');
|
||||||
if (numberPartBegin != -1) {
|
if (numberPartBegin != -1) {
|
||||||
final int datePartBegin = part.lastIndexOf('-', numberPartBegin-1);
|
final int datePartBegin = part.lastIndexOf('-',
|
||||||
if (datePartBegin != -1 &&
|
numberPartBegin - 1);
|
||||||
// cannot extract date if only one hyphen is in the string...
|
if (datePartBegin != -1 &&
|
||||||
numberPartBegin > datePartBegin) {
|
// cannot extract date if only one hyphen is in the
|
||||||
dateS = part.substring(datePartBegin + 1, numberPartBegin);
|
// 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
* Called by the parser whenever a chunk is found.
|
return getAll();
|
||||||
*/
|
}
|
||||||
public void record(Chunk chunk) {
|
|
||||||
allChunks.add(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to flag that all the chunks of the NameID
|
* Called by the parser whenever a chunk is found.
|
||||||
* have now been located.
|
*/
|
||||||
*/
|
public void record(Chunk chunk) {
|
||||||
public void chunksComplete() {
|
allChunks.add(chunk);
|
||||||
// Currently, we don't need to do anything special once
|
}
|
||||||
// all the chunks have been located
|
|
||||||
}
|
/**
|
||||||
|
* Used to flag that all the chunks of the NameID have now been located.
|
||||||
|
*/
|
||||||
|
public void chunksComplete() {
|
||||||
|
// Currently, we don't need to do anything special once
|
||||||
|
// all the chunks have been located
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 */
|
/** For logging problems we spot with the file */
|
||||||
private POILogger logger = POILogFactory.getLogger(PropertiesChunk.class);
|
private POILogger logger = POILogFactory.getLogger(PropertiesChunk.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds properties, indexed by type. If a property is multi-valued,
|
* Holds properties, indexed by type. If a property is multi-valued, or
|
||||||
* or variable length, it will be held via a {@link ChunkBasedPropertyValue}.
|
* variable length, it will be held via a {@link ChunkBasedPropertyValue}.
|
||||||
*/
|
*/
|
||||||
private Map<MAPIProperty, PropertyValue> properties =
|
private Map<MAPIProperty, PropertyValue> properties = new HashMap<MAPIProperty, PropertyValue>();
|
||||||
new HashMap<MAPIProperty, PropertyValue>();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The ChunkGroup that these properties apply to. Used when
|
* The ChunkGroup that these properties apply to. Used when matching chunks
|
||||||
* matching chunks to variable sized and multi-valued properties
|
* to variable sized and multi-valued properties
|
||||||
*/
|
*/
|
||||||
private ChunkGroup parentGroup;
|
private ChunkGroup parentGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Properties Chunk.
|
* Creates a Properties Chunk.
|
||||||
*/
|
*/
|
||||||
protected PropertiesChunk(ChunkGroup parentGroup) {
|
protected PropertiesChunk(ChunkGroup parentGroup) {
|
||||||
super(NAME, -1, Types.UNKNOWN);
|
super(NAME, -1, Types.UNKNOWN);
|
||||||
this.parentGroup = parentGroup;
|
this.parentGroup = parentGroup;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getEntryName() {
|
public String getEntryName() {
|
||||||
return NAME;
|
return NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all the properties in the chunk, without
|
* Returns all the properties in the chunk, without looking up any
|
||||||
* looking up any chunk-based values
|
* chunk-based values
|
||||||
*/
|
*/
|
||||||
public Map<MAPIProperty, PropertyValue> getRawProperties() {
|
public Map<MAPIProperty, PropertyValue> getRawProperties() {
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>Returns all the properties in the chunk, along with their
|
* <p>
|
||||||
* values.
|
* Returns all the properties in the chunk, along with their values.
|
||||||
* <p>Any chunk-based values will be looked up and returned as such
|
* <p>
|
||||||
*/
|
* Any chunk-based values will be looked up and returned as such
|
||||||
public Map<MAPIProperty, List<PropertyValue>> getProperties() {
|
*/
|
||||||
Map<MAPIProperty, List<PropertyValue>> props =
|
public Map<MAPIProperty, List<PropertyValue>> getProperties() {
|
||||||
new HashMap<MAPIProperty, List<PropertyValue>>(properties.size());
|
Map<MAPIProperty, List<PropertyValue>> props =
|
||||||
for (MAPIProperty prop : properties.keySet()) {
|
new HashMap<MAPIProperty, List<PropertyValue>>(properties.size());
|
||||||
props.put(prop, getValues(prop));
|
for (MAPIProperty prop : properties.keySet()) {
|
||||||
}
|
props.put(prop, getValues(prop));
|
||||||
return props;
|
}
|
||||||
}
|
return props;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all values for the given property, looking up chunk based
|
* Returns all values for the given property, looking up chunk based ones as
|
||||||
* ones as required, of null if none exist
|
* required, of null if none exist
|
||||||
*/
|
*/
|
||||||
public List<PropertyValue> getValues(MAPIProperty property) {
|
public List<PropertyValue> getValues(MAPIProperty property) {
|
||||||
PropertyValue val = properties.get(property);
|
PropertyValue val = properties.get(property);
|
||||||
if (val == null) {
|
if (val == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (val instanceof ChunkBasedPropertyValue) {
|
if (val instanceof ChunkBasedPropertyValue) {
|
||||||
// ChunkBasedPropertyValue cval = (ChunkBasedPropertyValue)val;
|
// ChunkBasedPropertyValue cval = (ChunkBasedPropertyValue)val;
|
||||||
// TODO Lookup
|
// TODO Lookup
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
} else {
|
} else {
|
||||||
return Collections.singletonList(val);
|
return Collections.singletonList(val);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the value / pointer to the value chunk of
|
* Returns the value / pointer to the value chunk of the property, or null
|
||||||
* the property, or null if none exists
|
* if none exists
|
||||||
*/
|
*/
|
||||||
public PropertyValue getRawValue(MAPIProperty property) {
|
public PropertyValue getRawValue(MAPIProperty property) {
|
||||||
return properties.get(property);
|
return properties.get(property);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called once the parent ChunkGroup has been populated, to match
|
* Called once the parent ChunkGroup has been populated, to match up the
|
||||||
* up the Chunks in it with our Variable Sized Properties.
|
* Chunks in it with our Variable Sized Properties.
|
||||||
*/
|
*/
|
||||||
protected void matchVariableSizedPropertiesToChunks() {
|
protected void matchVariableSizedPropertiesToChunks() {
|
||||||
// Index the Parent Group chunks for easy lookup
|
// Index the Parent Group chunks for easy lookup
|
||||||
// TODO Is this the right way?
|
// TODO Is this the right way?
|
||||||
Map<Integer,Chunk> chunks = new HashMap<Integer, Chunk>();
|
Map<Integer, Chunk> chunks = new HashMap<Integer, Chunk>();
|
||||||
for (Chunk chunk : parentGroup.getChunks()) {
|
for (Chunk chunk : parentGroup.getChunks()) {
|
||||||
chunks.put(chunk.chunkId, chunk);
|
chunks.put(chunk.chunkId, chunk);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loop over our values, looking for chunk based ones
|
// Loop over our values, looking for chunk based ones
|
||||||
for (PropertyValue val : properties.values()) {
|
for (PropertyValue val : properties.values()) {
|
||||||
if (val instanceof ChunkBasedPropertyValue) {
|
if (val instanceof ChunkBasedPropertyValue) {
|
||||||
ChunkBasedPropertyValue cVal = (ChunkBasedPropertyValue)val;
|
ChunkBasedPropertyValue cVal = (ChunkBasedPropertyValue) val;
|
||||||
Chunk chunk = chunks.get(cVal.getProperty().id);
|
Chunk chunk = chunks.get(cVal.getProperty().id);
|
||||||
//System.err.println(cVal.getProperty() + " = " + cVal + " -> " + HexDump.toHex(cVal.data));
|
// System.err.println(cVal.getProperty() + " = " + cVal + " -> "
|
||||||
|
// + HexDump.toHex(cVal.data));
|
||||||
|
|
||||||
// TODO Make sense of the raw offset value
|
// TODO Make sense of the raw offset value
|
||||||
|
|
||||||
if (chunk != null) {
|
if (chunk != null) {
|
||||||
cVal.setValue(chunk);
|
cVal.setValue(chunk);
|
||||||
} else {
|
|
||||||
logger.log(POILogger.WARN, "No chunk found matching Property " + cVal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void readProperties(InputStream value) throws IOException {
|
|
||||||
boolean going = true;
|
|
||||||
while (going) {
|
|
||||||
try {
|
|
||||||
// Read in the header
|
|
||||||
int typeID = LittleEndian.readUShort(value);
|
|
||||||
int id = LittleEndian.readUShort(value);
|
|
||||||
long flags = LittleEndian.readUInt(value);
|
|
||||||
|
|
||||||
// Turn the Type and ID into helper objects
|
|
||||||
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 {
|
} 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
|
protected void readProperties(InputStream value) throws IOException {
|
||||||
// then even fixed-length strings store their multiple
|
boolean going = true;
|
||||||
// values in another chunk (much as variable length ones)
|
while (going) {
|
||||||
|
try {
|
||||||
|
// Read in the header
|
||||||
|
int typeID = LittleEndian.readUShort(value);
|
||||||
|
int id = LittleEndian.readUShort(value);
|
||||||
|
long flags = LittleEndian.readUInt(value);
|
||||||
|
|
||||||
// Work out how long the "data" is
|
// Turn the Type and ID into helper objects
|
||||||
// This might be the actual data, or just a pointer
|
MAPIType type = Types.getById(typeID);
|
||||||
// to another chunk which holds the data itself
|
MAPIProperty prop = MAPIProperty.get(id);
|
||||||
boolean isPointer = false;
|
|
||||||
int length = type.getLength();
|
|
||||||
if (! type.isFixedLength()) {
|
|
||||||
isPointer = true;
|
|
||||||
length = 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grab the data block
|
// Wrap properties we don't know about as custom ones
|
||||||
byte[] data = new byte[length];
|
if (prop == MAPIProperty.UNKNOWN) {
|
||||||
IOUtils.readFully(value, data);
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
// Skip over any padding
|
// Sanity check the property's type against the value's type
|
||||||
if (length < 8) {
|
if (prop.usualType != type) {
|
||||||
byte[] padding = new byte[8-length];
|
// Is it an allowed substitution?
|
||||||
IOUtils.readFully(value, padding);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Wrap and store
|
// TODO Detect if it is multi-valued, since if it is
|
||||||
PropertyValue propVal = null;
|
// then even fixed-length strings store their multiple
|
||||||
if (isPointer) {
|
// values in another chunk (much as variable length ones)
|
||||||
// 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) {
|
// Work out how long the "data" is
|
||||||
logger.log(POILogger.WARN, "Duplicate values found for " + prop);
|
// This might be the actual data, or just a pointer
|
||||||
}
|
// to another chunk which holds the data itself
|
||||||
properties.put(prop, propVal);
|
boolean isPointer = false;
|
||||||
} catch (BufferUnderrunException e) {
|
int length = type.getLength();
|
||||||
// Invalid property, ended short
|
if (!type.isFixedLength()) {
|
||||||
going = false;
|
isPointer = true;
|
||||||
}
|
length = 8;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
protected void writeProperties(OutputStream out) throws IOException {
|
// Grab the data block
|
||||||
// TODO
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
public PropertyValue(MAPIProperty property, long flags, byte[] data) {
|
||||||
this.property = property;
|
this.property = property;
|
||||||
this.flags = flags;
|
this.flags = flags;
|
||||||
this.data = data;
|
this.data = data;
|
||||||
}
|
|
||||||
|
|
||||||
public MAPIProperty getProperty() {
|
|
||||||
return property;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the raw value flags.
|
|
||||||
* TODO Also provide getters for the flag meanings
|
|
||||||
*/
|
|
||||||
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 MAPIProperty getProperty() {
|
||||||
public LongLongPropertyValue(MAPIProperty property, long flags, byte[] data) {
|
return property;
|
||||||
super(property, flags, data);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Long getValue() {
|
/**
|
||||||
return LittleEndian.getLong(data);
|
* Get the raw value flags. TODO Also provide getters for the flag meanings
|
||||||
}
|
*/
|
||||||
public void setValue(long value) {
|
public long getFlags() {
|
||||||
if (data.length != 8) {
|
return flags;
|
||||||
data = new byte[8];
|
}
|
||||||
}
|
|
||||||
LittleEndian.putLong(data, 0, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class FloatPropertyValue extends PropertyValue {
|
public Object getValue() {
|
||||||
public FloatPropertyValue(MAPIProperty property, long flags, byte[] data) {
|
return data;
|
||||||
super(property, flags, data);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public Float getValue() {
|
public void setRawValue(byte[] value) {
|
||||||
return LittleEndian.getFloat(data);
|
this.data = value;
|
||||||
}
|
}
|
||||||
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 String toString() {
|
||||||
public DoublePropertyValue(MAPIProperty property, long flags, byte[] data) {
|
Object v = getValue();
|
||||||
super(property, flags, data);
|
if (v == null)
|
||||||
}
|
return "(No value available)";
|
||||||
|
|
||||||
public Double getValue() {
|
if (v instanceof byte[]) {
|
||||||
return LittleEndian.getDouble(data);
|
return ByteChunk.toDebugFriendlyString((byte[]) v);
|
||||||
}
|
} else {
|
||||||
public void setValue(double value) {
|
// Just use the normal toString on the value
|
||||||
if (data.length != 8) {
|
return v.toString();
|
||||||
data = new byte[8];
|
}
|
||||||
}
|
}
|
||||||
LittleEndian.putDouble(data, 0, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public static class NullPropertyValue extends PropertyValue {
|
||||||
* signed 64-bit integer that represents a base ten decimal,
|
public NullPropertyValue(MAPIProperty property, long flags,
|
||||||
* with four digits to the right of the decimal point
|
byte[] data) {
|
||||||
*/
|
super(property, flags, data);
|
||||||
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() {
|
public Void getValue() {
|
||||||
long unshifted = LittleEndian.getLong(data);
|
return null;
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
public static class BooleanPropertyValue extends PropertyValue {
|
||||||
* 64-bit integer specifying the number of 100ns periods since Jan 1, 1601
|
public BooleanPropertyValue(MAPIProperty property, long flags,
|
||||||
*/
|
byte[] data) {
|
||||||
public static class TimePropertyValue extends PropertyValue {
|
super(property, flags, data);
|
||||||
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() {
|
public Boolean getValue() {
|
||||||
long time = LittleEndian.getLong(data);
|
short val = LittleEndian.getShort(data);
|
||||||
time = (time / 10 / 1000) - OFFSET;
|
return val > 0;
|
||||||
|
}
|
||||||
|
|
||||||
Calendar timeC = LocaleUtil.getLocaleCalendar();
|
public void setValue(boolean value) {
|
||||||
timeC.setTimeInMillis(time);
|
if (data.length != 2) {
|
||||||
|
data = new byte[2];
|
||||||
|
}
|
||||||
|
if (value) {
|
||||||
|
LittleEndian.putShort(data, 0, (short) 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return timeC;
|
public static class ShortPropertyValue extends PropertyValue {
|
||||||
}
|
public ShortPropertyValue(MAPIProperty property, long flags,
|
||||||
public void setValue(Calendar value) {
|
byte[] data) {
|
||||||
if (data.length != 8) {
|
super(property, flags, data);
|
||||||
data = new byte[8];
|
}
|
||||||
}
|
|
||||||
long time = value.getTimeInMillis();
|
public Short getValue() {
|
||||||
time = (time + OFFSET) *10*1000;
|
return LittleEndian.getShort(data);
|
||||||
LittleEndian.putLong(data, 0, time);
|
}
|
||||||
}
|
|
||||||
}
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
|
||||||
*/
|
|
||||||
private PropertiesChunk recipientProperties;
|
|
||||||
|
|
||||||
public RecipientChunks(String name) {
|
public RecipientChunks(String name) {
|
||||||
recipientNumber = -1;
|
recipientNumber = -1;
|
||||||
int splitAt = name.lastIndexOf('#');
|
int splitAt = name.lastIndexOf('#');
|
||||||
if(splitAt > -1) {
|
if (splitAt > -1) {
|
||||||
String number = name.substring(splitAt+1);
|
String number = name.substring(splitAt + 1);
|
||||||
try {
|
try {
|
||||||
recipientNumber = Integer.parseInt(number, 16);
|
recipientNumber = Integer.parseInt(number, 16);
|
||||||
} catch(NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
logger.log(POILogger.ERROR, "Invalid recipient number in name " + name);
|
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.
|
* Tries to find their name, in whichever chunk holds it.
|
||||||
if(recipientSearchChunk != null) {
|
*/
|
||||||
String search = recipientSearchChunk.getAs7bitString();
|
public String getRecipientName() {
|
||||||
if(search.indexOf("SMTP:") != -1) {
|
if (recipientNameChunk != null) {
|
||||||
return search.substring(search.indexOf("SMTP:") + 5);
|
return recipientNameChunk.getValue();
|
||||||
}
|
}
|
||||||
}
|
if (recipientDisplayNameChunk != null) {
|
||||||
|
return recipientDisplayNameChunk.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
// Can't find it
|
// Can't find it
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Holds all the chunks that were found. */
|
/**
|
||||||
private List<Chunk> allChunks = new ArrayList<Chunk>();
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
public Map<MAPIProperty,List<PropertyValue>> getProperties() {
|
// This might be a real email, or might be
|
||||||
if (recipientProperties != null) {
|
// in CN=... format
|
||||||
return recipientProperties.getProperties();
|
if (recipientEmailChunk != null) {
|
||||||
}
|
String email = recipientEmailChunk.getValue();
|
||||||
else return Collections.emptyMap();
|
int cne = email.indexOf("/CN=");
|
||||||
}
|
if (cne == -1) {
|
||||||
public Chunk[] getAll() {
|
// Normal smtp address
|
||||||
return allChunks.toArray(new Chunk[allChunks.size()]);
|
return email;
|
||||||
}
|
} else {
|
||||||
public Chunk[] getChunks() {
|
// /O=..../CN=em@ail
|
||||||
return getAll();
|
return email.substring(cne + 4);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
// Might be in the name field, check there
|
||||||
* Called by the parser whenever a chunk is found.
|
if (recipientNameChunk != null) {
|
||||||
*/
|
String name = recipientNameChunk.getValue();
|
||||||
public void record(Chunk chunk) {
|
if (name.indexOf('@') > -1) {
|
||||||
if(chunk.getChunkId() == RECIPIENT_SEARCH.id) {
|
// Strip leading and trailing quotes if needed
|
||||||
// TODO - parse
|
if (name.startsWith("'") && name.endsWith("'")) {
|
||||||
recipientSearchChunk = (ByteChunk)chunk;
|
return name.substring(1, name.length() - 1);
|
||||||
}
|
}
|
||||||
else if(chunk.getChunkId() == RECIPIENT_NAME.id) {
|
return name;
|
||||||
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
|
// Check the search chunk, see if it's
|
||||||
allChunks.add(chunk);
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void chunksComplete() {
|
// Can't find it
|
||||||
if (recipientProperties != null) {
|
return null;
|
||||||
recipientProperties.matchVariableSizedPropertiesToChunks();
|
}
|
||||||
} else {
|
|
||||||
logger.log(POILogger.WARN, "Recipeints Chunk didn't contain a list of properties!");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/** Holds all the chunks that were found. */
|
||||||
* Orders by the recipient number.
|
private List<Chunk> allChunks = new ArrayList<Chunk>();
|
||||||
*/
|
|
||||||
public static class RecipientChunksSorter implements Comparator<RecipientChunks>, Serializable {
|
public Map<MAPIProperty, List<PropertyValue>> getProperties() {
|
||||||
public int compare(RecipientChunks a, RecipientChunks b) {
|
if (recipientProperties != null) {
|
||||||
if(a.recipientNumber < b.recipientNumber)
|
return recipientProperties.getProperties();
|
||||||
return -1;
|
} else
|
||||||
if(a.recipientNumber > b.recipientNumber)
|
return Collections.emptyMap();
|
||||||
return +1;
|
}
|
||||||
return 0;
|
|
||||||
}
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
@Override
|
||||||
public void readValue(InputStream stream) throws IOException {
|
public void readValue(InputStream stream) throws IOException {
|
||||||
// 8 bytes of reserved zeros
|
// 8 bytes of reserved zeros
|
||||||
LittleEndian.readLong(stream);
|
LittleEndian.readLong(stream);
|
||||||
|
|
||||||
// Now properties
|
// Now properties
|
||||||
readProperties(stream);
|
readProperties(stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeValue(OutputStream out) throws IOException {
|
public void writeValue(OutputStream out) throws IOException {
|
||||||
// 8 bytes of reserved zeros
|
// 8 bytes of reserved zeros
|
||||||
out.write(new byte[8]);
|
out.write(new byte[8]);
|
||||||
|
|
||||||
// Now properties
|
// Now properties
|
||||||
writeProperties(out);
|
writeProperties(out);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 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
|
public String toString() {
|
||||||
return new String(data, Charset.forName(encoding));
|
return this.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses as non-unicode, supposedly 7 bit CP1252 data and returns the
|
||||||
|
* string that that yields.
|
||||||
|
*/
|
||||||
|
protected static String parseAs7BitData(byte[] data) {
|
||||||
|
return parseAs7BitData(data, DEFAULT_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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
* Creates a standard, built-in type
|
||||||
*/
|
*/
|
||||||
private MAPIType(int id, String name, int length) {
|
private MAPIType(int id, String name, int length) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.length = length;
|
this.length = length;
|
||||||
builtInTypes.put(id, this);
|
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
|
* Creates a custom type
|
||||||
* -1 if it is a variable length type.
|
*/
|
||||||
*/
|
private MAPIType(int id, int length) {
|
||||||
public int getLength() {
|
this.id = id;
|
||||||
return length;
|
this.name = asCustomName(id);
|
||||||
}
|
this.length = length;
|
||||||
/**
|
customTypes.put(id, this);
|
||||||
* Is this type a fixed-length type, or a variable-length one?
|
}
|
||||||
*/
|
|
||||||
public boolean isFixedLength() {
|
|
||||||
return (length != -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getId() {
|
/**
|
||||||
return id;
|
* Returns the length, in bytes, of values of this type, or -1 if it is
|
||||||
}
|
* a variable length type.
|
||||||
public String getName() {
|
*/
|
||||||
return name;
|
public int getLength() {
|
||||||
}
|
return length;
|
||||||
|
}
|
||||||
|
|
||||||
public String toString() {
|
/**
|
||||||
return id + " / 0x" + asFileEnding() + " - " + name + " @ " + length;
|
* Is this type a fixed-length type, or a variable-length one?
|
||||||
}
|
*/
|
||||||
|
public boolean isFixedLength() {
|
||||||
|
return (length != -1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
public int getId() {
|
||||||
* Return the 4 character hex encoded version,
|
return id;
|
||||||
* as used in file endings
|
}
|
||||||
*/
|
|
||||||
public String asFileEnding() {
|
|
||||||
return Types.asFileEnding(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static MAPIType getById(int typeId) {
|
public String getName() {
|
||||||
return builtInTypes.get(typeId);
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String asFileEnding(int type) {
|
public String toString() {
|
||||||
String str = Integer.toHexString(type).toUpperCase(Locale.ROOT);
|
return id + " / 0x" + asFileEnding() + " - " + name + " @ "
|
||||||
while(str.length() < 4) {
|
+ length;
|
||||||
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...
|
* Return the 4 character hex encoded version, as used in file endings
|
||||||
if (getById(typeId) != null) {
|
*/
|
||||||
return getById(typeId);
|
public String asFileEnding() {
|
||||||
}
|
return Types.asFileEnding(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Try to get an existing definition of this
|
public static MAPIType getById(int typeId) {
|
||||||
MAPIType type = customTypes.get(typeId);
|
return builtInTypes.get(typeId);
|
||||||
|
}
|
||||||
|
|
||||||
// If none, do a thread-safe creation
|
public static String asFileEnding(int type) {
|
||||||
if (type == null) {
|
String str = Integer.toHexString(type).toUpperCase(Locale.ROOT);
|
||||||
synchronized (customTypes) {
|
while (str.length() < 4) {
|
||||||
type = customTypes.get(typeId);
|
str = "0" + str;
|
||||||
if (type == null) {
|
}
|
||||||
type = new MAPIType(typeId, -1);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user