]> source.dussan.org Git - poi.git/commitdiff
Improve HMEF handling of typed attributes (Strings and Dates), for both TNEF and...
authorNick Burch <nick@apache.org>
Thu, 3 Mar 2011 12:41:39 +0000 (12:41 +0000)
committerNick Burch <nick@apache.org>
Thu, 3 Mar 2011 12:41:39 +0000 (12:41 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1076603 13f79535-47bb-0310-9956-ffa450edef68

src/scratchpad/src/org/apache/poi/hmef/Attachment.java
src/scratchpad/src/org/apache/poi/hmef/HMEFMessage.java
src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIAttribute.java
src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIDateAttribute.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIStringAttribute.java
src/scratchpad/src/org/apache/poi/hmef/attribute/TNEFAttribute.java
src/scratchpad/src/org/apache/poi/hmef/attribute/TNEFDateAttribute.java [new file with mode: 0644]
src/scratchpad/src/org/apache/poi/hmef/attribute/TNEFStringAttribute.java
src/scratchpad/src/org/apache/poi/hmef/dev/HMEFDumper.java
src/scratchpad/testcases/org/apache/poi/hmef/TestAttachments.java
src/testcases/org/apache/poi/POIDataSamples.java

index 1d74ccc749d1ba61ec0b2ad7db5dfe3774ad4ce1..e961481a1e6e4b00ae10d8f90928542d9f9cf1b2 100644 (file)
 package org.apache.poi.hmef;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 
 import org.apache.poi.hmef.attribute.MAPIAttribute;
+import org.apache.poi.hmef.attribute.MAPIStringAttribute;
 import org.apache.poi.hmef.attribute.TNEFAttribute;
+import org.apache.poi.hmef.attribute.TNEFDateAttribute;
 import org.apache.poi.hmef.attribute.TNEFMAPIAttribute;
 import org.apache.poi.hmef.attribute.TNEFProperty;
+import org.apache.poi.hmef.attribute.TNEFStringAttribute;
 import org.apache.poi.hsmf.datatypes.MAPIProperty;
 
 
@@ -87,8 +91,68 @@ public final class Attachment {
       return mapiAttributes;
    }
    
+   
+   /**
+    * Return the string value of the mapi property, or null
+    *  if it isn't set
+    */
+   private String getString(MAPIProperty id) {
+      return MAPIStringAttribute.getAsString( getMessageMAPIAttribute(id) );
+   }
+   /**
+    * Returns the string value of the TNEF property, or
+    *  null if it isn't set
+    */
+   private String getString(TNEFProperty id) {
+      return TNEFStringAttribute.getAsString( getMessageAttribute(id) );
+   }
+   
+   /**
+    * Returns the short filename
+    */
    public String getFilename() {
-      TNEFAttribute attr = null;
-      return null;
+      return getString(TNEFProperty.ID_ATTACHTITLE);
+   }
+   /**
+    * Returns the long filename
+    */
+   public String getLongFilename() {
+      return getString(MAPIProperty.ATTACH_LONG_FILENAME);
+   }
+   /**
+    * Returns the file extension
+    */
+   public String getExtension() {
+      return getString(MAPIProperty.ATTACH_EXTENSION);
+   }
+   
+   /**
+    * Return when the file was last modified, if known.
+    */
+   public Date getModifiedDate() {
+      return TNEFDateAttribute.getAsDate(
+            getMessageAttribute(TNEFProperty.ID_ATTACHMODIFYDATE)
+      );
+   }
+   
+   /**
+    * Returns the contents of the attachment.
+    */
+   public byte[] getContents() {
+      TNEFAttribute contents = getMessageAttribute(TNEFProperty.ID_ATTACHDATA);
+      if(contents == null) {
+         throw new IllegalArgumentException("Attachment corrupt - no Data section");
+      }
+      return contents.getData();
+   }
+   
+   /**
+    * Returns the Meta File rendered representation
+    *  of the attachment, or null if not set.
+    */
+   public byte[] getRenderedMetaFile() {
+      TNEFAttribute meta = getMessageAttribute(TNEFProperty.ID_ATTACHMETAFILE);
+      if(meta == null) return null;
+      return meta.getData();
    }
 }
index c23718ee68f66c9cc6bc2608a778be6b7b8000ac..7da39f9c6c8004a8b7c58cf7f7839547e831f459 100644 (file)
@@ -155,19 +155,7 @@ public final class HMEFMessage {
     *  if it isn't set
     */
    private String getString(MAPIProperty id) {
-      MAPIAttribute attr = getMessageMAPIAttribute(id);
-      if(id == null) {
-         return null;
-      }
-      if(attr instanceof MAPIStringAttribute) {
-         return ((MAPIStringAttribute)attr).getDataString();
-      }
-      if(attr instanceof MAPIRtfAttribute) {
-         return ((MAPIRtfAttribute)attr).getDataString();
-      }
-      
-      System.err.println("Warning, no string property found: " + attr.toString());
-      return null;
+      return MAPIStringAttribute.getAsString( getMessageMAPIAttribute(id) );
    }
    
    /**
index 1ebd1211443c761453c2f96d591e6aaa9499fe34..31c3cbb88513fd1a5930983ece276f8988e7b83b 100644 (file)
@@ -165,6 +165,8 @@ public class MAPIAttribute {
             MAPIAttribute attr;
             if(type == Types.UNICODE_STRING || type == Types.ASCII_STRING) {
                attr = new MAPIStringAttribute(prop, type, data);
+            } else if(type == Types.APP_TIME || type == Types.TIME) {
+               attr = new MAPIDateAttribute(prop, type, data);
             } else if(id == MAPIProperty.RTF_COMPRESSED.id) {
                attr = new MAPIRtfAttribute(prop, type, data);
             } else {
diff --git a/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIDateAttribute.java b/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIDateAttribute.java
new file mode 100644 (file)
index 0000000..bf78636
--- /dev/null
@@ -0,0 +1,70 @@
+/* ====================================================================
+   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.hmef.attribute;
+
+import java.util.Date;
+
+import org.apache.poi.hmef.Attachment;
+import org.apache.poi.hmef.HMEFMessage;
+import org.apache.poi.hpsf.Util;
+import org.apache.poi.hsmf.datatypes.MAPIProperty;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * A pure-MAPI attribute holding a Date, which applies 
+ *  to a {@link HMEFMessage} or one of its {@link Attachment}s.
+ */
+public final class MAPIDateAttribute extends MAPIAttribute {
+   private Date data;
+   
+   /**
+    * Constructs a single new date attribute from the id, type,
+    *  and the contents of the stream
+    */
+   protected MAPIDateAttribute(MAPIProperty property, int type, byte[] data) {
+      super(property, type, data);
+      
+      // The value is a 64 bit Windows Filetime
+      this.data = Util.filetimeToDate(
+            LittleEndian.getLong(data, 0)
+      );
+   }
+
+   public Date getDate() {
+      return this.data;
+   }
+   
+   public String toString() {
+      return getProperty().toString() + " " + data.toString();
+   }
+   
+   /**
+    * Returns the Date of a Attribute, converting as appropriate
+    */
+   public static Date getAsDate(MAPIAttribute attr) {
+      if(attr == null) {
+         return null;
+      }
+      if(attr instanceof MAPIDateAttribute) {
+         return ((MAPIDateAttribute)attr).getDate();
+      }
+      
+      System.err.println("Warning, non date property found: " + attr.toString());
+      return null;
+  }
+}
index e7550de736a524b3c424f5c1c0d9d3d9e128a125..d53e59c7d51e66acefd07b3265b82398b0877d71 100644 (file)
@@ -63,4 +63,22 @@ public final class MAPIStringAttribute extends MAPIAttribute {
    public String toString() {
       return getProperty().toString() + " " + data;
    }
+   
+   /**
+    * Returns the string of a Attribute, converting as appropriate
+    */
+   public static String getAsString(MAPIAttribute attr) {
+      if(attr == null) {
+         return null;
+      }
+      if(attr instanceof MAPIStringAttribute) {
+         return ((MAPIStringAttribute)attr).getDataString();
+      }
+      if(attr instanceof MAPIRtfAttribute) {
+         return ((MAPIRtfAttribute)attr).getDataString();
+      }
+      
+      System.err.println("Warning, non string property found: " + attr.toString());
+      return null;
+  }
 }
index b2152a5e346467859053581fcc2fa433ebc7419b..af0ac3dcbafa9470b769fa112ecafe979c2abf91 100644 (file)
@@ -70,6 +70,9 @@ public class TNEFAttribute {
            type == TNEFProperty.TYPE_TEXT) {
          return new TNEFStringAttribute(id, type, inp);
       }
+      if(type == TNEFProperty.TYPE_DATE) {
+         return new TNEFDateAttribute(id, type, inp);
+      }
       return new TNEFAttribute(id, type, inp); 
    }
 
diff --git a/src/scratchpad/src/org/apache/poi/hmef/attribute/TNEFDateAttribute.java b/src/scratchpad/src/org/apache/poi/hmef/attribute/TNEFDateAttribute.java
new file mode 100644 (file)
index 0000000..7079389
--- /dev/null
@@ -0,0 +1,91 @@
+/* ====================================================================
+   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.hmef.attribute;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.apache.poi.hmef.Attachment;
+import org.apache.poi.hmef.HMEFMessage;
+import org.apache.poi.hpsf.Util;
+import org.apache.poi.util.LittleEndian;
+
+/**
+ * A Date attribute which applies to a {@link HMEFMessage}
+ *  or one of its {@link Attachment}s.
+ */
+public final class TNEFDateAttribute extends TNEFAttribute {
+   private Date data;
+   
+   /**
+    * Constructs a single new date attribute from the id, type,
+    *  and the contents of the stream
+    */
+   protected TNEFDateAttribute(int id, int type, InputStream inp) throws IOException {
+      super(id, type, inp);
+      
+      byte[] data = getData();
+      if(data.length == 8) {
+         // The value is a 64 bit Windows Filetime
+         this.data = Util.filetimeToDate(
+               LittleEndian.getLong(getData(), 0)
+         );
+      } else if(data.length == 14) {
+         // It's the 7 date fields. We think it's in UTC...
+         Calendar c = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
+         c.set(Calendar.YEAR, LittleEndian.getUShort(data, 0));
+         c.set(Calendar.MONTH, LittleEndian.getUShort(data, 2) - 1); // Java months are 0 based!
+         c.set(Calendar.DAY_OF_MONTH, LittleEndian.getUShort(data, 4));
+         c.set(Calendar.HOUR_OF_DAY, LittleEndian.getUShort(data, 6));
+         c.set(Calendar.MINUTE, LittleEndian.getUShort(data, 8));
+         c.set(Calendar.SECOND, LittleEndian.getUShort(data, 10));
+         // The 7th field is day of week, which we don't require
+         c.set(Calendar.MILLISECOND, 0); // Not set in the file
+         this.data = c.getTime();
+      } else {
+         throw new IllegalArgumentException("Invalid date, found " + data.length + " bytes");
+      }
+   }
+
+   public Date getDate() {
+      return this.data;
+   }
+   
+   public String toString() {
+      return "Attribute " + getProperty().toString() + ", type=" + getType() + 
+             ", date=" + data.toString(); 
+   }
+   
+   /**
+    * Returns the Date of a Attribute, converting as appropriate
+    */
+   public static Date getAsDate(TNEFAttribute attr) {
+      if(attr == null) {
+         return null;
+      }
+      if(attr instanceof TNEFDateAttribute) {
+         return ((TNEFDateAttribute)attr).getDate();
+      }
+      
+      System.err.println("Warning, non date property found: " + attr.toString());
+      return null;
+  }
+}
index 6063c5e2c0f7f5e2d3205b31b0e68591ed428e5b..983d3f9a23c45058cd7efe24fc1e601601ffee88 100644 (file)
@@ -25,31 +25,57 @@ import org.apache.poi.hmef.HMEFMessage;
 import org.apache.poi.util.StringUtil;
 
 /**
- * An String attribute which applies to a {@link HMEFMessage}
+ * A String attribute which applies to a {@link HMEFMessage}
  *  or one of its {@link Attachment}s.
  */
 public final class TNEFStringAttribute extends TNEFAttribute {
+   private String data;
+   
    /**
     * Constructs a single new string attribute from the id, type,
     *  and the contents of the stream
     */
    protected TNEFStringAttribute(int id, int type, InputStream inp) throws IOException {
       super(id, type, inp);
-   }
-
-   public String getString() {
+      
+      String tmpData = null;
       byte[] data = getData();
-      // TODO Verify if these are the right way around
       if(getType() == TNEFProperty.TYPE_TEXT) {
-         return StringUtil.getFromUnicodeLE(data);
+         tmpData = StringUtil.getFromUnicodeLE(data);
+      } else {
+         tmpData = StringUtil.getFromCompressedUnicode(
+              data, 0, data.length
+         );
       }
-      return StringUtil.getFromCompressedUnicode(
-            data, 0, data.length
-      );
+      
+      // Strip off the null terminator if present
+      if(tmpData.endsWith("\0")) {
+         tmpData = tmpData.substring(0, tmpData.length()-1);
+      }
+      this.data = tmpData;
+   }
+
+   public String getString() {
+      return this.data;
    }
    
    public String toString() {
       return "Attribute " + getProperty().toString() + ", type=" + getType() + 
              ", data=" + getString(); 
    }
+   
+   /**
+    * Returns the string of a Attribute, converting as appropriate
+    */
+   public static String getAsString(TNEFAttribute attr) {
+      if(attr == null) {
+         return null;
+      }
+      if(attr instanceof TNEFStringAttribute) {
+         return ((TNEFStringAttribute)attr).getString();
+      }
+      
+      System.err.println("Warning, non string property found: " + attr.toString());
+      return null;
+  }
 }
index e1ad196ee8d515241abb925e533db8300fd79ad8..7addb6b84e6480ce194eaffbccebd642107e76fd 100644 (file)
@@ -25,7 +25,9 @@ import java.util.List;
 import org.apache.poi.hmef.HMEFMessage;
 import org.apache.poi.hmef.attribute.TNEFAttribute;
 import org.apache.poi.hmef.attribute.MAPIAttribute;
+import org.apache.poi.hmef.attribute.TNEFDateAttribute;
 import org.apache.poi.hmef.attribute.TNEFProperty;
+import org.apache.poi.hmef.attribute.TNEFStringAttribute;
 import org.apache.poi.util.HexDump;
 import org.apache.poi.util.LittleEndian;
 
@@ -108,6 +110,14 @@ public final class HMEFDumper {
          
          // Print the contents
          String indent = "  ";
+         
+         if(attr instanceof TNEFStringAttribute) {
+            System.out.println(indent + indent + indent + ((TNEFStringAttribute)attr).getString());
+         }
+         if(attr instanceof TNEFDateAttribute) {
+            System.out.println(indent + indent + indent + ((TNEFDateAttribute)attr).getDate());
+         }
+         
          System.out.println(indent + "Data of length " + attr.getData().length);
          if(attr.getData().length > 0) {
             int len = attr.getData().length;
index f5ddb8aeb0b21b95fdca2248adae6c09e9b10587..13a82024a0a6c5a3158a5ba8ba7ce019864ed066 100644 (file)
 
 package org.apache.poi.hmef;
 
+import java.io.IOException;
+import java.text.DateFormat;
+import java.util.List;
+import java.util.Locale;
+
 import junit.framework.TestCase;
 
 import org.apache.poi.POIDataSamples;
+import org.apache.poi.util.IOUtils;
 
 public final class TestAttachments extends TestCase {
    private static final POIDataSamples _samples = POIDataSamples.getHMEFInstance();
+   private HMEFMessage quick;
+   
+   @Override
+   protected void setUp() throws Exception {
+      super.setUp();
+      
+      quick = new HMEFMessage(
+            _samples.openResourceAsStream("quick-winmail.dat")
+      );
+   }
 
    /**
     * Check the file is as we expect
     */
        public void testCounts() throws Exception {
-      HMEFMessage msg = new HMEFMessage(
-            _samples.openResourceAsStream("quick-winmail.dat")
-      );
-      
       // Should have 5 attachments
-      assertEquals(5, msg.getAttachments().size());
+      assertEquals(5, quick.getAttachments().size());
        }
    
        /**
         * Check some basic bits about the attachments 
         */
    public void testBasicAttachments() throws Exception {
-      // TODO
+      List<Attachment> attachments = quick.getAttachments();
+      
+      // Word first
+      assertEquals("quick.doc", attachments.get(0).getFilename());
+      assertEquals("quick.doc", attachments.get(0).getLongFilename());
+      assertEquals(".doc", attachments.get(0).getExtension());
+      
+      // Then HTML
+      assertEquals("QUICK~1.HTM", attachments.get(1).getFilename());
+      assertEquals("quick.html", attachments.get(1).getLongFilename());
+      assertEquals(".html", attachments.get(1).getExtension());
+      
+      // Then PDF
+      assertEquals("quick.pdf", attachments.get(2).getFilename());
+      assertEquals("quick.pdf", attachments.get(2).getLongFilename());
+      assertEquals(".pdf", attachments.get(2).getExtension());
+      
+      // Then Text
+      assertEquals("quick.txt", attachments.get(3).getFilename());
+      assertEquals("quick.txt", attachments.get(3).getLongFilename());
+      assertEquals(".txt", attachments.get(3).getExtension());
+      
+      // And finally XML
+      assertEquals("quick.xml", attachments.get(4).getFilename());
+      assertEquals("quick.xml", attachments.get(4).getLongFilename());
+      assertEquals(".xml", attachments.get(4).getExtension());
    }
    
    /**
@@ -48,13 +85,52 @@ public final class TestAttachments extends TestCase {
     *  the right values for key things
     */
    public void testAttachmentDetails() throws Exception {
-      // TODO
+      List<Attachment> attachments = quick.getAttachments();
+      
+      DateFormat fmt = DateFormat.getDateTimeInstance(
+            DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.UK
+      );
+      
+      // They should all have the same date on them
+      assertEquals("28-Apr-2010 13:40:56", fmt.format( attachments.get(0).getModifiedDate()));
+      assertEquals("28-Apr-2010 13:40:56", fmt.format( attachments.get(1).getModifiedDate()));
+      assertEquals("28-Apr-2010 13:40:56", fmt.format( attachments.get(2).getModifiedDate()));
+      assertEquals("28-Apr-2010 13:40:56", fmt.format( attachments.get(3).getModifiedDate()));
+      assertEquals("28-Apr-2010 13:40:56", fmt.format( attachments.get(4).getModifiedDate()));
+      
+      // They should all have a 3512 byte metafile rendered version
+      assertEquals(3512, attachments.get(0).getRenderedMetaFile().length);
+      assertEquals(3512, attachments.get(1).getRenderedMetaFile().length);
+      assertEquals(3512, attachments.get(2).getRenderedMetaFile().length);
+      assertEquals(3512, attachments.get(3).getRenderedMetaFile().length);
+      assertEquals(3512, attachments.get(4).getRenderedMetaFile().length);
    }
 
    /**
     * Ensure the attachment contents come back as they should do 
     */
    public void testAttachmentContents() throws Exception {
-      // TODO
+      List<Attachment> attachments = quick.getAttachments();
+      
+      assertContents("quick.doc", attachments.get(0));
+      assertContents("quick.html", attachments.get(1));
+      assertContents("quick.pdf", attachments.get(2));
+      assertContents("quick.txt", attachments.get(3));
+      assertContents("quick.xml", attachments.get(4));
+   }
+   
+   private void assertContents(String filename, Attachment attachment) 
+         throws IOException {
+      assertEquals(filename, attachment.getLongFilename());
+      
+      byte[] expected = IOUtils.toByteArray(
+            _samples.openResourceAsStream("quick-contents/" + filename)
+      );
+      byte[] actual = attachment.getContents();
+      
+      assertEquals(expected.length, actual.length);
+      for(int i=0; i<expected.length; i++) {
+         assertEquals("Byte " + i + " wrong", expected[i], actual[i]);
+      }
    }
 }
index 6f60a4e184ced8c5c1041a9dd94d90182cb79d01..6cdf7ce8f5a2226054f456c3b7f6fa9745b02c5c 100644 (file)
@@ -154,9 +154,15 @@ public final class POIDataSamples {
                     + "' not found in data dir '" + _resolvedDataDir.getAbsolutePath() + "'");\r
         }\r
         try {\r
-            if(sampleFileName.length() > 0 && !sampleFileName.equals(f.getCanonicalFile().getName())){\r
-                throw new RuntimeException("File name is case-sensitive: requested '" + sampleFileName\r
+            if(sampleFileName.length() > 0) {\r
+               String fn = sampleFileName;\r
+               if(fn.indexOf('/') > 0) {\r
+                  fn = fn.substring(fn.indexOf('/')+1);\r
+               }\r
+               if(!fn.equals(f.getCanonicalFile().getName())){\r
+                   throw new RuntimeException("File name is case-sensitive: requested '" + fn\r
                         + "' but actual file is '" + f.getCanonicalFile().getName() + "'");\r
+               }\r
             }\r
         } catch (IOException e){\r
             throw new RuntimeException(e);\r