Start on the code to process properties, and wire it up. No properties reading code exists yet

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1358813 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2012-07-08 18:50:11 +00:00
parent 4088217930
commit 8789e74439
10 changed files with 342 additions and 90 deletions

View File

@ -724,6 +724,24 @@ public class LittleEndian implements LittleEndianConsts
} }
return ( ch4 << 24 ) + ( ch3 << 16 ) + ( ch2 << 8 ) + ( ch1 << 0 ); 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 * get a long value from an InputStream

View File

@ -1026,6 +1026,8 @@ public class MAPIProperty {
new MAPIProperty(-1, Types.UNKNOWN, "Unknown", null); new MAPIProperty(-1, Types.UNKNOWN, "Unknown", null);
// 0x8??? ones are outlook specific, and not standard MAPI // 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_FIRST_CUSTOM = 0x8000;
private static final int ID_LAST_CUSTOM = 0xFFFE; private static final int ID_LAST_CUSTOM = 0xFFFE;

View File

@ -0,0 +1,89 @@
/* ====================================================================
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);
}
}

View File

@ -26,7 +26,7 @@ import java.util.List;
* 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 PREFIX = "__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>();

View File

@ -20,31 +20,68 @@ package org.apache.poi.hsmf.datatypes;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; 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 * 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 abstract class PropertiesChunk extends Chunk {
public static final String PREFIX = "__properties_version1.0"; 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. * Creates a Properties Chunk.
*/ */
public PropertiesChunk() { protected PropertiesChunk() {
super(PREFIX, -1, Types.UNKNOWN); super(NAME, -1, Types.UNKNOWN);
} }
@Override @Override
public String getEntryName() { 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 // TODO
} }
public void writeValue(OutputStream out) throws IOException { protected void writeProperties(OutputStream out) throws IOException {
// TODO // TODO
} }
} }

View File

@ -0,0 +1,53 @@
/* ====================================================================
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);
}
}

View File

@ -22,7 +22,6 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import org.apache.poi.hsmf.datatypes.Types;
import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.hsmf.datatypes.Types.MAPIType;
import org.apache.poi.util.IOUtils; import org.apache.poi.util.IOUtils;
import org.apache.poi.util.StringUtil; import org.apache.poi.util.StringUtil;

View File

@ -26,6 +26,7 @@ import java.util.Map;
*/ */
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>();
/** Unspecified */ /** Unspecified */
public static final MAPIType UNSPECIFIED = new MAPIType(0x0000, "Unspecified", -1); public static final MAPIType UNSPECIFIED = new MAPIType(0x0000, "Unspecified", -1);
@ -95,6 +96,7 @@ public final class Types {
this.id = id; this.id = id;
this.name = asCustomName(id); this.name = asCustomName(id);
this.length = length; this.length = length;
customTypes.put(id, this);
} }
/** /**
@ -150,6 +152,24 @@ public final class Types {
} }
public static MAPIType createCustom(int typeId) { 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;
} }
} }

View File

@ -23,6 +23,8 @@ import java.io.IOException;
import org.apache.poi.hsmf.datatypes.Chunk; import org.apache.poi.hsmf.datatypes.Chunk;
import org.apache.poi.hsmf.datatypes.ChunkGroup; import org.apache.poi.hsmf.datatypes.ChunkGroup;
import org.apache.poi.hsmf.datatypes.MAPIProperty; 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.hsmf.parsers.POIFSChunkParser;
import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.poifs.filesystem.POIFSFileSystem;
@ -42,17 +44,35 @@ public class HSMFDump {
for(Chunk chunk : chunks.getChunks()) { for(Chunk chunk : chunks.getChunks()) {
MAPIProperty attr = MAPIProperty.get(chunk.getChunkId()); MAPIProperty attr = MAPIProperty.get(chunk.getChunkId());
String idName = attr.id + " - " + attr.name; if (chunk instanceof PropertiesChunk) {
if(attr == MAPIProperty.UNKNOWN) { PropertiesChunk props = (PropertiesChunk)chunk;
idName = chunk.getChunkId() + " - (unknown)"; 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(); System.out.println();
} }

View File

@ -27,9 +27,12 @@ import org.apache.poi.hsmf.datatypes.ChunkGroup;
import org.apache.poi.hsmf.datatypes.Chunks; import org.apache.poi.hsmf.datatypes.Chunks;
import org.apache.poi.hsmf.datatypes.DirectoryChunk; import org.apache.poi.hsmf.datatypes.DirectoryChunk;
import org.apache.poi.hsmf.datatypes.MAPIProperty; 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.MessageSubmissionChunk;
import org.apache.poi.hsmf.datatypes.NameIdChunks; 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.RecipientChunks;
import org.apache.poi.hsmf.datatypes.StoragePropertiesChunk;
import org.apache.poi.hsmf.datatypes.StringChunk; import org.apache.poi.hsmf.datatypes.StringChunk;
import org.apache.poi.hsmf.datatypes.Types; import org.apache.poi.hsmf.datatypes.Types;
import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.hsmf.datatypes.Types.MAPIType;
@ -66,7 +69,7 @@ public final class POIFSChunkParser {
if(dir.getName().startsWith(AttachmentChunks.PREFIX)) { if(dir.getName().startsWith(AttachmentChunks.PREFIX)) {
group = new AttachmentChunks(dir.getName()); group = new AttachmentChunks(dir.getName());
} }
if(dir.getName().startsWith(NameIdChunks.PREFIX)) { if(dir.getName().startsWith(NameIdChunks.NAME)) {
group = new NameIdChunks(); group = new NameIdChunks();
} }
if(dir.getName().startsWith(RecipientChunks.PREFIX)) { if(dir.getName().startsWith(RecipientChunks.PREFIX)) {
@ -110,87 +113,98 @@ public final class POIFSChunkParser {
*/ */
protected static void process(Entry entry, ChunkGroup grouping) { protected static void process(Entry entry, ChunkGroup grouping) {
String entryName = entry.getName(); String entryName = entry.getName();
Chunk chunk = null;
if(entryName.length() < 9) { // Is it a properties chunk? (They have special names)
// Name in the wrong format if (entryName.equals(PropertiesChunk.NAME)) {
return; if (grouping instanceof Chunks) {
} // These should be the properties for the message itself
if(entryName.indexOf('_') == -1) { chunk = new MessagePropertiesChunk();
// Name in the wrong format } else {
return; // Will be properties on an attachment or recipient
} chunk = new StoragePropertiesChunk();
}
// 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
} else { } else {
// Underscores not the right place, something's wrong // Check it's a regular chunk
throw new IllegalArgumentException("Invalid chunk name " + entryName); if(entryName.length() < 9) {
} // Name in the wrong format
return;
// Now try to turn it into id + type }
try { if(entryName.indexOf('_') == -1) {
int chunkId = Integer.parseInt(ids.substring(0, 4), 16); // Name in the wrong format
int typeId = Integer.parseInt(ids.substring(4, 8), 16); return;
MAPIType type = Types.getById(typeId);
if (type == null) {
type = Types.createCustom(typeId);
} }
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 // Make sure we got what we expected, should be of
if(chunkId == MAPIProperty.MESSAGE_SUBMISSION_ID.id) { // the form __<name>_<id><type>
chunk = new MessageSubmissionChunk(namePrefix, chunkId, type); if(namePrefix.equals("Olk10SideProps") ||
} namePrefix.equals("Olk10SideProps_")) {
else { // This is some odd Outlook 2002 thing, skip
// Nothing special about this ID return;
// So, do the usual thing which is by type } else if(splitAt <= entryName.length()-8) {
if (type == Types.BINARY) { // In the right form for a normal chunk
chunk = new ByteChunk(namePrefix, chunkId, type); // 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.DIRECTORY) {
if(entry instanceof DirectoryNode) { // Special cases based on the ID
chunk = new DirectoryChunk((DirectoryNode)entry, namePrefix, chunkId, type); if(chunkId == MAPIProperty.MESSAGE_SUBMISSION_ID.id) {
} chunk = new MessageSubmissionChunk(namePrefix, chunkId, type);
}
else if (type == Types.ASCII_STRING ||
type == Types.UNICODE_STRING) {
chunk = new StringChunk(namePrefix, chunkId, type);
} }
else { 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(chunk != null) {
if(entry instanceof DocumentNode) { if(entry instanceof DocumentNode) {
try { try {
DocumentInputStream inp = new DocumentInputStream((DocumentNode)entry); DocumentInputStream inp = new DocumentInputStream((DocumentNode)entry);
chunk.readValue(inp); chunk.readValue(inp);
grouping.record(chunk);
} catch(IOException e) {
System.err.println("Error reading from part " + entry.getName() + " - " + e.toString());
}
} else {
grouping.record(chunk); grouping.record(chunk);
} catch(IOException e) {
System.err.println("Error reading from part " + entry.getName() + " - " + e.toString());
} }
} } else {
} catch(NumberFormatException e) { grouping.record(chunk);
// Name in the wrong format }
return;
} }
} }
} }