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 9a5378ff55
commit eb368e205d
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 );
}
/**
* 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

View File

@ -1026,6 +1026,8 @@ public class MAPIProperty {
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;

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
*/
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>();

View File

@ -20,31 +20,68 @@ package org.apache.poi.hsmf.datatypes;
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
}
}

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.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;

View File

@ -26,6 +26,7 @@ import java.util.Map;
*/
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);
@ -95,6 +96,7 @@ public final class Types {
this.id = id;
this.name = asCustomName(id);
this.length = length;
customTypes.put(id, this);
}
/**
@ -150,6 +152,24 @@ public final class Types {
}
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.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;
@ -42,17 +44,35 @@ public class HSMFDump {
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();
}

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.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;
@ -66,7 +69,7 @@ public final class POIFSChunkParser {
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)) {
@ -110,87 +113,98 @@ public final class POIFSChunkParser {
*/
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);
// 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.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);
// 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);
}
}
}
}