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;
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();
}
}
* 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) );
}
/**
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 {
--- /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.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;
+ }
+}
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;
+ }
}
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);
}
--- /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.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;
+ }
+}
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;
+ }
}
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;
// 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;
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());
}
/**
* 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]);
+ }
}
}
+ "' 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