Improve HMEF handling of typed attributes (Strings and Dates), for both TNEF and MAPI attributes, and use this to allow easier access to common file parts. Then use this in the attachment unit tests.

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1076603 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Nick Burch 2011-03-03 12:41:39 +00:00
parent 145de166a1
commit aa1963cd3b
11 changed files with 388 additions and 34 deletions

View File

@ -18,12 +18,16 @@
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();
}
}

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

View 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 {

View File

@ -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;
}
}

View 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;
}
}

View 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);
}

View File

@ -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;
}
}

View 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);
String tmpData = null;
byte[] data = getData();
if(getType() == TNEFProperty.TYPE_TEXT) {
tmpData = StringUtil.getFromUnicodeLE(data);
} else {
tmpData = 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() {
byte[] data = getData();
// TODO Verify if these are the right way around
if(getType() == TNEFProperty.TYPE_TEXT) {
return StringUtil.getFromUnicodeLE(data);
}
return StringUtil.getFromCompressedUnicode(
data, 0, data.length
);
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;
}
}

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

View File

@ -17,30 +17,67 @@
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]);
}
}
}

View File

@ -154,10 +154,16 @@ public final class POIDataSamples {
+ "' not found in data dir '" + _resolvedDataDir.getAbsolutePath() + "'");
}
try {
if(sampleFileName.length() > 0 && !sampleFileName.equals(f.getCanonicalFile().getName())){
throw new RuntimeException("File name is case-sensitive: requested '" + sampleFileName
if(sampleFileName.length() > 0) {
String fn = sampleFileName;
if(fn.indexOf('/') > 0) {
fn = fn.substring(fn.indexOf('/')+1);
}
if(!fn.equals(f.getCanonicalFile().getName())){
throw new RuntimeException("File name is case-sensitive: requested '" + fn
+ "' but actual file is '" + f.getCanonicalFile().getName() + "'");
}
}
} catch (IOException e){
throw new RuntimeException(e);
}