From a4dc97af6e21239ec56bc2006adb08a2da04bc87 Mon Sep 17 00:00:00 2001 From: Raman Date: Mon, 31 Jul 2017 17:25:25 -0400 Subject: [PATCH] 2841: Attachments are children of messages. 2692: Messages & Attachments show in DataSource tree --- .../casemodule/services/FileManager.java | 7 +- .../datamodel/AbstractContentChildren.java | 6 ++ .../datamodel/AbstractContentNode.java | 21 +++-- .../datamodel/BlackboardArtifactNode.java | 79 +++++++++++-------- .../autopsy/datamodel/ContentNodeVisitor.java | 8 ++ .../autopsy/datamodel/EmailExtracted.java | 2 +- .../directorytree/DataResultFilterNode.java | 54 ++++++++++++- .../DirectoryTreeFilterChildren.java | 27 ++++++- .../DirectoryTreeFilterNode.java | 17 +++- .../ThunderbirdMboxFileIngestModule.java | 24 +++--- 10 files changed, 185 insertions(+), 60 deletions(-) diff --git a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java index eb810e708c..3d48a422be 100644 --- a/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java +++ b/Core/src/org/sleuthkit/autopsy/casemodule/services/FileManager.java @@ -45,6 +45,7 @@ import org.sleuthkit.datamodel.LocalFilesDataSource; import org.sleuthkit.datamodel.TskDataException; import org.apache.commons.lang3.StringUtils; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.datamodel.AbstractContent; import org.sleuthkit.datamodel.CarvingResult; import org.sleuthkit.datamodel.TskData; @@ -295,7 +296,7 @@ public class FileManager implements Closeable { * @param atime The accessed time of the file. * @param mtime The modified time of the file. * @param isFile True if a file, false if a directory. - * @param parentFile The parent file from which the file was derived. + * @param parentObj The parent object from which the file was derived. * @param rederiveDetails The details needed to re-derive file (will be * specific to the derivation method), currently * unused. @@ -317,7 +318,7 @@ public class FileManager implements Closeable { long size, long ctime, long crtime, long atime, long mtime, boolean isFile, - AbstractFile parentFile, + Content parentObj, String rederiveDetails, String toolName, String toolVersion, String otherDetails, TskData.EncodingType encodingType) throws TskCoreException { if (null == caseDb) { @@ -325,7 +326,7 @@ public class FileManager implements Closeable { } return caseDb.addDerivedFile(fileName, localPath, size, ctime, crtime, atime, mtime, - isFile, parentFile, rederiveDetails, toolName, toolVersion, otherDetails, encodingType); + isFile, parentObj, rederiveDetails, toolName, toolVersion, otherDetails, encodingType); } /** diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java index 03066faa55..4e5e31c7cb 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentChildren.java @@ -25,6 +25,7 @@ import org.openide.util.NbBundle; import org.sleuthkit.autopsy.datamodel.FileTypes.FileTypesNode; import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.Accounts.AccountsRootNode; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.DerivedFile; import org.sleuthkit.datamodel.Directory; @@ -117,6 +118,11 @@ abstract class AbstractContentChildren extends Keys { return new SlackFileNode(sf); } + @Override + public AbstractContentNode visit(BlackboardArtifact art) { + return new BlackboardArtifactNode(art); + } + @Override protected AbstractContentNode defaultVisit(SleuthkitVisitableItem di) { throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(), diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java index 0a88cfd5dc..82a8130c23 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/AbstractContentNode.java @@ -21,8 +21,8 @@ package org.sleuthkit.autopsy.datamodel; import java.util.List; import java.util.logging.Level; -import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; +import org.openide.util.Lookup; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.TskCoreException; @@ -48,13 +48,23 @@ public abstract class AbstractContentNode extends ContentNode * @param content Underlying Content instances */ AbstractContentNode(T content) { - //TODO consider child factory for the content children - super(new ContentChildren(content), Lookups.singleton(content)); + this(content, Lookups.singleton(content) ); + } + + /** + * Handles aspects that depend on the Content object + * + * @param content Underlying Content instances + * @param lookup The Lookup object for the node. + */ + AbstractContentNode(T content, Lookup lookup) { + //TODO consider child factory for the content children + super(new ContentChildren(content), lookup); this.content = content; //super.setName(ContentUtils.getSystemName(content)); super.setName("content_" + Long.toString(content.getId())); //NON-NLS } - + /** * Return the content data associated with this node * @@ -66,8 +76,7 @@ public abstract class AbstractContentNode extends ContentNode @Override public void setName(String name) { - throw new UnsupportedOperationException( - NbBundle.getMessage(this.getClass(), "AbstractContentNode.exception.cannotChangeSysName.msg")); + super.setName(name); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java index f13aa441ff..0e1c7cb8aa 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/BlackboardArtifactNode.java @@ -25,6 +25,7 @@ import java.beans.PropertyChangeListener; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -34,6 +35,7 @@ import java.util.logging.Level; import java.util.stream.Collectors; import javax.swing.Action; import org.openide.nodes.Children; +import org.openide.nodes.Node; import org.openide.nodes.Sheet; import org.openide.util.Lookup; import org.openide.util.NbBundle; @@ -43,9 +45,11 @@ import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.BlackBoardArtifactTagDeletedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagAddedEvent; import org.sleuthkit.autopsy.casemodule.events.ContentTagDeletedEvent; +import static org.sleuthkit.autopsy.datamodel.DataModelActionsFactory.VIEW_IN_NEW_WINDOW; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import static org.sleuthkit.autopsy.datamodel.DisplayableItemNode.findLinked; +import org.sleuthkit.autopsy.directorytree.NewWindowViewAction; import org.sleuthkit.autopsy.timeline.actions.ViewArtifactInTimelineAction; import org.sleuthkit.autopsy.timeline.actions.ViewFileInTimelineAction; import org.sleuthkit.datamodel.AbstractFile; @@ -61,7 +65,7 @@ import org.sleuthkit.datamodel.TskCoreException; * Node wrapping a blackboard artifact object. This is generated from several * places in the tree. */ -public class BlackboardArtifactNode extends DisplayableItemNode { +public class BlackboardArtifactNode extends AbstractContentNode { private static final Logger LOGGER = Logger.getLogger(BlackboardArtifactNode.class.getName()); @@ -72,6 +76,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { private final BlackboardArtifact artifact; private Content associated = null; private List> customProperties; + /* * Artifact types which should have the full unique path of the associated * content as a property. @@ -130,22 +135,17 @@ public class BlackboardArtifactNode extends DisplayableItemNode { * @param iconPath icon to use for the artifact */ public BlackboardArtifactNode(BlackboardArtifact artifact, String iconPath) { - super(Children.LEAF, createLookup(artifact)); + super(artifact, createLookup(artifact)); this.artifact = artifact; // Look for associated Content i.e. the source file for the artifact - if (this.getLookup().lookupAll(Content.class).size() > 1) { - for (Content content : this.getLookup().lookupAll(Content.class)) { - if ( (content != null) && (!(content instanceof BlackboardArtifact)) ){ - this.associated = content; - break; - } + for (Content content : this.getLookup().lookupAll(Content.class)) { + if ( (content != null) && (!(content instanceof BlackboardArtifact)) ){ + this.associated = content; + break; } } - if (null == this.associated ) { - this.associated = this.getLookup().lookup(Content.class); - } this.setName(Long.toString(artifact.getArtifactID())); this.setDisplayName(); @@ -160,32 +160,18 @@ public class BlackboardArtifactNode extends DisplayableItemNode { * @param artifact artifact to encapsulate */ public BlackboardArtifactNode(BlackboardArtifact artifact) { - super(Children.LEAF, createLookup(artifact)); - - this.artifact = artifact; - // Look for associated Content - the source file for the artifact - if (this.getLookup().lookupAll(Content.class).size() > 1) { - for (Content content : this.getLookup().lookupAll(Content.class)) { - if ( (content != null) && (!(content instanceof BlackboardArtifact)) ){ - this.associated = content; - break; - } - } - } - if (null == this.associated ) { - this.associated = this.getLookup().lookup(Content.class); - } - this.setName(Long.toString(artifact.getArtifactID())); - this.setDisplayName(); - this.setIconBaseWithExtension(ExtractedContent.getIconFilePath(artifact.getArtifactTypeID())); //NON-NLS - Case.addPropertyChangeListener(pcl); + this(artifact, ExtractedContent.getIconFilePath(artifact.getArtifactTypeID())); } private void removeListeners() { Case.removePropertyChangeListener(pcl); } + public BlackboardArtifact getArtifact() { + return this.artifact; + } + @Override @NbBundle.Messages({ "BlackboardArtifactNode.getAction.errorTitle=Error getting actions", @@ -235,10 +221,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { */ private void setDisplayName() { String displayName = ""; //NON-NLS - if (associated != null) { - displayName = associated.getName(); - } - + // If this is a node for a keyword hit on an artifact, we set the // display name to be the artifact type name followed by " Artifact" // e.g. "Messages Artifact". @@ -262,9 +245,30 @@ public class BlackboardArtifactNode extends DisplayableItemNode { // Do nothing since the display name will be set to the file name. } } + + if (displayName.isEmpty() && artifact != null) { + displayName = artifact.getName(); + } + this.setDisplayName(displayName); + } + /** + * Return the name of the associated source file/content + * + * @return source file/content name + */ + public String getSrcName() { + + String srcName = ""; + if (associated != null) { + srcName = associated.getName(); + } + return srcName; + } + + @NbBundle.Messages({ "BlackboardArtifactNode.createSheet.artifactType.displayName=Artifact Type", "BlackboardArtifactNode.createSheet.artifactType.name=Artifact Type", @@ -288,7 +292,7 @@ public class BlackboardArtifactNode extends DisplayableItemNode { ss.put(new NodeProperty<>(NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.name"), NbBundle.getMessage(BlackboardArtifactNode.class, "BlackboardArtifactNode.createSheet.srcFile.displayName"), NO_DESCR, - this.getDisplayName())); + this.getSrcName())); if (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_INTERESTING_ARTIFACT_HIT.getTypeID()) { try { BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT)); @@ -579,4 +583,9 @@ public class BlackboardArtifactNode extends DisplayableItemNode { public String getItemType() { return getClass().getName(); } + + @Override + public T accept(ContentNodeVisitor v) { + return v.visit(this); + } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java index 6dbd8002ee..fbf31404a6 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/ContentNodeVisitor.java @@ -43,6 +43,9 @@ interface ContentNodeVisitor { T visit(LocalFileNode dfn); T visit(SlackFileNode sfn); + + T visit(BlackboardArtifactNode bban); + /** * Visitor with an implementable default behavior for all types. Override @@ -100,5 +103,10 @@ interface ContentNodeVisitor { public T visit(SlackFileNode sfn) { return defaultVisit(sfn); } + + @Override + public T visit(BlackboardArtifactNode bban) { + return defaultVisit(bban); + } } } diff --git a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java index ca4602e30a..ad740a5011 100644 --- a/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java +++ b/Core/src/org/sleuthkit/autopsy/datamodel/EmailExtracted.java @@ -428,7 +428,7 @@ public class EmailExtracted implements AutopsyVisitableItem { @Override public boolean isLeafTypeNode() { - return true; + return false; } @Override diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java index 21146610da..52b419731d 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DataResultFilterNode.java @@ -20,6 +20,7 @@ package org.sleuthkit.autopsy.directorytree; import java.awt.event.ActionEvent; import java.beans.PropertyVetoException; +import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; @@ -43,6 +44,7 @@ import org.sleuthkit.autopsy.actions.DeleteFileContentTagAction; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.ContextMenuExtensionPoint; import org.sleuthkit.autopsy.coreutils.Logger; +import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode.AbstractFilePropertyType; import org.sleuthkit.autopsy.datamodel.AbstractFsContentNode; import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; @@ -71,6 +73,8 @@ import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskException; import org.sleuthkit.datamodel.VirtualDirectory; import static org.sleuthkit.autopsy.directorytree.Bundle.*; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; +import org.sleuthkit.datamodel.TskCoreException; /** * A node used to wrap another node before passing it to the result viewers. The @@ -210,6 +214,26 @@ public class DataResultFilterNode extends FilterNode { return propertySets; } + /** + * Gets the display name for the wrapped node. + * + * OutlineView used in the DataResult table uses getDisplayName() to populate + * the first column, which is Source File. + * + * Hence this override to return the 'correct' displayName for the wrapped node. + * + * @return The display name for the node. + */ + @Override + public String getDisplayName() { + final Node orig = getOriginal(); + String name = orig.getDisplayName(); + if ((orig instanceof BlackboardArtifactNode)) { + name = ((BlackboardArtifactNode) orig).getSrcName(); + } + return name; + } + /** * Adds information about which child node of this node, if any, should be * selected. Can be null. @@ -248,16 +272,20 @@ public class DataResultFilterNode extends FilterNode { private boolean filterKnown; private boolean filterSlack; + private boolean filterArtifacts; // display message artifacts in the DataSource subtree /** * the constructor */ private DataResultFilterChildren(Node arg, ExplorerManager sourceEm) { super(arg); + + this.filterArtifacts = false; switch (SelectionContext.getSelectionContext(arg)) { case DATA_SOURCES: filterSlack = filterSlackFromDataSources; filterKnown = filterKnownFromDataSources; + filterArtifacts = true; break; case VIEWS: filterSlack = filterSlackFromViews; @@ -291,6 +319,16 @@ public class DataResultFilterNode extends FilterNode { return new Node[]{}; } } + + // filter out all non-message artifacts, if displaying the results from the Data Source tree + BlackboardArtifact art = key.getLookup().lookup(BlackboardArtifact.class); + if (art != null && filterArtifacts) { + if ( (art.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) && + (art.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) { + return new Node[]{}; + } + } + return new Node[]{new DataResultFilterNode(key, sourceEm, filterKnown, filterSlack)}; } } @@ -459,8 +497,20 @@ public class DataResultFilterNode extends FilterNode { @Override public AbstractAction visit(BlackboardArtifactNode ban) { - return new ViewContextAction( - NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewInDir.text"), ban); + BlackboardArtifact artifact = ban.getArtifact(); + try { + if ( (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) || + (artifact.getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) { + if (artifact.hasChildren()) { + return openChild(ban); + } + } + } + catch (TskCoreException ex) { + LOGGER.log(Level.SEVERE, MessageFormat.format("Error getting children from blackboard artifact{0}.", artifact.getArtifactID()), ex); //NON-NLS + } + return new ViewContextAction( + NbBundle.getMessage(this.getClass(), "DataResultFilterNode.action.viewInDir.text"), ban); } @Override diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java index 2b10d57f09..7ba8fd537c 100644 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterChildren.java @@ -26,6 +26,7 @@ import org.sleuthkit.autopsy.datamodel.DirectoryNode; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; import org.sleuthkit.autopsy.datamodel.AbstractAbstractFileNode; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNode; import org.sleuthkit.autopsy.datamodel.DisplayableItemNodeVisitor; import org.sleuthkit.autopsy.datamodel.FileNode; @@ -36,6 +37,7 @@ import org.sleuthkit.autopsy.datamodel.SlackFileNode; import org.sleuthkit.autopsy.datamodel.VirtualDirectoryNode; import org.sleuthkit.autopsy.datamodel.VolumeNode; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.LayoutFile; @@ -198,7 +200,7 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { List derivedChildren = node.getContentChildren(); //child of a file, must be a (derived) file too for (Content childContent : derivedChildren) { - if (((AbstractFile) childContent).isDir()) { + if ((childContent instanceof AbstractFile) && ((AbstractFile) childContent).isDir()) { return false; } else { try { @@ -249,6 +251,16 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { return defaultVisit(ft); } + @Override + public Boolean visit(BlackboardArtifactNode bbafn) { + // Only show Message arttifacts with children + if ( (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) || + (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) { + return bbafn.hasContentChildren(); + } + + return false; + } } private static class ShowItemVisitor extends DisplayableItemNodeVisitor.Default { @@ -292,10 +304,23 @@ class DirectoryTreeFilterChildren extends FilterNode.Children { //return vdn.hasContentChildren(); } + @Override public Boolean visit(FileTypesNode fileTypes) { return defaultVisit(fileTypes); } + + @Override + public Boolean visit(BlackboardArtifactNode bbafn) { + + // Only show Message arttifacts with children + if ( (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_EMAIL_MSG.getTypeID()) || + (bbafn.getArtifact().getArtifactTypeID() == ARTIFACT_TYPE.TSK_MESSAGE.getTypeID()) ) { + return bbafn.hasContentChildren(); + } + + return false; + } } } diff --git a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java index 59f5bf4406..81a704c57a 100755 --- a/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java +++ b/Core/src/org/sleuthkit/autopsy/directorytree/DirectoryTreeFilterNode.java @@ -25,14 +25,17 @@ import java.util.logging.Level; import javax.swing.Action; import org.openide.nodes.FilterNode; import org.openide.nodes.Node; +import org.openide.util.Exceptions; import org.openide.util.NbBundle; import org.openide.util.lookup.Lookups; import org.openide.util.lookup.ProxyLookup; import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.datamodel.AbstractContentNode; +import org.sleuthkit.autopsy.datamodel.BlackboardArtifactNode; import org.sleuthkit.autopsy.ingest.runIngestModuleWizard.RunIngestModulesAction; import org.sleuthkit.datamodel.AbstractFile; +import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Directory; import org.sleuthkit.datamodel.Image; @@ -77,7 +80,7 @@ class DirectoryTreeFilterNode extends FilterNode { String name = orig.getDisplayName(); if (orig instanceof AbstractContentNode) { AbstractFile file = getLookup().lookup(AbstractFile.class); - if (file != null) { + if ((file != null) && (false == (orig instanceof BlackboardArtifactNode)) ){ try { int numVisibleChildren = getVisibleChildCount(file); @@ -92,6 +95,15 @@ class DirectoryTreeFilterNode extends FilterNode { logger.log(Level.SEVERE, "Error getting children count to display for file: " + file, ex); //NON-NLS } } + else if (orig instanceof BlackboardArtifactNode) { + BlackboardArtifact artifact = ((BlackboardArtifactNode) orig).getArtifact(); + try { + int numAttachments = artifact.getChildrenCount(); + name = name + " \u200E(\u200E" + numAttachments + ")\u200E"; //NON-NLS + } catch (TskCoreException ex) { + logger.log(Level.SEVERE, "Error getting chidlren count for atifact: " + artifact, ex); //NON-NLS + } + } } return name; } @@ -115,7 +127,7 @@ class DirectoryTreeFilterNode extends FilterNode { if (purgeKnownFiles || purgeSlackFiles) { // Purge known and/or slack files from the file count for (int i = 0; i < childList.size(); i++) { - Content child = (Content) childList.get(i); + Content child = childList.get(i); if (child instanceof AbstractFile) { AbstractFile childFile = (AbstractFile) child; if ((purgeKnownFiles && childFile.getKnown() == TskData.FileKnown.KNOWN) @@ -125,6 +137,7 @@ class DirectoryTreeFilterNode extends FilterNode { } } } + return numVisibleChildren; } diff --git a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java index 8c0750bbcd..3b7d70d929 100644 --- a/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java +++ b/thunderbirdparser/src/org/sleuthkit/autopsy/thunderbirdparser/ThunderbirdMboxFileIngestModule.java @@ -295,11 +295,15 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { */ private void processEmails(List emails, AbstractFile abstractFile) { List derivedFiles = new ArrayList<>(); + + + for (EmailMessage email : emails) { - if (email.hasAttachment()) { - derivedFiles.addAll(handleAttachments(email.getAttachments(), abstractFile)); + BlackboardArtifact msgArtifact = addArtifact(email, abstractFile); + + if ((msgArtifact != null) && (email.hasAttachment())) { + derivedFiles.addAll(handleAttachments(email.getAttachments(), abstractFile, msgArtifact )); } - addArtifact(email, abstractFile); } if (derivedFiles.isEmpty() == false) { @@ -320,7 +324,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * * @return */ - private List handleAttachments(List attachments, AbstractFile abstractFile) { + private List handleAttachments(List attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact) { List files = new ArrayList<>(); for (EmailMessage.Attachment attach : attachments) { String filename = attach.getName(); @@ -334,7 +338,7 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { try { DerivedFile df = fileManager.addDerivedFile(filename, relPath, - size, cTime, crTime, aTime, mTime, true, abstractFile, "", + size, cTime, crTime, aTime, mTime, true, messageArtifact, "", EmailParserModuleFactory.getModuleName(), EmailParserModuleFactory.getModuleVersion(), "", encodingType); files.add(df); } catch (TskCoreException ex) { @@ -356,7 +360,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { * @param abstractFile */ @Messages({"ThunderbirdMboxFileIngestModule.addArtifact.indexError.message=Failed to index email message detected artifact for keyword search."}) - private void addArtifact(EmailMessage email, AbstractFile abstractFile) { + private BlackboardArtifact addArtifact(EmailMessage email, AbstractFile abstractFile) { + BlackboardArtifact bbart = null; List bbattributes = new ArrayList<>(); String to = email.getRecipients(); String cc = email.getCc(); @@ -414,12 +419,9 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { if (rtf.isEmpty() == false) { bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_EMAIL_CONTENT_RTF, EmailParserModuleFactory.getModuleName(), rtf)); } - - - try { - BlackboardArtifact bbart; + bbart = abstractFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_EMAIL_MSG); bbart.addAttributes(bbattributes); @@ -433,6 +435,8 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule { } catch (TskCoreException ex) { logger.log(Level.WARNING, null, ex); } + + return bbart; } void postErrorMessage(String subj, String details) {