]> source.dussan.org Git - poi.git/commitdiff
Start on the code to process properties, and wire it up. No properties reading code...
authorNick Burch <nick@apache.org>
Sun, 8 Jul 2012 18:50:11 +0000 (18:50 +0000)
committerNick Burch <nick@apache.org>
Sun, 8 Jul 2012 18:50:11 +0000 (18:50 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1358813 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/util/LittleEndian.java
src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java
src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessagePropertiesChunk.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hsmf/datatypes/NameIdChunks.java
src/scratchpad/src/org/apache/poi/hsmf/datatypes/PropertiesChunk.java
src/scratchpad/src/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hsmf/datatypes/StringChunk.java
src/scratchpad/src/org/apache/poi/hsmf/datatypes/Types.java
src/scratchpad/src/org/apache/poi/hsmf/dev/HSMFDump.java
src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java

index c07c4beb307bf8cedee31e463261f0e0ceb46fb0..6ceb12a4a1137bf1ac22627b3ba965b7d938cdb8 100644 (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
index 2f43c6bf9e10b6fd5ad0cd8935aa9b39d685dd01..a5ee6cdca5b22b2a2b9e2168406281638a6e6d3c 100644 (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;
    
diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessagePropertiesChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessagePropertiesChunk.java
new file mode 100644 (file)
index 0000000..ab9b1bf
--- /dev/null
@@ -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);
+   }
+}
index cbcce93d12fcdf4b2c3948bb0df326d8b618d452..bb78ea308e9987c8c5f85555a72cf90e2500ec7d 100644 (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>();
index 846c3d213ee6a9627e88642d848fb7793ac570b1..b83ae7eb468c268040e9a8eafe622a9fa2f8766c 100644 (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
        }
 }
diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java
new file mode 100644 (file)
index 0000000..166d38b
--- /dev/null
@@ -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);
+   }
+}
\ No newline at end of file
index f45989a7ea8784f0aef9d9e52a4822453556ac9f..2051f44c28586cca4030269c9d028aadc387f579 100644 (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;
index aa2864660ec5f245dd92ce8aa7baa9b524ec5321..a4732f081fa03a86a65b2dcae5cf5d05b34de440 100644 (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;
    }
 }
index 8091e71780bb79b2db43fc70e4cac0b6503a904e..436298de45a01141466166ceabcc09ed42d1543c 100644 (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();
       }
index b412fad05d2eb099a2ba2ed026250fb0b25adbbd..6800b0ac77d74d8dee5346bca0194ede49219daa 100644 (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);
-            }
-            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);
+          }
       }
    }
 }