2841: Attachments are children of messages.

2692: Messages & Attachments show in DataSource tree
This commit is contained in:
Raman 2017-07-31 17:25:25 -04:00
parent 4fdea244af
commit a4dc97af6e
10 changed files with 185 additions and 60 deletions

View File

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

View File

@ -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<T> extends Keys<T> {
return new SlackFileNode(sf);
}
@Override
public AbstractContentNode<? extends Content> visit(BlackboardArtifact art) {
return new BlackboardArtifactNode(art);
}
@Override
protected AbstractContentNode<? extends Content> defaultVisit(SleuthkitVisitableItem di) {
throw new UnsupportedOperationException(NbBundle.getMessage(this.getClass(),

View File

@ -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<T extends Content> 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<T extends Content> extends ContentNode
@Override
public void setName(String name) {
throw new UnsupportedOperationException(
NbBundle.getMessage(this.getClass(), "AbstractContentNode.exception.cannotChangeSysName.msg"));
super.setName(name);
}
@Override

View File

@ -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<BlackboardArtifact> {
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<NodeProperty<? extends Object>> 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> T accept(ContentNodeVisitor<T> v) {
return v.visit(this);
}
}

View File

@ -43,6 +43,9 @@ interface ContentNodeVisitor<T> {
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<T> {
public T visit(SlackFileNode sfn) {
return defaultVisit(sfn);
}
@Override
public T visit(BlackboardArtifactNode bban) {
return defaultVisit(bban);
}
}
}

View File

@ -428,7 +428,7 @@ public class EmailExtracted implements AutopsyVisitableItem {
@Override
public boolean isLeafTypeNode() {
return true;
return false;
}
@Override

View File

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

View File

@ -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<Content> 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<Boolean> {
@ -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;
}
}
}

View File

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

View File

@ -295,11 +295,15 @@ public final class ThunderbirdMboxFileIngestModule implements FileIngestModule {
*/
private void processEmails(List<EmailMessage> emails, AbstractFile abstractFile) {
List<AbstractFile> 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<AbstractFile> handleAttachments(List<EmailMessage.Attachment> attachments, AbstractFile abstractFile) {
private List<AbstractFile> handleAttachments(List<EmailMessage.Attachment> attachments, AbstractFile abstractFile, BlackboardArtifact messageArtifact) {
List<AbstractFile> 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<BlackboardAttribute> 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) {