From 2a4805f9382e4a7f085a74fb0ff53fa9fc216113 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Tue, 12 Jan 2010 12:02:18 +0000 Subject: [PATCH] Improved how HSMF handles multiple recipients git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@898295 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + src/java/org/apache/poi/util/StringUtil.java | 27 + .../src/org/apache/poi/hsmf/MAPIMessage.java | 476 +++++++++++------- .../poi/hsmf/datatypes/AttachmentChunks.java | 11 + .../poi/hsmf/datatypes/RecipientChunks.java | 107 +++- .../hsmf/extractor/OutlookTextExtactor.java | 60 ++- .../poi/hsmf/parsers/POIFSChunkParser.java | 2 +- .../org/apache/poi/hsmf/AllHSMFTests.java | 4 + .../org/apache/poi/hsmf/TestBasics.java | 11 +- .../poi/hsmf/datatypes/TestSorters.java | 97 ++++ .../extractor/TestOutlookTextExtractor.java | 75 ++- .../hsmf/parsers/TestPOIFSChunkParser.java | 123 ++++- .../org/apache/poi/util/TestStringUtil.java | 40 ++ test-data/hsmf/example_received_regular.msg | Bin 0 -> 53248 bytes test-data/hsmf/example_received_unicode.msg | Bin 0 -> 56832 bytes test-data/hsmf/example_sent_regular.msg | Bin 0 -> 53248 bytes test-data/hsmf/example_sent_unicode.msg | Bin 0 -> 53760 bytes 17 files changed, 817 insertions(+), 217 deletions(-) create mode 100644 src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestSorters.java create mode 100644 test-data/hsmf/example_received_regular.msg create mode 100644 test-data/hsmf/example_received_unicode.msg create mode 100644 test-data/hsmf/example_sent_regular.msg create mode 100644 test-data/hsmf/example_sent_unicode.msg diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 5ee7b31f2f..986be3b352 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + Improved how HSMF handles multiple recipients Add PublisherTextExtractor support to ExtractorFactory Add XSLF support for text extraction from tables Support attachments as embeded documents within the new OutlookTextExtractor diff --git a/src/java/org/apache/poi/util/StringUtil.java b/src/java/org/apache/poi/util/StringUtil.java index b08a979fa4..62e42c2a2f 100644 --- a/src/java/org/apache/poi/util/StringUtil.java +++ b/src/java/org/apache/poi/util/StringUtil.java @@ -20,6 +20,7 @@ package org.apache.poi.util; import java.io.UnsupportedEncodingException; import java.text.FieldPosition; import java.text.NumberFormat; +import java.util.Iterator; import org.apache.poi.hssf.record.RecordInputStream; /** @@ -392,4 +393,30 @@ public class StringUtil { return true; } } + + /** + * An Iterator over an array of Strings. + */ + public static class StringsIterator implements Iterator { + private String[] strings; + private int position = 0; + public StringsIterator(String[] strings) { + if(strings != null) { + this.strings = strings; + } else { + this.strings = new String[0]; + } + } + + public boolean hasNext() { + return position < strings.length; + } + public String next() { + int ourPos = position++; + if(ourPos >= strings.length) + throw new ArrayIndexOutOfBoundsException(ourPos); + return strings[ourPos]; + } + public void remove() {} + } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java b/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java index 911119b19e..05c14482a8 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java @@ -23,14 +23,17 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import org.apache.poi.POIDocument; import org.apache.poi.hsmf.datatypes.AttachmentChunks; +import org.apache.poi.hsmf.datatypes.AttachmentChunks.AttachmentChunksSorter; import org.apache.poi.hsmf.datatypes.ChunkGroup; import org.apache.poi.hsmf.datatypes.Chunks; import org.apache.poi.hsmf.datatypes.NameIdChunks; import org.apache.poi.hsmf.datatypes.RecipientChunks; +import org.apache.poi.hsmf.datatypes.RecipientChunks.RecipientChunksSorter; import org.apache.poi.hsmf.datatypes.StringChunk; import org.apache.poi.hsmf.exceptions.ChunkNotFoundException; import org.apache.poi.hsmf.parsers.POIFSChunkParser; @@ -46,47 +49,47 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem; * [MS-OXCMSG]: Message and Attachment Object Protocol Specification */ public class MAPIMessage extends POIDocument { - private Chunks mainChunks; - private NameIdChunks nameIdChunks; - private RecipientChunks recipientChunks; - private AttachmentChunks[] attachmentChunks; - - private boolean returnNullOnMissingChunk = false; + private Chunks mainChunks; + private NameIdChunks nameIdChunks; + private RecipientChunks[] recipientChunks; + private AttachmentChunks[] attachmentChunks; - /** - * Constructor for creating new files. - * - */ - public MAPIMessage() { - // TODO - make writing possible - super(new POIFSFileSystem()); - } + private boolean returnNullOnMissingChunk = false; + + /** + * Constructor for creating new files. + * + */ + public MAPIMessage() { + // TODO - make writing possible + super(new POIFSFileSystem()); + } - /** - * Constructor for reading MSG Files from the file system. - * @param filename - * @throws IOException - */ - public MAPIMessage(String filename) throws IOException { - this(new FileInputStream(new File(filename))); - } + /** + * Constructor for reading MSG Files from the file system. + * @param filename + * @throws IOException + */ + public MAPIMessage(String filename) throws IOException { + this(new FileInputStream(new File(filename))); + } - /** - * Constructor for reading MSG Files from an input stream. - * @param in - * @throws IOException - */ - public MAPIMessage(InputStream in) throws IOException { - this(new POIFSFileSystem(in)); - } + /** + * Constructor for reading MSG Files from an input stream. + * @param in + * @throws IOException + */ + public MAPIMessage(InputStream in) throws IOException { + this(new POIFSFileSystem(in)); + } /** * Constructor for reading MSG Files from a POIFS filesystem * @param in * @throws IOException */ public MAPIMessage(POIFSFileSystem fs) throws IOException { - this(fs.getRoot(), fs); + this(fs.getRoot(), fs); } /** * Constructor for reading MSG Files from a certain @@ -96,178 +99,254 @@ public class MAPIMessage extends POIDocument { */ public MAPIMessage(DirectoryNode poifsDir, POIFSFileSystem fs) throws IOException { super(poifsDir, fs); - - // Grab all the chunks - ChunkGroup[] chunkGroups = POIFSChunkParser.parse(poifsDir); - - // Grab interesting bits - ArrayList attachments = new ArrayList(); - for(ChunkGroup group : chunkGroups) { - // Should only ever be one of these - if(group instanceof Chunks) { - mainChunks = (Chunks)group; - } else if(group instanceof NameIdChunks) { - nameIdChunks = (NameIdChunks)group; - } else if(group instanceof RecipientChunks) { - recipientChunks = (RecipientChunks)group; - } - - // Add to list(s) - if(group instanceof AttachmentChunks) { - attachments.add((AttachmentChunks)group); - } - } - attachmentChunks = attachments.toArray(new AttachmentChunks[attachments.size()]); - } + + // Grab all the chunks + ChunkGroup[] chunkGroups = POIFSChunkParser.parse(poifsDir); + + // Grab interesting bits + ArrayList attachments = new ArrayList(); + ArrayList recipients = new ArrayList(); + for(ChunkGroup group : chunkGroups) { + // Should only ever be one of these + if(group instanceof Chunks) { + mainChunks = (Chunks)group; + } else if(group instanceof NameIdChunks) { + nameIdChunks = (NameIdChunks)group; + } else if(group instanceof RecipientChunks) { + recipients.add( (RecipientChunks)group ); + } + + // Add to list(s) + if(group instanceof AttachmentChunks) { + attachments.add( (AttachmentChunks)group ); + } + } + attachmentChunks = attachments.toArray(new AttachmentChunks[attachments.size()]); + recipientChunks = recipients.toArray(new RecipientChunks[recipients.size()]); + + // Now sort these chunks lists so they're in ascending order, + // rather than in random filesystem order + Arrays.sort(attachmentChunks, new AttachmentChunksSorter()); + Arrays.sort(recipientChunks, new RecipientChunksSorter()); + } - /** - * Gets a string value based on the passed chunk. - * @throws ChunkNotFoundException if the chunk isn't there - */ - public String getStringFromChunk(StringChunk chunk) throws ChunkNotFoundException { - if(chunk == null) { - if(returnNullOnMissingChunk) { - return null; - } else { - throw new ChunkNotFoundException(); - } - } - return chunk.getValue(); - } + /** + * Gets a string value based on the passed chunk. + * @throws ChunkNotFoundException if the chunk isn't there + */ + public String getStringFromChunk(StringChunk chunk) throws ChunkNotFoundException { + if(chunk == null) { + if(returnNullOnMissingChunk) { + return null; + } else { + throw new ChunkNotFoundException(); + } + } + return chunk.getValue(); + } - /** - * Gets the plain text body of this Outlook Message - * @return The string representation of the 'text' version of the body, if available. - * @throws ChunkNotFoundException - */ - public String getTextBody() throws ChunkNotFoundException { - return getStringFromChunk(mainChunks.textBodyChunk); - } + /** + * Gets the plain text body of this Outlook Message + * @return The string representation of the 'text' version of the body, if available. + * @throws ChunkNotFoundException + */ + public String getTextBody() throws ChunkNotFoundException { + return getStringFromChunk(mainChunks.textBodyChunk); + } - /** - * Gets the subject line of the Outlook Message - * @throws ChunkNotFoundException - */ - public String getSubject() throws ChunkNotFoundException { - return getStringFromChunk(mainChunks.subjectChunk); - } + /** + * Gets the subject line of the Outlook Message + * @throws ChunkNotFoundException + */ + public String getSubject() throws ChunkNotFoundException { + return getStringFromChunk(mainChunks.subjectChunk); + } - /** - * Gets the display value of the "TO" line of the outlook message - * This is not the actual list of addresses/values that will be sent to if you click Reply in the email. - * @throws ChunkNotFoundException - */ - public String getDisplayTo() throws ChunkNotFoundException { - return getStringFromChunk(mainChunks.displayToChunk); - } + /** + * Gets the display value of the "FROM" line of the outlook message + * This is not the actual address that was sent from but the formated display of the user name. + * @throws ChunkNotFoundException + */ + public String getDisplayFrom() throws ChunkNotFoundException { + return getStringFromChunk(mainChunks.displayFromChunk); + } - /** - * Gets the display value of the "FROM" line of the outlook message - * This is not the actual address that was sent from but the formated display of the user name. - * @throws ChunkNotFoundException - */ - public String getDisplayFrom() throws ChunkNotFoundException { - return getStringFromChunk(mainChunks.displayFromChunk); - } + /** + * Gets the display value of the "TO" line of the outlook message. + * If there are multiple recipients, they will be separated + * by semicolons. + * This is not the actual list of addresses/values that will be + * sent to if you click Reply in the email - those are stored + * in {@link RecipientChunks}. + * @throws ChunkNotFoundException + */ + public String getDisplayTo() throws ChunkNotFoundException { + return getStringFromChunk(mainChunks.displayToChunk); + } - /** - * Gets the display value of the "TO" line of the outlook message - * This is not the actual list of addresses/values that will be sent to if you click Reply in the email. - * @throws ChunkNotFoundException - */ - public String getDisplayCC() throws ChunkNotFoundException { - return getStringFromChunk(mainChunks.displayCCChunk); - } + /** + * Gets the display value of the "CC" line of the outlook message. + * If there are multiple recipients, they will be separated + * by semicolons. + * This is not the actual list of addresses/values that will be + * sent to if you click Reply in the email - those are stored + * in {@link RecipientChunks}. + * @throws ChunkNotFoundException + */ + public String getDisplayCC() throws ChunkNotFoundException { + return getStringFromChunk(mainChunks.displayCCChunk); + } - /** - * Gets the display value of the "TO" line of the outlook message - * This is not the actual list of addresses/values that will be sent to if you click Reply in the email. - * @throws ChunkNotFoundException - */ - public String getDisplayBCC() throws ChunkNotFoundException { - return getStringFromChunk(mainChunks.displayBCCChunk); - } - - - /** - * Returns the recipient's email address, checking all the - * likely chunks in search of it. - */ - public String getRecipientEmailAddress() throws ChunkNotFoundException { - if(recipientChunks == null) { - throw new ChunkNotFoundException("No recipients section present"); - } - String email = recipientChunks.getRecipientEmailAddress(); - if(email != null) { - return email; - } else { - throw new ChunkNotFoundException(); - } - } + /** + * Gets the display value of the "BCC" line of the outlook message. + * If there are multiple recipients, they will be separated + * by semicolons. + * This is not the actual list of addresses/values that will be + * sent to if you click Reply in the email - those are stored + * in {@link RecipientChunks}. + * This will only be present in sent emails, not received ones! + * @throws ChunkNotFoundException + */ + public String getDisplayBCC() throws ChunkNotFoundException { + return getStringFromChunk(mainChunks.displayBCCChunk); + } + + /** + * Returns all the recipients' email address, separated by + * semicolons. Checks all the likely chunks in search of + * the addresses. + */ + public String getRecipientEmailAddress() throws ChunkNotFoundException { + return toSemicolonList(getRecipientEmailAddressList()); + } + /** + * Returns an array of all the recipient's email address, normally + * in TO then CC then BCC order. + * Checks all the likely chunks in search of the addresses. + */ + public String[] getRecipientEmailAddressList() throws ChunkNotFoundException { + if(recipientChunks == null || recipientChunks.length == 0) { + throw new ChunkNotFoundException("No recipients section present"); + } + + String[] emails = new String[recipientChunks.length]; + for(int i=0; i { + @Override + public int compare(AttachmentChunks a, AttachmentChunks b) { + return a.poifsName.compareTo(b.poifsName); + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/RecipientChunks.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/RecipientChunks.java index 15c35c069d..b20aba8a39 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/RecipientChunks.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/RecipientChunks.java @@ -18,20 +18,29 @@ package org.apache.poi.hsmf.datatypes; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; /** * Collection of convenience chunks for the - * Recip(ient) part of an outlook file + * Recip(ient) part of an outlook file. + * + * If a message has multiple recipients, there will be + * several of these. */ public final class RecipientChunks implements ChunkGroup { public static final String PREFIX = "__recip_version1.0_#"; public static final int RECIPIENT_NAME = 0x3001; public static final int DELIVERY_TYPE = 0x3002; - public static final int RECIPIENT_SEARCH = 0x300B; - public static final int RECIPIENT_EMAIL = 0x39FE; + public static final int RECIPIENT_EMAIL_ADDRESS = 0x3003; + public static final int RECIPIENT_SEARCH = 0x300B; + public static final int RECIPIENT_SMTP_ADDRESS = 0x39FE; + public static final int RECIPIENT_DISPLAY_NAME = 0x5FF6; + + /** Our 0 based position in the list of recipients */ + public int recipientNumber; /** TODO */ public ByteChunk recipientSearchChunk; @@ -42,27 +51,84 @@ public final class RecipientChunks implements ChunkGroup { */ public StringChunk recipientNameChunk; /** - * The email address of the recipient, but + * The email address of the recipient, which + * could be in SMTP or SEARCH format, but * isn't always present... */ public StringChunk recipientEmailChunk; + /** + * The smtp destination email address of + * the recipient, but isn't always present... + */ + public StringChunk recipientSMTPChunk; /** * Normally EX or SMTP. Will generally affect * where the email address ends up. */ public StringChunk deliveryTypeChunk; + /** + * The display name of the recipient. + * Normally seems to hold the same value + * as in recipientNameChunk + */ + public StringChunk recipientDisplayNameChunk; + public RecipientChunks(String name) { + recipientNumber = -1; + int splitAt = name.lastIndexOf('#'); + if(splitAt > -1) { + String number = name.substring(splitAt+1); + try { + recipientNumber = Integer.parseInt(number, 16); + } catch(NumberFormatException e) { + System.err.println("Invalid recipient number in name " + name); + } + } + } + + /** + * Tries to find their name, + * in whichever chunk holds it. + */ + public String getRecipientName() { + if(recipientNameChunk != null) { + return recipientNameChunk.getValue(); + } + if(recipientDisplayNameChunk != null) { + return recipientDisplayNameChunk.getValue(); + } + + // Can't find it + return null; + } + /** * Tries to find their email address, in * whichever chunk holds it given the * delivery type. */ public String getRecipientEmailAddress() { - if(recipientEmailChunk != null) { - return recipientEmailChunk.getValue(); + // If we have this, it really has the email + if(recipientSMTPChunk != null) { + return recipientSMTPChunk.getValue(); } - // Probably in the name field + + // This might be a real email, or might be + // in CN=... format + if(recipientEmailChunk != null) { + String email = recipientEmailChunk.getValue(); + int cne = email.indexOf("/CN="); + if(cne == -1) { + // Normal smtp address + return email; + } else { + // /O=..../CN=em@ail + return email.substring(cne+4); + } + } + + // Might be in the name field, check there if(recipientNameChunk != null) { String name = recipientNameChunk.getValue(); if(name.indexOf('@') > -1) { @@ -73,13 +139,16 @@ public final class RecipientChunks implements ChunkGroup { return name; } } - // Check the search chunk + + // Check the search chunk, see if it's + // encoded as a SMTP destination in there. if(recipientSearchChunk != null) { String search = recipientSearchChunk.getAs7bitString(); if(search.indexOf("SMTP:") != -1) { return search.substring(search.indexOf("SMTP:") + 5); } } + // Can't find it return null; } @@ -104,11 +173,17 @@ public final class RecipientChunks implements ChunkGroup { recipientSearchChunk = (ByteChunk)chunk; break; case RECIPIENT_NAME: + recipientDisplayNameChunk = (StringChunk)chunk; + break; + case RECIPIENT_DISPLAY_NAME: recipientNameChunk = (StringChunk)chunk; break; - case RECIPIENT_EMAIL: + case RECIPIENT_EMAIL_ADDRESS: recipientEmailChunk = (StringChunk)chunk; break; + case RECIPIENT_SMTP_ADDRESS: + recipientSMTPChunk = (StringChunk)chunk; + break; case DELIVERY_TYPE: deliveryTypeChunk = (StringChunk)chunk; break; @@ -117,4 +192,18 @@ public final class RecipientChunks implements ChunkGroup { // And add to the main list allChunks.add(chunk); } + + /** + * Orders by the recipient number. + */ + public static class RecipientChunksSorter implements Comparator { + @Override + public int compare(RecipientChunks a, RecipientChunks b) { + if(a.recipientNumber < b.recipientNumber) + return -1; + if(a.recipientNumber > b.recipientNumber) + return +1; + return 0; + } + } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/extractor/OutlookTextExtactor.java b/src/scratchpad/src/org/apache/poi/hsmf/extractor/OutlookTextExtactor.java index a6ada5bb95..8bbea40893 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/extractor/OutlookTextExtactor.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/extractor/OutlookTextExtactor.java @@ -25,6 +25,7 @@ import org.apache.poi.hsmf.MAPIMessage; import org.apache.poi.hsmf.exceptions.ChunkNotFoundException; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.util.StringUtil.StringsIterator; /** * A text extractor for HSMF (Outlook) .msg files. @@ -50,7 +51,7 @@ public class OutlookTextExtactor extends POIOLE2TextExtractor { public MAPIMessage getMAPIMessage() { return (MAPIMessage)document; } - + /** * Outputs something a little like a RFC822 email */ @@ -58,20 +59,33 @@ public class OutlookTextExtactor extends POIOLE2TextExtractor { MAPIMessage msg = (MAPIMessage)document; StringBuffer s = new StringBuffer(); + StringsIterator emails; + try { + emails = new StringsIterator( + msg.getRecipientEmailAddressList() + ); + } catch(ChunkNotFoundException e) { + emails = new StringsIterator(new String[0]); + } + try { s.append("From: " + msg.getDisplayFrom() + "\n"); } catch(ChunkNotFoundException e) {} + + // For To, CC and BCC, try to match the names + // up with their email addresses. Relies on the + // Recipient Chunks being in the same order as + // people in To + CC + BCC. try { - s.append("To: " + msg.getDisplayTo() + "\n"); + handleEmails(s, "To", msg.getDisplayTo(), emails); } catch(ChunkNotFoundException e) {} try { - if(msg.getDisplayCC().length() > 0) - s.append("CC: " + msg.getDisplayCC() + "\n"); + handleEmails(s, "CC", msg.getDisplayCC(), emails); } catch(ChunkNotFoundException e) {} try { - if(msg.getDisplayBCC().length() > 0) - s.append("BCC: " + msg.getDisplayBCC() + "\n"); + handleEmails(s, "BCC", msg.getDisplayBCC(), emails); } catch(ChunkNotFoundException e) {} + try { SimpleDateFormat f = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss"); s.append("Date: " + f.format(msg.getMessageDate().getTime()) + "\n"); @@ -85,4 +99,38 @@ public class OutlookTextExtactor extends POIOLE2TextExtractor { return s.toString(); } + + /** + * Takes a Display focused string, eg "Nick; Jim" and an iterator + * of emails, and does its best to return something like + * "Nick ; Jim " + */ + protected void handleEmails(StringBuffer s, String type, String displayText, StringsIterator emails) { + if(displayText == null || displayText.length() == 0) { + return; + } + + String[] names = displayText.split(";\\s*"); + boolean first = true; + + s.append(type + ": "); + for(String name : names) { + if(first) { + first = false; + } else { + s.append("; "); + } + + s.append(name); + if(emails.hasNext()) { + String email = emails.next(); + // Append the email address in <>, assuming + // the name wasn't already the email address + if(! email.equals(name)) { + s.append( " <" + email + ">"); + } + } + } + s.append("\n"); + } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java b/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java index 3a38d1b3d0..2f2899f345 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java @@ -67,7 +67,7 @@ public final class POIFSChunkParser { group = new NameIdChunks(); } if(dir.getName().startsWith(RecipientChunks.PREFIX)) { - group = new RecipientChunks(); + group = new RecipientChunks(dir.getName()); } if(group != null) { diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/AllHSMFTests.java b/src/scratchpad/testcases/org/apache/poi/hsmf/AllHSMFTests.java index 80660aa05a..710d991d96 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/AllHSMFTests.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/AllHSMFTests.java @@ -21,6 +21,7 @@ import junit.framework.Test; import junit.framework.TestSuite; import org.apache.poi.hsmf.datatypes.*; +import org.apache.poi.hsmf.extractor.TestOutlookTextExtractor; import org.apache.poi.hsmf.parsers.*; public final class AllHSMFTests { @@ -34,7 +35,10 @@ public final class AllHSMFTests { suite.addTestSuite(TestChunkData.class); suite.addTestSuite(TestTypes.class); + suite.addTestSuite(TestSorters.class); + suite.addTestSuite(TestOutlookTextExtractor.class); + suite.addTestSuite(TestPOIFSChunkParser.class); return suite; diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/TestBasics.java b/src/scratchpad/testcases/org/apache/poi/hsmf/TestBasics.java index 008a4edba4..25c793339f 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/TestBasics.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/TestBasics.java @@ -52,8 +52,17 @@ public final class TestBasics extends TestCase { public void testRecipientEmail() throws Exception { assertEquals("travis@overwrittenstack.com", simple.getRecipientEmailAddress()); assertEquals("kevin.roast@alfresco.org", quick.getRecipientEmailAddress()); - assertEquals("randall.scarberry@pnl.gov", outlook30.getRecipientEmailAddress()); assertEquals("nicolas1.23456@free.fr", attachments.getRecipientEmailAddress()); + + // This one has lots... + assertEquals(18, outlook30.getRecipientEmailAddressList().length); + assertEquals("shawn.bohn@pnl.gov; gus.calapristi@pnl.gov; Richard.Carter@pnl.gov; " + + "barb.cheney@pnl.gov; nick.cramer@pnl.gov; vern.crow@pnl.gov; Laura.Curtis@pnl.gov; " + + "julie.dunkle@pnl.gov; david.gillen@pnl.gov; michelle@pnl.gov; Jereme.Haack@pnl.gov; " + + "Michelle.Hart@pnl.gov; ranata.johnson@pnl.gov; grant.nakamura@pnl.gov; " + + "debbie.payne@pnl.gov; stuart.rose@pnl.gov; randall.scarberry@pnl.gov; Leigh.Williams@pnl.gov", + outlook30.getRecipientEmailAddress() + ); } /** diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestSorters.java b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestSorters.java new file mode 100644 index 0000000000..815fb2d661 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestSorters.java @@ -0,0 +1,97 @@ +/* ==================================================================== + 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.hsmf.datatypes; + +import java.util.Arrays; + +import org.apache.poi.hsmf.datatypes.AttachmentChunks.AttachmentChunksSorter; +import org.apache.poi.hsmf.datatypes.RecipientChunks.RecipientChunksSorter; + +import junit.framework.TestCase; + +/** + * Checks that the sorters on the chunk groups order + * chunks properly. + */ +public final class TestSorters extends TestCase { + public void testAttachmentChunksSorter() { + AttachmentChunks[] chunks; + + // Simple + chunks = new AttachmentChunks[] { + new AttachmentChunks("__attach_version1.0_#00000001"), + new AttachmentChunks("__attach_version1.0_#00000000"), + }; + Arrays.sort(chunks, new AttachmentChunksSorter()); + assertEquals("__attach_version1.0_#00000000", chunks[0].getPOIFSName()); + assertEquals("__attach_version1.0_#00000001", chunks[1].getPOIFSName()); + + // Lots, with gaps + chunks = new AttachmentChunks[] { + new AttachmentChunks("__attach_version1.0_#00000101"), + new AttachmentChunks("__attach_version1.0_#00000001"), + new AttachmentChunks("__attach_version1.0_#00000002"), + new AttachmentChunks("__attach_version1.0_#00000005"), + new AttachmentChunks("__attach_version1.0_#00000026"), + new AttachmentChunks("__attach_version1.0_#00000000"), + new AttachmentChunks("__attach_version1.0_#000000AB"), + }; + Arrays.sort(chunks, new AttachmentChunksSorter()); + assertEquals("__attach_version1.0_#00000000", chunks[0].getPOIFSName()); + assertEquals("__attach_version1.0_#00000001", chunks[1].getPOIFSName()); + assertEquals("__attach_version1.0_#00000002", chunks[2].getPOIFSName()); + assertEquals("__attach_version1.0_#00000005", chunks[3].getPOIFSName()); + assertEquals("__attach_version1.0_#00000026", chunks[4].getPOIFSName()); + assertEquals("__attach_version1.0_#000000AB", chunks[5].getPOIFSName()); + assertEquals("__attach_version1.0_#00000101", chunks[6].getPOIFSName()); + } + + public void testRecipientChunksSorter() { + RecipientChunks[] chunks; + + // Simple + chunks = new RecipientChunks[] { + new RecipientChunks("__recip_version1.0_#00000001"), + new RecipientChunks("__recip_version1.0_#00000000"), + }; + Arrays.sort(chunks, new RecipientChunksSorter()); + assertEquals(0, chunks[0].recipientNumber); + assertEquals(1, chunks[1].recipientNumber); + + // Lots, with gaps + chunks = new RecipientChunks[] { + new RecipientChunks("__recip_version1.0_#00020001"), + new RecipientChunks("__recip_version1.0_#000000FF"), + new RecipientChunks("__recip_version1.0_#00000205"), + new RecipientChunks("__recip_version1.0_#00000001"), + new RecipientChunks("__recip_version1.0_#00000005"), + new RecipientChunks("__recip_version1.0_#00000009"), + new RecipientChunks("__recip_version1.0_#00000404"), + new RecipientChunks("__recip_version1.0_#00000000"), + }; + Arrays.sort(chunks, new RecipientChunksSorter()); + assertEquals(0, chunks[0].recipientNumber); + assertEquals(1, chunks[1].recipientNumber); + assertEquals(5, chunks[2].recipientNumber); + assertEquals(9, chunks[3].recipientNumber); + assertEquals(0xFF, chunks[4].recipientNumber); + assertEquals(0x205, chunks[5].recipientNumber); + assertEquals(0x404, chunks[6].recipientNumber); + assertEquals(0x20001, chunks[7].recipientNumber); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/extractor/TestOutlookTextExtractor.java b/src/scratchpad/testcases/org/apache/poi/hsmf/extractor/TestOutlookTextExtractor.java index b15bbc7249..e8c9dfdc63 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/extractor/TestOutlookTextExtractor.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/extractor/TestOutlookTextExtractor.java @@ -53,7 +53,7 @@ public final class TestOutlookTextExtractor extends TestCase { String text = ext.getText(); assertContains(text, "From: Kevin Roast\n"); - assertContains(text, "To: Kevin Roast\n"); + assertContains(text, "To: Kevin Roast \n"); assertEquals(-1, text.indexOf("CC:")); assertEquals(-1, text.indexOf("BCC:")); assertContains(text, "Subject: Test the content transformer\n"); @@ -92,4 +92,77 @@ public final class TestOutlookTextExtractor extends TestCase { assertEquals(inp, poifs); assertEquals(inp, mapi); } + + /** + * Test that we correctly handle multiple To+CC+BCC + * recipients in an email we sent. + */ + public void testSentWithMulipleRecipients() throws Exception { + // To: 'Ashutosh Dandavate' , + // 'Paul Holmes-Higgin' , + // 'Mike Farman' + // Cc: nickb@alfresco.com, nick.burch@alfresco.com, + // 'Roy Wetherall' + // Bcc: 'David Caruana' , + // 'Vonka Jan' + + String[] files = new String[] { + "example_sent_regular.msg", "example_sent_unicode.msg" + }; + for(String file : files) { + MAPIMessage msg = new MAPIMessage(new POIFSFileSystem( + new FileInputStream(samples.getFile(file)) + )); + + OutlookTextExtactor ext = new OutlookTextExtactor(msg); + String text = ext.getText(); + + assertContains(text, "From: Mike Farman\n"); + assertContains(text, "To: 'Ashutosh Dandavate' ; " + + "'Paul Holmes-Higgin' ; 'Mike Farman' \n"); + assertContains(text, "CC: 'nickb@alfresco.com' ; " + + "'nick.burch@alfresco.com' ; 'Roy Wetherall' \n"); + assertContains(text, "BCC: 'David Caruana' ; " + + "'Vonka Jan' \n"); + assertContains(text, "Subject: This is a test message please ignore\n"); + assertEquals(-1, text.indexOf("Date:")); + assertContains(text, "The quick brown fox jumps over the lazy dog"); + } + } + + /** + * Test that we correctly handle multiple To+CC + * recipients in an email we received. + */ + public void testReceivedWithMultipleRecipients() throws Exception { + // To: 'Ashutosh Dandavate' , + // 'Paul Holmes-Higgin' , + // 'Mike Farman' + // Cc: nickb@alfresco.com, nick.burch@alfresco.com, + // 'Roy Wetherall' + // (No BCC shown) + + + String[] files = new String[] { + "example_received_regular.msg", "example_received_unicode.msg" + }; + for(String file : files) { + MAPIMessage msg = new MAPIMessage(new POIFSFileSystem( + new FileInputStream(samples.getFile(file)) + )); + + OutlookTextExtactor ext = new OutlookTextExtactor(msg); + String text = ext.getText(); + + assertContains(text, "From: Mike Farman\n"); + assertContains(text, "To: 'Ashutosh Dandavate' ; " + + "'Paul Holmes-Higgin' ; 'Mike Farman' \n"); + assertContains(text, "CC: nickb@alfresco.com; " + + "nick.burch@alfresco.com; 'Roy Wetherall' \n"); + assertEquals(-1, text.indexOf("BCC:")); + assertContains(text, "Subject: This is a test message please ignore\n"); + assertEquals(-1, text.indexOf("Date:")); + assertContains(text, "The quick brown fox jumps over the lazy dog"); + } + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/parsers/TestPOIFSChunkParser.java b/src/scratchpad/testcases/org/apache/poi/hsmf/parsers/TestPOIFSChunkParser.java index 5df734f1f2..68094e2ee8 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/parsers/TestPOIFSChunkParser.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/parsers/TestPOIFSChunkParser.java @@ -21,6 +21,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Calendar; import org.apache.poi.hsmf.MAPIMessage; @@ -29,6 +30,7 @@ import org.apache.poi.hsmf.datatypes.ChunkGroup; import org.apache.poi.hsmf.datatypes.Chunks; import org.apache.poi.hsmf.datatypes.NameIdChunks; import org.apache.poi.hsmf.datatypes.RecipientChunks; +import org.apache.poi.hsmf.datatypes.RecipientChunks.RecipientChunksSorter; import org.apache.poi.hsmf.datatypes.StringChunk; import org.apache.poi.hsmf.datatypes.Types; import org.apache.poi.hsmf.exceptions.ChunkNotFoundException; @@ -81,7 +83,7 @@ public final class TestPOIFSChunkParser extends TestCase { } } - public void testFindsRecips() throws IOException { + public void testFindsRecips() throws IOException, ChunkNotFoundException { POIFSFileSystem simple = new POIFSFileSystem( new FileInputStream(samples.getFile("quick.msg")) ); @@ -95,7 +97,9 @@ public final class TestPOIFSChunkParser extends TestCase { assertTrue(groups[2] instanceof NameIdChunks); RecipientChunks recips = (RecipientChunks)groups[1]; - assertEquals("kevin.roast@alfresco.org", recips.recipientEmailChunk.getValue()); + assertEquals("kevin.roast@alfresco.org", recips.recipientSMTPChunk.getValue()); + assertEquals("/O=HOSTEDSERVICE2/OU=FIRST ADMINISTRATIVE GROUP/CN=RECIPIENTS/CN=Kevin.roast@ben", + recips.recipientEmailChunk.getValue()); String search = new String(recips.recipientSearchChunk.getValue(), "ASCII"); assertEquals("CN=KEVIN.ROAST@BEN\0", search.substring(search.length()-19)); @@ -103,20 +107,123 @@ public final class TestPOIFSChunkParser extends TestCase { // Now via MAPIMessage MAPIMessage msg = new MAPIMessage(simple); assertNotNull(msg.getRecipientDetailsChunks()); + assertEquals(1, msg.getRecipientDetailsChunks().length); - assertEquals("kevin.roast@alfresco.org", msg.getRecipientDetailsChunks().recipientEmailChunk.getValue()); + assertEquals("kevin.roast@alfresco.org", msg.getRecipientDetailsChunks()[0].recipientSMTPChunk.getValue()); + assertEquals("kevin.roast@alfresco.org", msg.getRecipientDetailsChunks()[0].getRecipientEmailAddress()); + assertEquals("Kevin Roast", msg.getRecipientDetailsChunks()[0].getRecipientName()); + assertEquals("kevin.roast@alfresco.org", msg.getRecipientEmailAddress()); // Try both SMTP and EX files for recipient - assertEquals("EX", msg.getRecipientDetailsChunks().deliveryTypeChunk.getValue()); - assertEquals("kevin.roast@alfresco.org", msg.getRecipientDetailsChunks().recipientEmailChunk.getValue()); + assertEquals("EX", msg.getRecipientDetailsChunks()[0].deliveryTypeChunk.getValue()); + assertEquals("kevin.roast@alfresco.org", msg.getRecipientDetailsChunks()[0].recipientSMTPChunk.getValue()); + assertEquals("/O=HOSTEDSERVICE2/OU=FIRST ADMINISTRATIVE GROUP/CN=RECIPIENTS/CN=Kevin.roast@ben", + msg.getRecipientDetailsChunks()[0].recipientEmailChunk.getValue()); + // Now look at another message msg = new MAPIMessage(new POIFSFileSystem( new FileInputStream(samples.getFile("simple_test_msg.msg")) )); - assertEquals("SMTP", msg.getRecipientDetailsChunks().deliveryTypeChunk.getValue()); - assertEquals(null, msg.getRecipientDetailsChunks().recipientEmailChunk); - assertEquals("travis@overwrittenstack.com", msg.getRecipientDetailsChunks().recipientNameChunk.getValue()); + assertNotNull(msg.getRecipientDetailsChunks()); + assertEquals(1, msg.getRecipientDetailsChunks().length); + + assertEquals("SMTP", msg.getRecipientDetailsChunks()[0].deliveryTypeChunk.getValue()); + assertEquals(null, msg.getRecipientDetailsChunks()[0].recipientSMTPChunk); + assertEquals(null, msg.getRecipientDetailsChunks()[0].recipientNameChunk); + assertEquals("travis@overwrittenstack.com", msg.getRecipientDetailsChunks()[0].recipientEmailChunk.getValue()); + assertEquals("travis@overwrittenstack.com", msg.getRecipientEmailAddress()); + } + + public void testFindsMultipleRecipients() throws IOException, ChunkNotFoundException { + POIFSFileSystem multiple = new POIFSFileSystem( + new FileInputStream(samples.getFile("example_received_unicode.msg")) + ); + + multiple.getRoot().getEntry("__recip_version1.0_#00000000"); + multiple.getRoot().getEntry("__recip_version1.0_#00000001"); + multiple.getRoot().getEntry("__recip_version1.0_#00000002"); + multiple.getRoot().getEntry("__recip_version1.0_#00000003"); + multiple.getRoot().getEntry("__recip_version1.0_#00000004"); + multiple.getRoot().getEntry("__recip_version1.0_#00000005"); + + ChunkGroup[] groups = POIFSChunkParser.parse(multiple.getRoot()); + assertEquals(9, groups.length); + assertTrue(groups[0] instanceof Chunks); + assertTrue(groups[1] instanceof RecipientChunks); + assertTrue(groups[2] instanceof AttachmentChunks); + assertTrue(groups[3] instanceof RecipientChunks); + assertTrue(groups[4] instanceof RecipientChunks); + assertTrue(groups[5] instanceof RecipientChunks); + assertTrue(groups[6] instanceof RecipientChunks); + assertTrue(groups[7] instanceof RecipientChunks); + assertTrue(groups[8] instanceof NameIdChunks); + + // In FS order initially + RecipientChunks[] chunks = new RecipientChunks[] { + (RecipientChunks)groups[1], + (RecipientChunks)groups[3], + (RecipientChunks)groups[4], + (RecipientChunks)groups[5], + (RecipientChunks)groups[6], + (RecipientChunks)groups[7], + }; + assertEquals(6, chunks.length); + assertEquals(0, chunks[0].recipientNumber); + assertEquals(4, chunks[1].recipientNumber); + assertEquals(3, chunks[2].recipientNumber); + assertEquals(2, chunks[3].recipientNumber); + assertEquals(1, chunks[4].recipientNumber); + assertEquals(5, chunks[5].recipientNumber); + + // Check + assertEquals("'Ashutosh Dandavate'", chunks[0].getRecipientName()); + assertEquals("ashutosh.dandavate@alfresco.com", chunks[0].getRecipientEmailAddress()); + assertEquals("nick.burch@alfresco.com", chunks[1].getRecipientName()); + assertEquals("nick.burch@alfresco.com", chunks[1].getRecipientEmailAddress()); + assertEquals("nickb@alfresco.com", chunks[2].getRecipientName()); + assertEquals("nickb@alfresco.com", chunks[2].getRecipientEmailAddress()); + assertEquals("'Mike Farman'", chunks[3].getRecipientName()); + assertEquals("mikef@alfresco.com", chunks[3].getRecipientEmailAddress()); + assertEquals("'Paul Holmes-Higgin'", chunks[4].getRecipientName()); + assertEquals("paul.hh@alfresco.com", chunks[4].getRecipientEmailAddress()); + assertEquals("'Roy Wetherall'", chunks[5].getRecipientName()); + assertEquals("roy.wetherall@alfresco.com", chunks[5].getRecipientEmailAddress()); + + // Now sort, and re-check + Arrays.sort(chunks, new RecipientChunksSorter()); + + assertEquals("'Ashutosh Dandavate'", chunks[0].getRecipientName()); + assertEquals("ashutosh.dandavate@alfresco.com", chunks[0].getRecipientEmailAddress()); + assertEquals("'Paul Holmes-Higgin'", chunks[1].getRecipientName()); + assertEquals("paul.hh@alfresco.com", chunks[1].getRecipientEmailAddress()); + assertEquals("'Mike Farman'", chunks[2].getRecipientName()); + assertEquals("mikef@alfresco.com", chunks[2].getRecipientEmailAddress()); + assertEquals("nickb@alfresco.com", chunks[3].getRecipientName()); + assertEquals("nickb@alfresco.com", chunks[3].getRecipientEmailAddress()); + assertEquals("nick.burch@alfresco.com", chunks[4].getRecipientName()); + assertEquals("nick.burch@alfresco.com", chunks[4].getRecipientEmailAddress()); + assertEquals("'Roy Wetherall'", chunks[5].getRecipientName()); + assertEquals("roy.wetherall@alfresco.com", chunks[5].getRecipientEmailAddress()); + + // Finally check on message + MAPIMessage msg = new MAPIMessage(multiple); + assertEquals(6, msg.getRecipientEmailAddressList().length); + assertEquals(6, msg.getRecipientNamesList().length); + + assertEquals("'Ashutosh Dandavate'", msg.getRecipientNamesList()[0]); + assertEquals("'Paul Holmes-Higgin'", msg.getRecipientNamesList()[1]); + assertEquals("'Mike Farman'", msg.getRecipientNamesList()[2]); + assertEquals("nickb@alfresco.com", msg.getRecipientNamesList()[3]); + assertEquals("nick.burch@alfresco.com", msg.getRecipientNamesList()[4]); + assertEquals("'Roy Wetherall'", msg.getRecipientNamesList()[5]); + + assertEquals("ashutosh.dandavate@alfresco.com", msg.getRecipientEmailAddressList()[0]); + assertEquals("paul.hh@alfresco.com", msg.getRecipientEmailAddressList()[1]); + assertEquals("mikef@alfresco.com", msg.getRecipientEmailAddressList()[2]); + assertEquals("nickb@alfresco.com", msg.getRecipientEmailAddressList()[3]); + assertEquals("nick.burch@alfresco.com", msg.getRecipientEmailAddressList()[4]); + assertEquals("roy.wetherall@alfresco.com", msg.getRecipientEmailAddressList()[5]); } public void testFindsNameId() throws IOException { diff --git a/src/testcases/org/apache/poi/util/TestStringUtil.java b/src/testcases/org/apache/poi/util/TestStringUtil.java index e05fe0611a..4f85929bd2 100644 --- a/src/testcases/org/apache/poi/util/TestStringUtil.java +++ b/src/testcases/org/apache/poi/util/TestStringUtil.java @@ -20,6 +20,8 @@ package org.apache.poi.util; import java.io.UnsupportedEncodingException; import java.text.NumberFormat; +import org.apache.poi.util.StringUtil.StringsIterator; + import junit.framework.TestCase; /** @@ -158,5 +160,43 @@ public final class TestStringUtil extends TestCase { return nf.format( num ); } + + public void testStringsIterator() { + StringsIterator i; + + + i = new StringsIterator(new String[0]); + assertFalse(i.hasNext()); + try { + i.next(); + fail(); + } catch(ArrayIndexOutOfBoundsException e) {} + + + i = new StringsIterator(new String[] {"1"}); + assertTrue(i.hasNext()); + assertEquals("1", i.next()); + + assertFalse(i.hasNext()); + try { + i.next(); + fail(); + } catch(ArrayIndexOutOfBoundsException e) {} + + + i = new StringsIterator(new String[] {"1","2","3"}); + assertTrue(i.hasNext()); + assertEquals("1", i.next()); + assertTrue(i.hasNext()); + assertEquals("2", i.next()); + assertTrue(i.hasNext()); + assertEquals("3", i.next()); + + assertFalse(i.hasNext()); + try { + i.next(); + fail(); + } catch(ArrayIndexOutOfBoundsException e) {} + } } diff --git a/test-data/hsmf/example_received_regular.msg b/test-data/hsmf/example_received_regular.msg new file mode 100644 index 0000000000000000000000000000000000000000..57c66b084e5757bab54341e762012f52f8ed58a5 GIT binary patch literal 53248 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 literal 0 HcmV?d00001 diff --git a/test-data/hsmf/example_received_unicode.msg b/test-data/hsmf/example_received_unicode.msg new file mode 100644 index 0000000000000000000000000000000000000000..08256f922f99a98b5492876ad1a518e648dcd13e GIT binary patch literal 56832 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 RzzBCl{$h&vU*}Z<{}0@NatHtb literal 0 HcmV?d00001 diff --git a/test-data/hsmf/example_sent_regular.msg b/test-data/hsmf/example_sent_regular.msg new file mode 100644 index 0000000000000000000000000000000000000000..0c1c3f6dac0fd3ec1b714d35626391ea7f6d0891 GIT binary patch literal 53248 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 E0H$Kq)c^nh literal 0 HcmV?d00001 diff --git a/test-data/hsmf/example_sent_unicode.msg b/test-data/hsmf/example_sent_unicode.msg new file mode 100644 index 0000000000000000000000000000000000000000..76aa32434d543344309958b12049a644245af447 GIT binary patch literal 53760 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