This commit is contained in:
Greg DiCristofaro 2022-01-20 14:42:35 -05:00
parent 1f7a19bb11
commit 1c9d647d0b
3 changed files with 197 additions and 131 deletions

View File

@ -32,7 +32,6 @@ import java.util.EnumSet;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -50,7 +49,6 @@ import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener; import javax.swing.event.PopupMenuListener;
import javax.swing.tree.TreeSelectionModel; import javax.swing.tree.TreeSelectionModel;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
import org.openide.explorer.ExplorerUtils; import org.openide.explorer.ExplorerUtils;
import org.openide.explorer.view.BeanTreeView; import org.openide.explorer.view.BeanTreeView;
@ -89,8 +87,7 @@ import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
import org.sleuthkit.autopsy.corecomponents.SelectionResponder; import org.sleuthkit.autopsy.corecomponents.SelectionResponder;
import org.sleuthkit.autopsy.datamodel.CreditCards; import org.sleuthkit.autopsy.datamodel.CreditCards;
import org.sleuthkit.autopsy.datamodel.accounts.BINRange; import org.sleuthkit.autopsy.datamodel.accounts.BINRange;
import org.sleuthkit.autopsy.mainui.datamodel.EmailsDAO; import org.sleuthkit.autopsy.mainui.datamodel.MainDAO;
import org.sleuthkit.autopsy.mainui.nodes.AnalysisResultTypeFactory;
import org.sleuthkit.autopsy.mainui.nodes.AnalysisResultTypeFactory.KeywordSetFactory; import org.sleuthkit.autopsy.mainui.nodes.AnalysisResultTypeFactory.KeywordSetFactory;
import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo.BlackboardArtifactNodeSelectionInfo; import org.sleuthkit.autopsy.mainui.nodes.ChildNodeSelectionInfo.BlackboardArtifactNodeSelectionInfo;
import org.sleuthkit.autopsy.mainui.nodes.TreeNode; import org.sleuthkit.autopsy.mainui.nodes.TreeNode;
@ -1564,19 +1561,14 @@ public final class DirectoryTreeTopComponent extends TopComponent implements Dat
} }
} }
Node parentNode = emailMsgRootNode; Node parentNode = null;
Node[] childNodes = emailMsgRootNode.getChildren().getNodes(true); Node[] childNodes = emailMsgRootNode.getChildren().getNodes(true);
while (childNodes != null) { while (childNodes != null) {
for (Node child : childNodes) { for (Node child : childNodes) {
if (Objects.equals(path, child.getName())) { if (MainDAO.getInstance().getEmailsDAO().getNextSubFolder(child.getName(), path).isPresent()) {
return child;
} else if ((StringUtils.isBlank(path) && StringUtils.isBlank(child.getName()))
|| (StringUtils.isNotBlank(path) && path.startsWith(child.getName()))) {
parentNode = child; parentNode = child;
childNodes = parentNode.getChildren().getNodes(true); childNodes = parentNode.getChildren().getNodes(true);
break; break;
} else {
return null;
} }
} }
} }

View File

@ -37,7 +37,6 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.python.icu.text.MessageFormat; import org.python.icu.text.MessageFormat;
@ -73,7 +72,6 @@ public class EmailsDAO extends AbstractDAO {
private static final Logger logger = Logger.getLogger(EmailsDAO.class.getName()); private static final Logger logger = Logger.getLogger(EmailsDAO.class.getName());
private static final String PATH_DELIMITER = "\\"; private static final String PATH_DELIMITER = "\\";
private static final String REGEX_PATH_DELIMITER = "\\\\";
private static final String ESCAPE_CHAR = "$"; private static final String ESCAPE_CHAR = "$";
private final Cache<SearchParams<EmailSearchParams>, SearchResultsDTO> searchParamsCache private final Cache<SearchParams<EmailSearchParams>, SearchResultsDTO> searchParamsCache
@ -104,15 +102,41 @@ public class EmailsDAO extends AbstractDAO {
return searchParamsCache.get(emailSearchParams, () -> fetchEmailMessageDTOs(emailSearchParams)); return searchParamsCache.get(emailSearchParams, () -> fetchEmailMessageDTOs(emailSearchParams));
} }
/**
* Sets the values of a results view prepared statement used in
* fetchEmailMessageDTOs.
*
* @param preparedStatement The prepared statement.
* @param normalizedPath The query path indicated by TSK_PATH.
* @param dataSourceId The data source id.
*
* @throws TskCoreException
*/
private void setResultsViewPreparedStatement(CaseDbPreparedStatement preparedStatement, String normalizedPath, Long dataSourceId) throws TskCoreException {
int paramIdx = 0;
if (normalizedPath != null) {
preparedStatement.setString(++paramIdx, normalizedPath);
String noEndingSlash = normalizedPath.endsWith(PATH_DELIMITER) ? normalizedPath.substring(0, normalizedPath.length() - 1) : normalizedPath;
preparedStatement.setString(++paramIdx, noEndingSlash);
}
if (dataSourceId != null) {
preparedStatement.setLong(++paramIdx, dataSourceId);
}
}
private SearchResultsDTO fetchEmailMessageDTOs(SearchParams<EmailSearchParams> searchParams) throws NoCurrentCaseException, TskCoreException, SQLException, IllegalStateException { private SearchResultsDTO fetchEmailMessageDTOs(SearchParams<EmailSearchParams> searchParams) throws NoCurrentCaseException, TskCoreException, SQLException, IllegalStateException {
// get current page of communication accounts results // get current page of communication accounts results
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
Blackboard blackboard = skCase.getBlackboard(); Blackboard blackboard = skCase.getBlackboard();
String pathWhereStatement = StringUtils.isBlank(searchParams.getParamData().getFolder()) String normalizedPath = getNormalizedPath(searchParams.getParamData().getFolder());
? "AND attr.value_text IS NULL OR attr.value_text NOT LIKE '/%' ESCAPE '" + ESCAPE_CHAR + " \n" String pathWhereStatement = (normalizedPath == null)
: "AND attr.value_text LIKE ? ESCAPE '" + ESCAPE_CHAR + "' \n"; // if searching for result without any folder, find items that are not prefixed with '\' or aren't null
? "AND attr.value_text IS NULL OR attr.value_text NOT LIKE '" + PATH_DELIMITER + "%' ESCAPE '" + ESCAPE_CHAR + " \n"
// the path should start with the prescribed folder
: "AND (attr.value_text = ? OR attr.value_text = ?)\n";
String baseQuery = "FROM blackboard_artifacts art \n" String baseQuery = "FROM blackboard_artifacts art \n"
+ "LEFT JOIN blackboard_attributes attr ON attr.artifact_id = art.artifact_id \n" + "LEFT JOIN blackboard_attributes attr ON attr.artifact_id = art.artifact_id \n"
@ -134,19 +158,9 @@ public class EmailsDAO extends AbstractDAO {
List<Long> pagedIds = new ArrayList<>(); List<Long> pagedIds = new ArrayList<>();
AtomicReference<Long> totalCount = new AtomicReference<>(0L); AtomicReference<Long> totalCount = new AtomicReference<>(0L);
// query for counts
try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(countsQuery)) { try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(countsQuery)) {
setResultsViewPreparedStatement(preparedStatement, searchParams.getParamData().getFolder(), searchParams.getParamData().getDataSourceId());
int paramIdx = 0;
if (searchParams.getParamData().getFolder() != null) {
preparedStatement.setString(++paramIdx, MessageFormat.format("%{0}%",
SubDAOUtils.likeEscape(searchParams.getParamData().getFolder() + "%", ESCAPE_CHAR)
));
}
if (searchParams.getParamData().getDataSourceId() != null) {
preparedStatement.setLong(++paramIdx, searchParams.getParamData().getDataSourceId());
}
getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> {
try { try {
if (resultSet.next()) { if (resultSet.next()) {
@ -159,21 +173,11 @@ public class EmailsDAO extends AbstractDAO {
}); });
} }
// if there is a result count, get paged artifact ids
List<BlackboardArtifact> allArtifacts = Collections.emptyList(); List<BlackboardArtifact> allArtifacts = Collections.emptyList();
if (totalCount.get() > 0) { if (totalCount.get() > 0) {
try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(itemsQuery)) { try (CaseDbPreparedStatement preparedStatement = getCase().getCaseDbAccessManager().prepareSelect(itemsQuery)) {
setResultsViewPreparedStatement(preparedStatement, searchParams.getParamData().getFolder(), searchParams.getParamData().getDataSourceId());
int paramIdx = 0;
if (searchParams.getParamData().getFolder() != null) {
preparedStatement.setString(++paramIdx, MessageFormat.format("%{0}%",
SubDAOUtils.likeEscape(searchParams.getParamData().getFolder(), ESCAPE_CHAR)
));
}
if (searchParams.getParamData().getDataSourceId() != null) {
preparedStatement.setLong(++paramIdx, searchParams.getParamData().getDataSourceId());
}
getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> { getCase().getCaseDbAccessManager().select(preparedStatement, (resultSet) -> {
try { try {
while (resultSet.next()) { while (resultSet.next()) {
@ -200,78 +204,139 @@ public class EmailsDAO extends AbstractDAO {
tableData.rows, searchParams.getStartItem(), totalCount.get()); tableData.rows, searchParams.getStartItem(), totalCount.get());
} }
/**
* Converts a list of data artifacts gathered using
* Blackboard.getDataArtifactsWhere into a list of blackboard artifacts.
*
* @param blackboard The TSK blackboard.
* @param whereClause The where clause to use with
* Blackboard.getDataArtifactsWhere.
*
* @return The list of BlackboardArtifacts.
*
* @throws TskCoreException
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private List<BlackboardArtifact> getDataArtifactsAsBBA(Blackboard blackboard, String whereClause) throws TskCoreException { private List<BlackboardArtifact> getDataArtifactsAsBBA(Blackboard blackboard, String whereClause) throws TskCoreException {
return (List<BlackboardArtifact>) (List<? extends BlackboardArtifact>) blackboard.getDataArtifactsWhere(whereClause); return (List<BlackboardArtifact>) (List<? extends BlackboardArtifact>) blackboard.getDataArtifactsWhere(whereClause);
} }
/**
* Determines the last non-blank folder segment.
*
* @param fullPath The full path taken from a TSK_PATH.
*
* @return The last non-blank folder segment.
*/
private static String getLastFolderSegment(String fullPath) { private static String getLastFolderSegment(String fullPath) {
if (StringUtils.isNotBlank(fullPath)) { // getNormalizedPath should remove any trailing whitespace or path delimiters,
String[] folderPieces = fullPath.split(REGEX_PATH_DELIMITER); // so take the last index of the path delimiter if it exists, and use everything after that.
for (int i = folderPieces.length - 1; i >= 0; i--) { String normalizedPath = getNormalizedPath(fullPath);
if (StringUtils.isNotBlank(folderPieces[i])) { if (normalizedPath == null) {
return folderPieces[i].trim();
}
}
}
return null; return null;
} }
if (normalizedPath.length() > 1) {
int lastIdx = normalizedPath.lastIndexOf(PATH_DELIMITER, normalizedPath.length() - 1);
if (lastIdx >= 0) {
return normalizedPath.substring(lastIdx + 1, normalizedPath.length() - 1);
}
}
return "";
}
/**
* Returns the folder display name based on the folder. If blank, returns
* Default folder string.
*
* @param folder The folder.
*
* @return The folder display name.
*/
@Messages({"EmailsDAO_getFolderDisplayName_defaultName=[Default]"}) @Messages({"EmailsDAO_getFolderDisplayName_defaultName=[Default]"})
public static String getFolderDisplayName(String folder) { public static String getFolderDisplayName(String folder) {
return StringUtils.isBlank(folder) return folder == null
? Bundle.EmailsDAO_getFolderDisplayName_defaultName() ? Bundle.EmailsDAO_getFolderDisplayName_defaultName()
: folder; : folder;
} }
/**
* Normalizes the path. If path is blank or does not start with a path
* delimiter, return an empty string. Otherwise, remove all trailing
* whitespace and path delimiters.
*
* @param origPath The original path.
*
* @return The normalized path.
*/
private static String getNormalizedPath(String origPath) { private static String getNormalizedPath(String origPath) {
String safePath = StringUtils.defaultString(origPath); if (origPath == null || !origPath.startsWith(PATH_DELIMITER)) {
if (StringUtils.isBlank(safePath)) { return null;
return "";
} }
safePath = safePath.trim(); if (!origPath.endsWith(PATH_DELIMITER)) {
if (!safePath.endsWith(PATH_DELIMITER)) { origPath = origPath + PATH_DELIMITER;
safePath = safePath + PATH_DELIMITER;
} }
return safePath; return origPath;
} }
/**
* Creates a tree item dto with the given parameters.
*
* @param fullPath The full TSK_PATH path.
* @param dataSourceId The data source object id.
* @param count The count to display.
*
* @return The tree item dto.
*/
public TreeItemDTO<EmailSearchParams> createEmailTreeItem(String fullPath, Long dataSourceId, TreeDisplayCount count) { public TreeItemDTO<EmailSearchParams> createEmailTreeItem(String fullPath, Long dataSourceId, TreeDisplayCount count) {
return createEmailTreeItem(fullPath, getLastFolderSegment(fullPath), dataSourceId, count); String normalizedPath = getNormalizedPath(fullPath);
} String lastSegment = getLastFolderSegment(fullPath);
String displayName = getFolderDisplayName(lastSegment);
public TreeItemDTO<EmailSearchParams> createEmailTreeItem(String fullPath, String folderName, Long dataSourceId, TreeDisplayCount count) {
return new TreeItemDTO<>( return new TreeItemDTO<>(
EmailSearchParams.getTypeId(), EmailSearchParams.getTypeId(),
new EmailSearchParams(dataSourceId, fullPath), new EmailSearchParams(dataSourceId, normalizedPath),
folderName, normalizedPath == null ? 0 : normalizedPath,
getFolderDisplayName(folderName), displayName,
count count
); );
} }
public Optional<String> getNextSubFolder(String folderParent, String folder) { /**
String normalizedParent = folderParent == null ? null : getNormalizedPath(folderParent); * Returns the next relevant subfolder (the full path) after the parent path
String normalizedFolder = folder == null ? null : getNormalizedPath(folder); * or empty if child path is not a sub path of parent path path.
*
* @param parentPath The parent path.
* @param childPath The child path.
*
* @return The next subfolder or empty.
*/
public Optional<String> getNextSubFolder(String parentPath, String childPath) {
String normalizedParent = getNormalizedPath(parentPath);
String normalizedChild = getNormalizedPath(childPath);
if (normalizedParent == null || normalizedFolder.startsWith(normalizedParent)) { if (normalizedChild == null) {
if (normalizedFolder == null) { return (normalizedParent == null)
return Optional.of(null); ? Optional.of(null)
} else { : Optional.empty();
int nextDelim = normalizedFolder.indexOf(PATH_DELIMITER, normalizedParent.length());
if (nextDelim >= 0) {
return Optional.of(normalizedFolder.substring(normalizedParent.length(), nextDelim));
} else {
return Optional.of(normalizedFolder.substring(normalizedParent.length()));
} }
if (normalizedParent == null) {
normalizedParent = PATH_DELIMITER;
}
// ensure that child is a sub path of parent
if (normalizedChild.startsWith(normalizedParent)) {
int nextDelimiter = normalizedChild.indexOf(PATH_DELIMITER, normalizedParent.length());
return nextDelimiter >= 0
? Optional.of(getNormalizedPath(normalizedChild.substring(0, nextDelimiter + 1)))
: Optional.of(normalizedChild);
} }
} else {
return Optional.empty(); return Optional.empty();
} }
}
/** /**
* Returns sql to query for email counts. * Returns sql to query for email counts.
@ -302,13 +367,18 @@ public class EmailsDAO extends AbstractDAO {
String substringFolderSql; String substringFolderSql;
String folderWhereStatement; String folderWhereStatement;
if (StringUtils.isBlank(folder)) { if (folder == null) {
substringFolderSql = "CASE WHEN p.path LIKE '" + PATH_DELIMITER + "%' ESCAPE '" + ESCAPE_CHAR + "' THEN SUBSTR(p.path, 2) ELSE '' END"; substringFolderSql = "CASE WHEN p.path LIKE '" + PATH_DELIMITER + "%' ESCAPE '" + ESCAPE_CHAR + "' THEN SUBSTR(p.path, 2) ELSE NULL END";
folderWhereStatement = ""; folderWhereStatement = "";
} else { } else {
// if exact match, // if exact match,
substringFolderSql = "CASE WHEN (p.path = ? OR p.path = ?) THEN NULL ELSE SUBSTR(p.path, LENGTH(?) + 1) END"; substringFolderSql = "CASE\n"
folderWhereStatement = " WHERE (p.path LIKE ? ESCAPE '" + ESCAPE_CHAR + "' OR p.path = ?)\n"; + " WHEN p.path LIKE ? THEN \n"
+ " SUBSTR(p.path, LENGTH(?))\n"
+ " ELSE\n"
+ " NULL\n"
+ "END";
folderWhereStatement = " WHERE (p.path = ? OR p.path LIKE ? ESCAPE '" + ESCAPE_CHAR + "')\n";
} }
String query = "\n MAX(grouped_res.folder) AS folder\n" String query = "\n MAX(grouped_res.folder) AS folder\n"
@ -348,9 +418,10 @@ public class EmailsDAO extends AbstractDAO {
* Returns the accounts and their counts in the current data source if a * Returns the accounts and their counts in the current data source if a
* data source id is provided or all accounts if data source id is null. * data source id is provided or all accounts if data source id is null.
* *
* @param dataSourceId The data source id or null for no data source filter. * @param dataSourceId The data source id or null for no data source
* @param folder The email folder parent (using '\' as prefix, suffix, * filter.
* and delimiter). If null, root level folders * @param normalizedPath The email folder parent (using '\' as prefix,
* suffix, and delimiter). If null, root level folders
* *
* @return The results. * @return The results.
* *
@ -358,15 +429,12 @@ public class EmailsDAO extends AbstractDAO {
*/ */
public TreeResultsDTO<EmailSearchParams> getEmailCounts(Long dataSourceId, String folder) throws ExecutionException { public TreeResultsDTO<EmailSearchParams> getEmailCounts(Long dataSourceId, String folder) throws ExecutionException {
// folder ending with slash if not null String normalizedParent = getNormalizedPath(folder);
String endSlashFolder = (folder != null && !folder.endsWith(PATH_DELIMITER))
? folder + PATH_DELIMITER
: folder;
// a series of full folder paths prefixed and delimiter of '\' (no suffix) // a series of full folder paths prefixed and delimiter of '\' (no suffix)
Set<String> indeterminateTypes = this.emailCounts.getEnqueued().stream() Set<String> indeterminateTypes = this.emailCounts.getEnqueued().stream()
.filter(evt -> (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId))) .filter(evt -> (dataSourceId == null || Objects.equals(evt.getDataSourceId(), dataSourceId)))
.map(evt -> getNextSubFolder(folder, evt.getFolder())) .map(evt -> getNextSubFolder(normalizedParent, evt.getFolder()))
.filter(opt -> opt.isPresent()) .filter(opt -> opt.isPresent())
.map(opt -> opt.get()) .map(opt -> opt.get())
.collect(Collectors.toSet()); .collect(Collectors.toSet());
@ -374,16 +442,20 @@ public class EmailsDAO extends AbstractDAO {
String query = null; String query = null;
try { try {
SleuthkitCase skCase = getCase(); SleuthkitCase skCase = getCase();
query = getFolderChildrenSql(skCase.getDatabaseType(), endSlashFolder, dataSourceId); query = getFolderChildrenSql(skCase.getDatabaseType(), normalizedParent, dataSourceId);
try (CaseDbPreparedStatement preparedStatement = skCase.getCaseDbAccessManager().prepareSelect(query)) { try (CaseDbPreparedStatement preparedStatement = skCase.getCaseDbAccessManager().prepareSelect(query)) {
int paramIdx = 0; int paramIdx = 0;
if (folder != null) { if (normalizedParent != null) {
preparedStatement.setString(++paramIdx, folder); String likeStatement = SubDAOUtils.likeEscape(normalizedParent, ESCAPE_CHAR) + "%";
preparedStatement.setString(++paramIdx, endSlashFolder); String normalizedWithoutSlash = normalizedParent.endsWith(PATH_DELIMITER)
preparedStatement.setString(++paramIdx, endSlashFolder); ? normalizedParent.substring(0, normalizedParent.length() - 1)
preparedStatement.setString(++paramIdx, SubDAOUtils.likeEscape(endSlashFolder, ESCAPE_CHAR) + "%"); : normalizedParent;
preparedStatement.setString(++paramIdx, folder);
preparedStatement.setString(++paramIdx, likeStatement);
preparedStatement.setString(++paramIdx, normalizedParent);
preparedStatement.setString(++paramIdx, normalizedWithoutSlash);
preparedStatement.setString(++paramIdx, likeStatement);
} }
if (dataSourceId != null) { if (dataSourceId != null) {
@ -396,23 +468,23 @@ public class EmailsDAO extends AbstractDAO {
try { try {
while (resultSet.next()) { while (resultSet.next()) {
String rsFolderSegment = resultSet.getString("folder"); String rsFolderSegment = resultSet.getString("folder");
// if blank returned, assume it belongs to base results; don't provide ending slash
String rsPath; String rsPath;
if (StringUtils.isNotBlank(folder) && StringUtils.isNotBlank(rsFolderSegment)) { if (normalizedParent != null && rsFolderSegment != null) {
rsPath = endSlashFolder + rsFolderSegment; // both the parent path and next folder segment are present
} else if (StringUtils.isBlank(folder) && StringUtils.isBlank(rsFolderSegment)) { rsPath = getNormalizedPath(normalizedParent + PATH_DELIMITER + rsFolderSegment + PATH_DELIMITER);
rsPath = ""; } else if (rsFolderSegment == null) {
} else if (StringUtils.isNotBlank(folder)) { // the folder segment is not present
rsPath = folder; rsPath = getNormalizedPath(normalizedParent);
} else { } else {
rsPath = PATH_DELIMITER + rsFolderSegment; // the normalized parent is not present but the folder segment is
rsPath = getNormalizedPath(PATH_DELIMITER + rsFolderSegment + PATH_DELIMITER);
} }
TreeDisplayCount treeDisplayCount = indeterminateTypes.contains(rsPath) TreeDisplayCount treeDisplayCount = indeterminateTypes.contains(rsPath)
? TreeDisplayCount.INDETERMINATE ? TreeDisplayCount.INDETERMINATE
: TreeResultsDTO.TreeDisplayCount.getDeterminate(resultSet.getLong("count")); : TreeResultsDTO.TreeDisplayCount.getDeterminate(resultSet.getLong("count"));
accumulatedData.add(createEmailTreeItem(rsPath, rsFolderSegment, dataSourceId, treeDisplayCount)); accumulatedData.add(createEmailTreeItem(rsPath, dataSourceId, treeDisplayCount));
} }
} catch (SQLException ex) { } catch (SQLException ex) {
throw new IllegalStateException("A sql exception occurred.", ex); throw new IllegalStateException("A sql exception occurred.", ex);
@ -420,7 +492,7 @@ public class EmailsDAO extends AbstractDAO {
}); });
// if only one item of this type, don't show children // if only one item of this type, don't show children
if (accumulatedData.size() == 1 && StringUtils.isBlank(accumulatedData.get(0).getId().toString())) { if (accumulatedData.size() == 1 && accumulatedData.get(0).getId() == null) {
return new TreeResultsDTO<>(Collections.emptyList()); return new TreeResultsDTO<>(Collections.emptyList());
} else { } else {
// return results // return results
@ -431,7 +503,7 @@ public class EmailsDAO extends AbstractDAO {
} catch (SQLException | NoCurrentCaseException | TskCoreException | IllegalStateException ex) { } catch (SQLException | NoCurrentCaseException | TskCoreException | IllegalStateException ex) {
throw new ExecutionException( throw new ExecutionException(
MessageFormat.format("An error occurred while fetching email counts for folder: {0} and sql: \n{1}", MessageFormat.format("An error occurred while fetching email counts for folder: {0} and sql: \n{1}",
folder == null ? "<null>" : folder, normalizedParent == null ? "<null>" : normalizedParent,
query == null ? "<null>" : query), query == null ? "<null>" : query),
ex); ex);
} }
@ -480,7 +552,7 @@ public class EmailsDAO extends AbstractDAO {
try { try {
if (art.getType().getTypeID() == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) { if (art.getType().getTypeID() == BlackboardArtifact.Type.TSK_EMAIL_MSG.getTypeID()) {
BlackboardAttribute attr = art.getAttribute(BlackboardAttribute.Type.TSK_PATH); BlackboardAttribute attr = art.getAttribute(BlackboardAttribute.Type.TSK_PATH);
String folder = attr == null ? null : attr.getValueString(); String folder = attr == null ? null : getNormalizedPath(attr.getValueString());
emailMap emailMap
.computeIfAbsent(folder, (k) -> new HashSet<>()) .computeIfAbsent(folder, (k) -> new HashSet<>())
.add(art.getDataSourceObjectID()); .add(art.getDataSourceObjectID());
@ -531,8 +603,9 @@ public class EmailsDAO extends AbstractDAO {
private boolean isEmailInvalidating(EmailSearchParams parameters, DAOEvent evt) { private boolean isEmailInvalidating(EmailSearchParams parameters, DAOEvent evt) {
if (evt instanceof EmailEvent) { if (evt instanceof EmailEvent) {
EmailEvent emailEvt = (EmailEvent) evt; EmailEvent emailEvt = (EmailEvent) evt;
return Objects.equals(getNormalizedPath(parameters.getFolder()), getNormalizedPath(emailEvt.getFolder())) // determines if sub folder or not. if equivalent, will return present
&& (parameters.getDataSourceId() == null || Objects.equals(parameters.getDataSourceId(), emailEvt.getDataSourceId())); return (getNextSubFolder(parameters.getFolder(), emailEvt.getFolder()).isPresent()
&& (parameters.getDataSourceId() == null || Objects.equals(parameters.getDataSourceId(), emailEvt.getDataSourceId())));
} else { } else {
return false; return false;

View File

@ -298,7 +298,8 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
@Override @Override
protected TreeNode<EmailSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> rowData) { protected TreeNode<EmailSearchParams> createNewNode(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> rowData) {
return new EmailNode(rowData); boolean isLeafNode = Objects.equals(rowData.getSearchParams().getFolder(), this.folderParent);
return new EmailNode(rowData, isLeafNode);
} }
@Override @Override
@ -328,11 +329,11 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
@Override @Override
public int compare(TreeItemDTO<? extends EmailSearchParams> o1, TreeItemDTO<? extends EmailSearchParams> o2) { public int compare(TreeItemDTO<? extends EmailSearchParams> o1, TreeItemDTO<? extends EmailSearchParams> o2) {
String safeO1 = o1.getId() == null ? "" : o1.getId().toString(); String safeO1 = o1.getId() instanceof String ? o1.getId().toString() : "";
String safeO2 = o2.getId() == null ? "" : o2.getId().toString(); String safeO2 = o2.getId() instanceof String ? o2.getId().toString() : "";
boolean firstDown = StringUtils.isBlank(safeO1); boolean firstDown = o1.getId() instanceof String;
boolean secondDown = StringUtils.isBlank(safeO2); boolean secondDown = o2.getId() instanceof String;
if (firstDown == secondDown) { if (firstDown == secondDown) {
return safeO1.compareToIgnoreCase(safeO2); return safeO1.compareToIgnoreCase(safeO2);
@ -347,37 +348,37 @@ public class DataArtifactTypeFactory extends TreeChildFactory<DataArtifactSearch
*/ */
static class EmailNode extends TreeNode<EmailSearchParams> { static class EmailNode extends TreeNode<EmailSearchParams> {
private final Children children; private final boolean isLeafNode;
/** /**
* Main constructor. * Main constructor.
* *
* @param itemData The data to display. * @param itemData The data to display.
*/ */
public EmailNode(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> itemData) { public EmailNode(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> itemData, boolean isLeafNode) {
this(itemData, Children.create(new EmailFolderFactory(itemData.getSearchParams().getFolder(), itemData.getSearchParams().getDataSourceId()), true)); this(itemData,
isLeafNode
? Children.LEAF
: Children.create(new EmailFolderFactory(itemData.getSearchParams().getFolder(), itemData.getSearchParams().getDataSourceId()), true),
isLeafNode);
} }
private EmailNode(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> itemData, Children children) { private EmailNode(TreeResultsDTO.TreeItemDTO<? extends EmailSearchParams> itemData, Children children, boolean isLeafNode) {
super(itemData.getId().toString(), super(itemData.getId().toString(),
"org/sleuthkit/autopsy/images/folder-icon-16.png", "org/sleuthkit/autopsy/images/folder-icon-16.png",
itemData, itemData,
children, children,
getDefaultLookup(itemData)); getDefaultLookup(itemData));
this.children = children; this.isLeafNode = isLeafNode;
}
private boolean hasChildren() {
return !StringUtils.isBlank(this.getItemData().getId().toString()) && this.children.getNodesCount(true) > 0;
} }
@Override @Override
public void respondSelection(DataResultTopComponent dataResultPanel) { public void respondSelection(DataResultTopComponent dataResultPanel) {
if (hasChildren()) { if (this.isLeafNode) {
super.respondSelection(dataResultPanel);
} else {
dataResultPanel.displayEmailMessages(super.getItemData().getSearchParams()); dataResultPanel.displayEmailMessages(super.getItemData().getSearchParams());
} else {
super.respondSelection(dataResultPanel);
} }
} }
} }