}
return ( ch4 << 24 ) + ( ch3 << 16 ) + ( ch2 << 8 ) + ( ch1 << 0 );
}
+
+ /**
+ * get an unsigned int value from an InputStream
+ *
+ * @param stream
+ * the InputStream from which the int is to be read
+ * @return the unsigned int (32-bit) value
+ * @exception IOException
+ * will be propagated back to the caller
+ * @exception BufferUnderrunException
+ * if the stream cannot provide enough bytes
+ */
+ public static long readUInt( InputStream stream ) throws IOException,
+ BufferUnderrunException
+ {
+ long retNum = readInt(stream);
+ return retNum & 0x00FFFFFFFFl;
+ }
/**
* get a long value from an InputStream
new MAPIProperty(-1, Types.UNKNOWN, "Unknown", null);
// 0x8??? ones are outlook specific, and not standard MAPI
+ // TODO See http://msdn.microsoft.com/en-us/library/ee157150%28v=exchg.80%29 for some
+ // info on how we might decode them properly in the future
private static final int ID_FIRST_CUSTOM = 0x8000;
private static final int ID_LAST_CUSTOM = 0xFFFE;
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hsmf.datatypes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * A {@link PropertiesChunk} for a Message or Embedded-Message.
+ * This has a 32 byte header
+ */
+public class MessagePropertiesChunk extends PropertiesChunk {
+ private long nextRecipientId;
+ private long nextAttachmentId;
+ private long recipientCount;
+ private long attachmentCount;
+
+ public MessagePropertiesChunk() {
+ super();
+ }
+
+ public long getNextRecipientId() {
+ return nextRecipientId;
+ }
+ public long getNextAttachmentId() {
+ return nextAttachmentId;
+ }
+
+ public long getRecipientCount() {
+ return recipientCount;
+ }
+ public long getAttachmentCount() {
+ return attachmentCount;
+ }
+
+ @Override
+ public void readValue(InputStream stream) throws IOException {
+ // 8 bytes of reserved zeros
+ LittleEndian.readLong(stream);
+
+ // Nexts and counts
+ nextRecipientId = LittleEndian.readUInt(stream);
+ nextAttachmentId = LittleEndian.readUInt(stream);
+ recipientCount = LittleEndian.readUInt(stream);
+ attachmentCount = LittleEndian.readUInt(stream);
+
+ // 8 bytes of reserved zeros
+ LittleEndian.readLong(stream);
+
+ // Now properties
+ readProperties(stream);
+ }
+
+ @Override
+ public void writeValue(OutputStream out) throws IOException {
+ // 8 bytes of reserved zeros
+ out.write(new byte[8]);
+
+ // Nexts and counts
+ LittleEndian.putUInt(nextRecipientId, out);
+ LittleEndian.putUInt(nextAttachmentId, out);
+ LittleEndian.putUInt(recipientCount, out);
+ LittleEndian.putUInt(attachmentCount, out);
+
+ // 8 bytes of reserved zeros
+ out.write(new byte[8]);
+
+ // Now properties
+ writeProperties(out);
+ }
+}
* NameID part of an outlook file
*/
public final class NameIdChunks implements ChunkGroup {
- public static final String PREFIX = "__nameid_version1.0";
+ public static final String NAME = "__nameid_version1.0";
/** Holds all the chunks that were found. */
private List<Chunk> allChunks = new ArrayList<Chunk>();
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
/**
* A Chunk which holds fixed-length properties, and pointer
- * to the variable length ones (which get their own chunk)
+ * to the variable length ones (which get their own chunk).
+ * There are two kinds of PropertiesChunks, which differ only in
+ * their headers.
*/
-public class PropertiesChunk extends Chunk {
- public static final String PREFIX = "__properties_version1.0";
+public abstract class PropertiesChunk extends Chunk {
+ public static final String NAME = "__properties_version1.0";
+
+ /**
+ * Holds properties, indexed by type. Properties can be multi-valued
+ */
+ private Map<MAPIProperty, List<PropertyValue>> properties =
+ new HashMap<MAPIProperty, List<PropertyValue>>();
/**
* Creates a Properties Chunk.
*/
- public PropertiesChunk() {
- super(PREFIX, -1, Types.UNKNOWN);
+ protected PropertiesChunk() {
+ super(NAME, -1, Types.UNKNOWN);
}
@Override
public String getEntryName() {
- return PREFIX;
+ return NAME;
}
+
+ /**
+ * Returns all the properties in the chunk
+ */
+ public Map<MAPIProperty, List<PropertyValue>> getProperties() {
+ return properties;
+ }
+
+ /**
+ * Returns all values for the given property, of null if none exist
+ */
+ public List<PropertyValue> getValues(MAPIProperty property) {
+ return properties.get(property);
+ }
+
+ /**
+ * Returns the (first/only) value for the given property, or
+ * null if none exist
+ */
+ public PropertyValue getValue(MAPIProperty property) {
+ List<PropertyValue> values = properties.get(property);
+ if (values != null && values.size() > 0) {
+ return values.get(0);
+ }
+ return null;
+ }
- public void readValue(InputStream value) throws IOException {
+ protected void readProperties(InputStream value) throws IOException {
// TODO
}
- public void writeValue(OutputStream out) throws IOException {
+ protected void writeProperties(OutputStream out) throws IOException {
// TODO
}
}
--- /dev/null
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.hsmf.datatypes;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * A {@link PropertiesChunk} for a Storage Properties, such as
+ * Attachments and Recipients.
+ * This only has a 8 byte header
+ */
+public class StoragePropertiesChunk extends PropertiesChunk {
+ public StoragePropertiesChunk() {
+ super();
+ }
+
+ @Override
+ public void readValue(InputStream stream) throws IOException {
+ // 8 bytes of reserved zeros
+ LittleEndian.readLong(stream);
+
+ // Now properties
+ readProperties(stream);
+ }
+
+ @Override
+ public void writeValue(OutputStream out) throws IOException {
+ // 8 bytes of reserved zeros
+ out.write(new byte[8]);
+
+ // Now properties
+ writeProperties(out);
+ }
+}
\ No newline at end of file
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
-import org.apache.poi.hsmf.datatypes.Types;
import org.apache.poi.hsmf.datatypes.Types.MAPIType;
import org.apache.poi.util.IOUtils;
import org.apache.poi.util.StringUtil;
*/
public final class Types {
private static Map<Integer, MAPIType> builtInTypes = new HashMap<Integer, MAPIType>();
+ private static Map<Integer, MAPIType> customTypes = new HashMap<Integer, Types.MAPIType>();
/** Unspecified */
public static final MAPIType UNSPECIFIED = new MAPIType(0x0000, "Unspecified", -1);
this.id = id;
this.name = asCustomName(id);
this.length = length;
+ customTypes.put(id, this);
}
/**
}
public static MAPIType createCustom(int typeId) {
- return new MAPIType(typeId, -1);
+ // 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;
}
}
import org.apache.poi.hsmf.datatypes.Chunk;
import org.apache.poi.hsmf.datatypes.ChunkGroup;
import org.apache.poi.hsmf.datatypes.MAPIProperty;
+import org.apache.poi.hsmf.datatypes.PropertiesChunk;
+import org.apache.poi.hsmf.datatypes.PropertyValue;
import org.apache.poi.hsmf.parsers.POIFSChunkParser;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
for(Chunk chunk : chunks.getChunks()) {
MAPIProperty attr = MAPIProperty.get(chunk.getChunkId());
- String idName = attr.id + " - " + attr.name;
- if(attr == MAPIProperty.UNKNOWN) {
- idName = chunk.getChunkId() + " - (unknown)";
+ if (chunk instanceof PropertiesChunk) {
+ PropertiesChunk props = (PropertiesChunk)chunk;
+ System.out.println(
+ " Properties - " + props.getProperties().size() + ":"
+ );
+
+ for (MAPIProperty prop : props.getProperties().keySet()) {
+ System.out.println(
+ " * " + prop
+ );
+ for (PropertyValue v : props.getValues(prop)) {
+ System.out.println(
+ " = " + v.toString()
+ );
+ }
+ }
+ } else {
+ String idName = attr.id + " - " + attr.name;
+ if(attr == MAPIProperty.UNKNOWN) {
+ idName = chunk.getChunkId() + " - (unknown)";
+ }
+
+ System.out.println(
+ " " + idName + " - " + chunk.getType().getName()
+ );
+ System.out.println(
+ " " + chunk.toString()
+ );
}
-
- System.out.println(
- " " + idName + " - " + chunk.getType().getName()
- );
- System.out.println(
- " " + chunk.toString()
- );
}
System.out.println();
}
import org.apache.poi.hsmf.datatypes.Chunks;
import org.apache.poi.hsmf.datatypes.DirectoryChunk;
import org.apache.poi.hsmf.datatypes.MAPIProperty;
+import org.apache.poi.hsmf.datatypes.MessagePropertiesChunk;
import org.apache.poi.hsmf.datatypes.MessageSubmissionChunk;
import org.apache.poi.hsmf.datatypes.NameIdChunks;
+import org.apache.poi.hsmf.datatypes.PropertiesChunk;
import org.apache.poi.hsmf.datatypes.RecipientChunks;
+import org.apache.poi.hsmf.datatypes.StoragePropertiesChunk;
import org.apache.poi.hsmf.datatypes.StringChunk;
import org.apache.poi.hsmf.datatypes.Types;
import org.apache.poi.hsmf.datatypes.Types.MAPIType;
if(dir.getName().startsWith(AttachmentChunks.PREFIX)) {
group = new AttachmentChunks(dir.getName());
}
- if(dir.getName().startsWith(NameIdChunks.PREFIX)) {
+ if(dir.getName().startsWith(NameIdChunks.NAME)) {
group = new NameIdChunks();
}
if(dir.getName().startsWith(RecipientChunks.PREFIX)) {
*/
protected static void process(Entry entry, ChunkGroup grouping) {
String entryName = entry.getName();
+ Chunk chunk = null;
- if(entryName.length() < 9) {
- // Name in the wrong format
- return;
- }
- if(entryName.indexOf('_') == -1) {
- // Name in the wrong format
- return;
- }
-
- // Split it into its parts
- int splitAt = entryName.lastIndexOf('_');
- String namePrefix = entryName.substring(0, splitAt+1);
- String ids = entryName.substring(splitAt+1);
-
- // Make sure we got what we expected, should be of
- // the form __<name>_<id><type>
- if(namePrefix.equals("Olk10SideProps") ||
- namePrefix.equals("Olk10SideProps_")) {
- // This is some odd Outlook 2002 thing, skip
- return;
- } else if(splitAt <= entryName.length()-8) {
- // In the right form for a normal chunk
- // We'll process this further in a little bit
+ // Is it a properties chunk? (They have special names)
+ if (entryName.equals(PropertiesChunk.NAME)) {
+ if (grouping instanceof Chunks) {
+ // These should be the properties for the message itself
+ chunk = new MessagePropertiesChunk();
+ } else {
+ // Will be properties on an attachment or recipient
+ chunk = new StoragePropertiesChunk();
+ }
} else {
- // Underscores not the right place, something's wrong
- throw new IllegalArgumentException("Invalid chunk name " + entryName);
- }
-
- // Now try to turn it into id + type
- try {
- int chunkId = Integer.parseInt(ids.substring(0, 4), 16);
- int typeId = Integer.parseInt(ids.substring(4, 8), 16);
-
- MAPIType type = Types.getById(typeId);
- if (type == null) {
- type = Types.createCustom(typeId);
+ // Check it's a regular chunk
+ if(entryName.length() < 9) {
+ // Name in the wrong format
+ return;
+ }
+ if(entryName.indexOf('_') == -1) {
+ // Name in the wrong format
+ return;
}
- Chunk chunk = null;
+ // Split it into its parts
+ int splitAt = entryName.lastIndexOf('_');
+ String namePrefix = entryName.substring(0, splitAt+1);
+ String ids = entryName.substring(splitAt+1);
- // Special cases based on the ID
- if(chunkId == MAPIProperty.MESSAGE_SUBMISSION_ID.id) {
- chunk = new MessageSubmissionChunk(namePrefix, chunkId, type);
- }
- else {
- // Nothing special about this ID
- // So, do the usual thing which is by type
- if (type == Types.BINARY) {
- chunk = new ByteChunk(namePrefix, chunkId, type);
- }
- else if (type == Types.DIRECTORY) {
- if(entry instanceof DirectoryNode) {
- chunk = new DirectoryChunk((DirectoryNode)entry, namePrefix, chunkId, type);
- }
+ // Make sure we got what we expected, should be of
+ // the form __<name>_<id><type>
+ if(namePrefix.equals("Olk10SideProps") ||
+ namePrefix.equals("Olk10SideProps_")) {
+ // This is some odd Outlook 2002 thing, skip
+ return;
+ } else if(splitAt <= entryName.length()-8) {
+ // In the right form for a normal chunk
+ // We'll process this further in a little bit
+ } else {
+ // Underscores not the right place, something's wrong
+ throw new IllegalArgumentException("Invalid chunk name " + entryName);
+ }
+
+ // Now try to turn it into id + type
+ try {
+ int chunkId = Integer.parseInt(ids.substring(0, 4), 16);
+ int typeId = Integer.parseInt(ids.substring(4, 8), 16);
+
+ MAPIType type = Types.getById(typeId);
+ if (type == null) {
+ type = Types.createCustom(typeId);
}
- else if (type == Types.ASCII_STRING ||
- type == Types.UNICODE_STRING) {
- chunk = new StringChunk(namePrefix, chunkId, type);
+
+ // Special cases based on the ID
+ if(chunkId == MAPIProperty.MESSAGE_SUBMISSION_ID.id) {
+ chunk = new MessageSubmissionChunk(namePrefix, chunkId, type);
}
else {
- // Type of an unsupported type! Skipping...
+ // Nothing special about this ID
+ // So, do the usual thing which is by type
+ if (type == Types.BINARY) {
+ chunk = new ByteChunk(namePrefix, chunkId, type);
+ }
+ else if (type == Types.DIRECTORY) {
+ if(entry instanceof DirectoryNode) {
+ chunk = new DirectoryChunk((DirectoryNode)entry, namePrefix, chunkId, type);
+ }
+ }
+ else if (type == Types.ASCII_STRING ||
+ type == Types.UNICODE_STRING) {
+ chunk = new StringChunk(namePrefix, chunkId, type);
+ }
+ else {
+ // Type of an unsupported type! Skipping...
+ }
}
+ } catch(NumberFormatException e) {
+ // Name in the wrong format
+ return;
}
+ }
- if(chunk != null) {
- if(entry instanceof DocumentNode) {
- try {
- DocumentInputStream inp = new DocumentInputStream((DocumentNode)entry);
- chunk.readValue(inp);
- grouping.record(chunk);
- } catch(IOException e) {
- System.err.println("Error reading from part " + entry.getName() + " - " + e.toString());
- }
- } else {
+ if(chunk != null) {
+ if(entry instanceof DocumentNode) {
+ try {
+ DocumentInputStream inp = new DocumentInputStream((DocumentNode)entry);
+ chunk.readValue(inp);
grouping.record(chunk);
+ } catch(IOException e) {
+ System.err.println("Error reading from part " + entry.getName() + " - " + e.toString());
}
- }
- } catch(NumberFormatException e) {
- // Name in the wrong format
- return;
+ } else {
+ grouping.record(chunk);
+ }
}
}
}