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:
parent
145de166a1
commit
aa1963cd3b
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -154,9 +154,15 @@ 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);
|
||||
|
Loading…
Reference in New Issue
Block a user