git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1076603 13f79535-47bb-0310-9956-ffa450edef68tags/REL_3_8_BETA2
@@ -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); | |||
} | |||
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; | |||
} | |||
} |
@@ -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); |