Merge branch 'domain-discovery' of https://github.com/sleuthkit/autopsy into 6711-MakeDatamodelMoreGeneric

This commit is contained in:
William Schaefer 2020-08-20 11:57:01 -04:00
commit 4811cf49fe
51 changed files with 3407 additions and 762 deletions

View File

@ -31,7 +31,9 @@ import javax.swing.JOptionPane;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import static org.sleuthkit.autopsy.casemodule.Case.Events.CURRENT_CASE;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.events.AutopsyEvent;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.datamodel.IngestJobInfo; import org.sleuthkit.datamodel.IngestJobInfo;
import org.sleuthkit.datamodel.IngestModuleInfo; import org.sleuthkit.datamodel.IngestModuleInfo;
@ -47,6 +49,8 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
private static final Logger logger = Logger.getLogger(IngestJobInfoPanel.class.getName()); private static final Logger logger = Logger.getLogger(IngestJobInfoPanel.class.getName());
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED); private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.STARTED, IngestManager.IngestJobEvent.CANCELLED, IngestManager.IngestJobEvent.COMPLETED);
private static final Set<Case.Events> CASE_EVENTS_OF_INTEREST = EnumSet.of(Case.Events.CURRENT_CASE);
private List<IngestJobInfo> ingestJobs; private List<IngestJobInfo> ingestJobs;
private final List<IngestJobInfo> ingestJobsForSelectedDataSource = new ArrayList<>(); private final List<IngestJobInfo> ingestJobsForSelectedDataSource = new ArrayList<>();
private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel(); private IngestJobTableModel ingestJobTableModel = new IngestJobTableModel();
@ -79,6 +83,16 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
refresh(); refresh();
} }
}); });
Case.addEventTypeSubscriber(CASE_EVENTS_OF_INTEREST, (PropertyChangeEvent evt) -> {
if (!(evt instanceof AutopsyEvent) || (((AutopsyEvent) evt).getSourceType() != AutopsyEvent.SourceType.LOCAL)) {
return;
}
if (CURRENT_CASE == Case.Events.valueOf(evt.getPropertyName())) {
refresh();
}
});
} }
/** /**
@ -110,9 +124,15 @@ public final class IngestJobInfoPanel extends javax.swing.JPanel {
*/ */
private void refresh() { private void refresh() {
try { try {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase(); if (Case.isCaseOpen()) {
this.ingestJobs = skCase.getIngestJobs(); SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
setDataSource(selectedDataSource); this.ingestJobs = skCase.getIngestJobs();
setDataSource(selectedDataSource);
} else {
this.ingestJobs = new ArrayList<>();
setDataSource(null);
}
} catch (TskCoreException | NoCurrentCaseException ex) { } catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex); logger.log(Level.SEVERE, "Failed to load ingest jobs.", ex);
JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE); JOptionPane.showMessageDialog(this, Bundle.IngestJobInfoPanel_loadIngestJob_error_text(), Bundle.IngestJobInfoPanel_loadIngestJob_error_title(), JOptionPane.ERROR_MESSAGE);

View File

@ -36,3 +36,4 @@ DataSourceSummaryDetailsPanel.unallocatedSizeValue.text=
DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type
DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category
DataSourceSummaryCountsPanel.jLabel1.text=Results by Type DataSourceSummaryCountsPanel.jLabel1.text=Results by Type
DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run

View File

@ -1,4 +1,6 @@
CTL_DataSourceSummaryAction=Data Source Summary CTL_DataSourceSummaryAction=Data Source Summary
DataSourceSummaryCountsPanel.ArtifactCountsTableModel.count.header=Count
DataSourceSummaryCountsPanel.ArtifactCountsTableModel.type.header=Result Type
DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All
DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated
DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count
@ -59,9 +61,6 @@ DataSourceSummaryDetailsPanel.unallocatedSizeValue.text=
DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type DataSourceSummaryCountsPanel.byMimeTypeLabel.text=Files by MIME Type
DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category DataSourceSummaryCountsPanel.byCategoryLabel.text=Files by Category
DataSourceSummaryCountsPanel.jLabel1.text=Results by Type DataSourceSummaryCountsPanel.jLabel1.text=Results by Type
DataSourceSummaryDialog.countsTab.title=Counts
DataSourceSummaryDialog.detailsTab.title=Details
DataSourceSummaryDialog.ingestHistoryTab.title=Ingest History
DataSourceSummaryDialog.window.title=Data Sources Summary DataSourceSummaryDialog.window.title=Data Sources Summary
DataSourceSummaryNode.column.dataSourceName.header=Data Source Name DataSourceSummaryNode.column.dataSourceName.header=Data Source Name
DataSourceSummaryNode.column.files.header=Files DataSourceSummaryNode.column.files.header=Files
@ -70,4 +69,14 @@ DataSourceSummaryNode.column.status.header=Ingest Status
DataSourceSummaryNode.column.tags.header=Tags DataSourceSummaryNode.column.tags.header=Tags
DataSourceSummaryNode.column.type.header=Type DataSourceSummaryNode.column.type.header=Type
DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source DataSourceSummaryNode.viewDataSourceAction.text=Go to Data Source
DataSourceSummaryTabbedPane_countsTab_title=Counts
DataSourceSummaryTabbedPane_detailsTab_title=Details
DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History
DataSourceSummaryTabbedPane_userActivityTab_title=User Activity
DataSourceSummaryUserActivityPanel.programsRunLabel.text=Top Programs Run
DataSourceSummaryUserActivityPanel_tab_title=User Activity
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run
DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program
ViewSummaryInformationAction.name.text=View Summary Information ViewSummaryInformationAction.name.text=View Summary Information

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2019 Basis Technology Corp. * Copyright 2019 - 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,7 +18,11 @@
*/ */
package org.sleuthkit.autopsy.casemodule.datasourcesummary; package org.sleuthkit.autopsy.casemodule.datasourcesummary;
import java.io.File;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Level; import java.util.logging.Level;
@ -26,13 +30,20 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.datamodel.SleuthkitCase; import org.sleuthkit.datamodel.SleuthkitCase;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import java.util.Collections; import java.util.Collections;
import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
/** /**
@ -43,6 +54,609 @@ final class DataSourceInfoUtilities {
private static final Logger logger = Logger.getLogger(DataSourceInfoUtilities.class.getName()); private static final Logger logger = Logger.getLogger(DataSourceInfoUtilities.class.getName());
/**
* Gets a count of files for a particular datasource where it is not a
* virtual directory and has a name.
*
* @param currentDataSource The datasource.
* @param additionalWhere Additional sql where clauses.
* @param onError The message to log on error.
*
* @return The count of files or null on error.
*/
private static Long getCountOfFiles(DataSource currentDataSource, String additionalWhere, String onError) {
if (currentDataSource != null) {
try {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
return skCase.countFilesWhere(
"dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ " AND name<>''"
+ " AND data_source_obj_id=" + currentDataSource.getId()
+ " AND " + additionalWhere);
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, onError, ex);
//unable to get count of files for the specified types cell will be displayed as empty
}
}
return null;
}
/**
* Get count of files in a data source.
*
* @param currentDataSource The data source.
*
* @return The count.
*/
static Long getCountOfFiles(DataSource currentDataSource) {
return getCountOfFiles(currentDataSource,
"type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType(),
"Unable to get count of files, providing empty results");
}
/**
* Get count of unallocated files in a data source.
*
* @param currentDataSource The data source.
*
* @return The count.
*/
static Long getCountOfUnallocatedFiles(DataSource currentDataSource) {
return getCountOfFiles(currentDataSource,
"type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue(),
"Unable to get counts of unallocated files for datasource, providing empty results");
}
/**
* Get count of directories in a data source.
*
* @param currentDataSource The data source.
*
* @return The count.
*/
static Long getCountOfDirectories(DataSource currentDataSource) {
return getCountOfFiles(currentDataSource,
"type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ " AND meta_type=" + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue(),
"Unable to get count of directories for datasource, providing empty results");
}
/**
* Get count of slack files in a data source.
*
* @param currentDataSource The data source.
*
* @return The count.
*/
static Long getCountOfSlackFiles(DataSource currentDataSource) {
return getCountOfFiles(currentDataSource,
"type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType(),
"Unable to get count of slack files for datasources, providing empty results");
}
/**
* An interface for handling a result set and returning a value.
*/
private interface ResultSetHandler<T> {
T process(ResultSet resultset) throws SQLException;
}
/**
* Retrieves a result based on the provided query.
*
* @param query The query.
* @param processor The result set handler.
* @param errorMessage The error message to display if there is an error
* retrieving the resultset.
*
* @return The ResultSetHandler value or null if no ResultSet could be
* obtained.
*/
private static <T> T getBaseQueryResult(String query, ResultSetHandler<T> processor, String errorMessage) {
try (SleuthkitCase.CaseDbQuery dbQuery = Case.getCurrentCaseThrows().getSleuthkitCase().executeQuery(query)) {
ResultSet resultSet = dbQuery.getResultSet();
try {
return processor.process(resultSet);
} catch (SQLException ex) {
logger.log(Level.WARNING, errorMessage, ex);
}
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, errorMessage, ex);
}
return null;
}
/**
* Gets the size of unallocated files in a particular datasource.
*
* @param currentDataSource The data source.
*
* @return The size or null if the query could not be executed.
*/
static Long getSizeOfUnallocatedFiles(DataSource currentDataSource) {
if (currentDataSource == null) {
return null;
}
final String valueParam = "value";
final String countParam = "count";
String query = "SELECT SUM(size) AS " + valueParam + ", COUNT(*) AS " + countParam
+ " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()
+ " AND name<>''"
+ " AND data_source_obj_id=" + currentDataSource.getId();
ResultSetHandler<Long> handler = (resultSet) -> {
if (resultSet.next()) {
// ensure that there is an unallocated count result that is attached to this data source
long resultCount = resultSet.getLong(valueParam);
return (resultCount > 0) ? resultSet.getLong(valueParam) : null;
} else {
return null;
}
};
String errorMessage = "Unable to get size of unallocated files; returning null.";
return getBaseQueryResult(query, handler, errorMessage);
}
/**
* Generates a result set handler that will return a map of string to long.
*
* @param keyParam The named parameter in the result set representing the
* key.
* @param valueParam The named parameter in the result set representing the
* value.
*
* @return The result set handler to generate the map of string to long.
*/
private static ResultSetHandler<LinkedHashMap<String, Long>> getStringLongResultSetHandler(String keyParam, String valueParam) {
return (resultSet) -> {
LinkedHashMap<String, Long> toRet = new LinkedHashMap<>();
while (resultSet.next()) {
try {
toRet.put(resultSet.getString(keyParam), resultSet.getLong(valueParam));
} catch (SQLException ex) {
logger.log(Level.WARNING, "Failed to get a result pair from the result set.", ex);
}
}
return toRet;
};
}
/**
* Retrieves counts for each artifact type in a data source.
*
* @param selectedDataSource The data source.
*
* @return A mapping of artifact type name to the counts or null if there
* was an error executing the query.
*/
static Map<String, Long> getCountsOfArtifactsByType(DataSource selectedDataSource) {
if (selectedDataSource == null) {
return Collections.emptyMap();
}
final String nameParam = "name";
final String valueParam = "value";
String query
= "SELECT bbt.display_name AS " + nameParam + ", COUNT(*) AS " + valueParam
+ " FROM blackboard_artifacts bba "
+ " INNER JOIN blackboard_artifact_types bbt ON bba.artifact_type_id = bbt.artifact_type_id"
+ " WHERE bba.data_source_obj_id =" + selectedDataSource.getId()
+ " GROUP BY bbt.display_name";
String errorMessage = "Unable to get artifact type counts; returning null.";
return getBaseQueryResult(query, getStringLongResultSetHandler(nameParam, valueParam), errorMessage);
}
/**
* Describes a result of a program run on a datasource.
*/
static class TopProgramsResult {
private final String programName;
private final String programPath;
private final Long runTimes;
private final Date lastRun;
/**
* Main constructor.
*
* @param programName The name of the program.
* @param programPath The path of the program.
* @param runTimes The number of runs.
*/
TopProgramsResult(String programName, String programPath, Long runTimes, Date lastRun) {
this.programName = programName;
this.programPath = programPath;
this.runTimes = runTimes;
this.lastRun = lastRun;
}
/**
* @return The name of the program
*/
String getProgramName() {
return programName;
}
/**
* @return The path of the program.
*/
String getProgramPath() {
return programPath;
}
/**
* @return The number of run times or null if not present.
*/
Long getRunTimes() {
return runTimes;
}
/**
* @return The last time the program was run or null if not present.
*/
public Date getLastRun() {
return lastRun;
}
}
/**
* A SQL join type.
*/
private enum JoinType {
LEFT,
RIGHT,
INNER,
OUTER
}
/**
* A blackboard attribute value column.
*/
private enum AttributeColumn {
value_text,
value_int32,
value_int64
}
/**
* The suffix joined to a key name for use as an identifier of a query.
*/
private static final String QUERY_SUFFIX = "_query";
/**
* Creates a sql statement querying the blackboard attributes table for a
* particular attribute type and returning a specified value. That query
* also joins with the blackboard artifact table.
*
* @param joinType The type of join statement to create.
* @param attributeColumn The blackboard attribute column that should be
* returned.
* @param attrType The attribute type to query for.
* @param keyName The aliased name of the attribute to return. This
* is also used to calculate the alias of the query
* same as getFullKey.
* @param bbaName The blackboard artifact table alias.
*
* @return The generated sql statement.
*/
private static String getAttributeJoin(JoinType joinType, AttributeColumn attributeColumn, ATTRIBUTE_TYPE attrType, String keyName, String bbaName) {
String queryName = keyName + QUERY_SUFFIX;
String innerQueryName = "inner_attribute_" + queryName;
return "\n" + joinType + " JOIN (\n"
+ " SELECT \n"
+ " " + innerQueryName + ".artifact_id,\n"
+ " " + innerQueryName + "." + attributeColumn + " AS " + keyName + "\n"
+ " FROM blackboard_attributes " + innerQueryName + "\n"
+ " WHERE " + innerQueryName + ".attribute_type_id = " + attrType.getTypeID() + " -- " + attrType.name() + "\n"
+ ") " + queryName + " ON " + queryName + ".artifact_id = " + bbaName + ".artifact_id\n";
}
/**
* Given a column key, creates the full name for the column key.
*
* @param key The column key.
*
* @return The full identifier for the column key.
*/
private static String getFullKey(String key) {
return key + QUERY_SUFFIX + "." + key;
}
/**
* Constructs a SQL 'where' statement from a list of clauses and puts
* parenthesis around each clause.
*
* @param clauses The clauses
*
* @return The generated 'where' statement.
*/
private static String getWhereString(List<String> clauses) {
if (clauses.isEmpty()) {
return "";
}
List<String> parenthesized = clauses.stream()
.map(c -> "(" + c + ")")
.collect(Collectors.toList());
return "\nWHERE " + String.join("\n AND ", parenthesized) + "\n";
}
/**
* Generates a [column] LIKE sql clause.
*
* @param column The column identifier.
* @param likeString The string that will be used as column comparison.
* @param isLike if false, the statement becomes NOT LIKE.
*
* @return The generated statement.
*/
private static String getLikeClause(String column, String likeString, boolean isLike) {
return column + (isLike ? "" : " NOT") + " LIKE '" + likeString + "'";
}
/**
* Retrieves a list of the top programs used on the data source. Currently
* determines this based off of which prefetch results return the highest
* count.
*
* @param dataSource The data source.
* @param count The number of programs to return.
*
* @return
*/
static List<TopProgramsResult> getTopPrograms(DataSource dataSource, int count) {
if (dataSource == null || count <= 0) {
return Collections.emptyList();
}
// ntosboot should be ignored
final String ntosBootIdentifier = "NTOSBOOT";
// programs in windows directory to be ignored
final String windowsDir = "/WINDOWS%";
final String nameParam = "name";
final String pathParam = "path";
final String runCountParam = "run_count";
final String lastRunParam = "last_run";
String bbaQuery = "bba";
final String query = "SELECT\n"
+ " " + getFullKey(nameParam) + " AS " + nameParam + ",\n"
+ " " + getFullKey(pathParam) + " AS " + pathParam + ",\n"
+ " MAX(" + getFullKey(runCountParam) + ") AS " + runCountParam + ",\n"
+ " MAX(" + getFullKey(lastRunParam) + ") AS " + lastRunParam + "\n"
+ "FROM blackboard_artifacts " + bbaQuery + "\n"
+ getAttributeJoin(JoinType.INNER, AttributeColumn.value_text, ATTRIBUTE_TYPE.TSK_PROG_NAME, nameParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_text, ATTRIBUTE_TYPE.TSK_PATH, pathParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_int32, ATTRIBUTE_TYPE.TSK_COUNT, runCountParam, bbaQuery)
+ getAttributeJoin(JoinType.LEFT, AttributeColumn.value_int64, ATTRIBUTE_TYPE.TSK_DATETIME, lastRunParam, bbaQuery)
+ getWhereString(Arrays.asList(
bbaQuery + ".artifact_type_id = " + ARTIFACT_TYPE.TSK_PROG_RUN.getTypeID(),
bbaQuery + ".data_source_obj_id = " + dataSource.getId(),
// exclude ntosBootIdentifier from results
getLikeClause(getFullKey(nameParam), ntosBootIdentifier, false),
// exclude windows directory items from results
getFullKey(pathParam) + " IS NULL OR " + getLikeClause(getFullKey(pathParam), windowsDir, false)
))
+ "GROUP BY " + getFullKey(nameParam) + ", " + getFullKey(pathParam) + "\n"
+ "ORDER BY \n"
+ " MAX(" + getFullKey(runCountParam) + ") DESC,\n"
+ " MAX(" + getFullKey(lastRunParam) + ") DESC,\n"
+ " " + getFullKey(nameParam) + " ASC";
final String errorMessage = "Unable to get top program results; returning null.";
ResultSetHandler<List<TopProgramsResult>> handler = (resultSet) -> {
List<TopProgramsResult> progResults = new ArrayList<>();
boolean quitAtCount = false;
while (resultSet.next() && (!quitAtCount || progResults.size() < count)) {
try {
long lastRunEpoch = resultSet.getLong(lastRunParam);
Date lastRun = (resultSet.wasNull()) ? null : new Date(lastRunEpoch * 1000);
Long runCount = resultSet.getLong(runCountParam);
if (resultSet.wasNull()) {
runCount = null;
}
if (lastRun != null || runCount != null) {
quitAtCount = true;
}
progResults.add(new TopProgramsResult(
resultSet.getString(nameParam),
resultSet.getString(pathParam),
runCount,
lastRun));
} catch (SQLException ex) {
logger.log(Level.WARNING, "Failed to get a top program result from the result set.", ex);
}
}
return progResults;
};
return getBaseQueryResult(query, handler, errorMessage);
}
/**
* Functions that determine the folder name of a list of path elements. If
* not matched, function returns null.
*/
private static final List<Function<List<String>, String>> SHORT_FOLDER_MATCHERS = Arrays.asList(
// handle Program Files and Program Files (x86) - if true, return the next folder
(pathList) -> {
if (pathList.size() < 2) {
return null;
}
String rootParent = pathList.get(0).toUpperCase();
if ("PROGRAM FILES".equals(rootParent) || "PROGRAM FILES (X86)".equals(rootParent)) {
return pathList.get(1);
} else {
return null;
}
},
// if there is a folder named "APPLICATION DATA" or "APPDATA"
(pathList) -> {
for (String pathEl : pathList) {
String uppered = pathEl.toUpperCase();
if ("APPLICATION DATA".equals(uppered) || "APPDATA".equals(uppered)) {
return "AppData";
}
}
return null;
}
);
/**
* Determines a short folder name if any. Otherwise, returns empty string.
*
* @param strPath The string path.
*
* @return The short folder name or empty string if not found.
*/
static String getShortFolderName(String strPath, String applicationName) {
if (strPath == null) {
return "";
}
List<String> pathEls = new ArrayList<>(Arrays.asList(applicationName));
File file = new File(strPath);
while (file != null && StringUtils.isNotBlank(file.getName())) {
pathEls.add(file.getName());
file = file.getParentFile();
}
Collections.reverse(pathEls);
for (Function<List<String>, String> matchEntry : SHORT_FOLDER_MATCHERS) {
String result = matchEntry.apply(pathEls);
if (StringUtils.isNotBlank(result)) {
return result;
}
}
return "";
}
/**
* Generates a string which is a concatenation of the value received from
* the result set.
*
* @param query The query.
* @param valueParam The parameter for the value in the result set.
* @param separator The string separator used in concatenation.
* @param errorMessage The error message if the result set could not
* be received.
* @param singleErrorMessage The error message if a single result could not
* be obtained.
*
* @return The concatenated string or null if the query could not be
* executed.
*/
private static String getConcattedStringsResult(String query, String valueParam, String separator, String errorMessage, String singleErrorMessage) {
ResultSetHandler<String> handler = (resultSet) -> {
String toRet = "";
boolean first = true;
while (resultSet.next()) {
try {
if (first) {
first = false;
} else {
toRet += separator;
}
toRet += resultSet.getString(valueParam);
} catch (SQLException ex) {
logger.log(Level.WARNING, singleErrorMessage, ex);
}
}
return toRet;
};
return getBaseQueryResult(query, handler, errorMessage);
}
/**
* Generates a concatenated string value based on the text value of a
* particular attribute in a particular artifact for a specific data source.
*
* @param dataSourceId The data source id.
* @param artifactTypeId The artifact type id.
* @param attributeTypeId The attribute type id.
*
* @return The concatenated value or null if the query could not be
* executed.
*/
private static String getConcattedAttrValue(long dataSourceId, int artifactTypeId, int attributeTypeId) {
final String valueParam = "concatted_attribute_value";
String query = "SELECT attr.value_text AS " + valueParam
+ " FROM blackboard_artifacts bba "
+ " INNER JOIN blackboard_attributes attr ON bba.artifact_id = attr.artifact_id "
+ " WHERE bba.data_source_obj_id = " + dataSourceId
+ " AND bba.artifact_type_id = " + artifactTypeId
+ " AND attr.attribute_type_id = " + attributeTypeId;
String errorMessage = "Unable to execute query to retrieve concatted attribute values.";
String singleErrorMessage = "There was an error retrieving one of the results. That result will be omitted from concatted value.";
String separator = ", ";
return getConcattedStringsResult(query, valueParam, separator, errorMessage, singleErrorMessage);
}
/**
* Retrieves the concatenation of operating system attributes for a
* particular data source.
*
* @param dataSource The data source.
*
* @return The concatenated value or null if the query could not be
* executed.
*/
static String getOperatingSystems(DataSource dataSource) {
if (dataSource == null) {
return null;
}
return getConcattedAttrValue(dataSource.getId(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME.getTypeID());
}
/**
* Retrieves the concatenation of data source usage for a particular data
* source.
*
* @param dataSource The data source.
*
* @return The concatenated value or null if the query could not be
* executed.
*/
static String getDataSourceType(DataSource dataSource) {
if (dataSource == null) {
return null;
}
return getConcattedAttrValue(dataSource.getId(),
BlackboardArtifact.ARTIFACT_TYPE.TSK_DATA_SOURCE_USAGE.getTypeID(),
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DESCRIPTION.getTypeID());
}
/** /**
* Get a map containing the TSK_DATA_SOURCE_USAGE description attributes * Get a map containing the TSK_DATA_SOURCE_USAGE description attributes
* associated with each data source in the current case. * associated with each data source in the current case.
@ -147,39 +761,6 @@ final class DataSourceInfoUtilities {
} }
} }
/**
* Get a map containing the names of operating systems joined in a comma
* seperated list to the Data Source they exist on in the current case. No
* item will exist in the map for data sources which do not contain
* TS_OS_INFO artifacts which have a program name.
*
* @return Collection which maps datasource id to a String which is a comma
* seperated list of Operating system names found on the data
* source.
*/
static Map<Long, String> getOperatingSystems() {
Map<Long, String> osDetailMap = new HashMap<>();
try {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
ArrayList<BlackboardArtifact> osInfoArtifacts = skCase.getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_INFO);
for (BlackboardArtifact osInfo : osInfoArtifacts) {
BlackboardAttribute programName = osInfo.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME));
if (programName != null) {
String currentOsString = osDetailMap.get(osInfo.getDataSource().getId());
if (currentOsString == null || currentOsString.isEmpty()) {
currentOsString = programName.getValueString();
} else {
currentOsString = currentOsString + ", " + programName.getValueString();
}
osDetailMap.put(osInfo.getDataSource().getId(), currentOsString);
}
}
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.SEVERE, "Failed to load OS info artifacts.", ex);
}
return osDetailMap;
}
/** /**
* Get the number of files in the case database for the current data source * Get the number of files in the case database for the current data source
* which have the specified mimetypes. * which have the specified mimetypes.
@ -212,134 +793,6 @@ final class DataSourceInfoUtilities {
return null; return null;
} }
/**
* Get a map containing the number of unallocated files in each data source
* in the current case.
*
* @return Collection which maps datasource id to a count for the number of
* unallocated files in the datasource, will only contain entries
* for datasources which have at least 1 unallocated file
*/
static Map<Long, Long> getCountsOfUnallocatedFiles() {
try {
final String countUnallocatedFilesQuery = "data_source_obj_id, COUNT(*) AS value"
+ " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()
+ " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
return getValuesMap(countUnallocatedFilesQuery);
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get counts of unallocated files for all datasources, providing empty results", ex);
return Collections.emptyMap();
}
}
/**
* Get a map containing the total size of unallocated files in each data
* source in the current case.
*
* @return Collection which maps datasource id to a total size in bytes of
* unallocated files in the datasource, will only contain entries
* for datasources which have at least 1 unallocated file
*/
static Map<Long, Long> getSizeOfUnallocatedFiles() {
try {
final String countUnallocatedFilesQuery = "data_source_obj_id, sum(size) AS value"
+ " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ " AND dir_flags=" + TskData.TSK_FS_NAME_FLAG_ENUM.UNALLOC.getValue()
+ " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
return getValuesMap(countUnallocatedFilesQuery);
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get size of unallocated files for all datasources, providing empty results", ex);
return Collections.emptyMap();
}
}
/**
* Get a map containing the number of directories in each data source in the
* current case.
*
* @return Collection which maps datasource id to a count for the number of
* directories in the datasource, will only contain entries for
* datasources which have at least 1 directory
*/
static Map<Long, Long> getCountsOfDirectories() {
try {
final String countDirectoriesQuery = "data_source_obj_id, COUNT(*) AS value"
+ " FROM tsk_files WHERE type<>" + TskData.TSK_DB_FILES_TYPE_ENUM.VIRTUAL_DIR.getFileType()
+ " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ " AND meta_type=" + TskData.TSK_FS_META_TYPE_ENUM.TSK_FS_META_TYPE_DIR.getValue()
+ " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
return getValuesMap(countDirectoriesQuery);
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get counts of directories for all datasources, providing empty results", ex);
return Collections.emptyMap();
}
}
/**
* Get a map containing the number of slack files in each data source in the
* current case.
*
* @return Collection which maps datasource id to a count for the number of
* slack files in the datasource, will only contain entries for
* datasources which have at least 1 slack file
*/
static Map<Long, Long> getCountsOfSlackFiles() {
try {
final String countSlackFilesQuery = "data_source_obj_id, COUNT(*) AS value"
+ " FROM tsk_files WHERE type=" + TskData.TSK_DB_FILES_TYPE_ENUM.SLACK.getFileType()
+ " AND dir_type<>" + TskData.TSK_FS_NAME_TYPE_ENUM.VIRT_DIR.getValue()
+ " AND name<>'' GROUP BY data_source_obj_id"; //NON-NLS
return getValuesMap(countSlackFilesQuery);
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get counts of slack files for all datasources, providing empty results", ex);
return Collections.emptyMap();
}
}
/**
* Get a map containing maps which map artifact type to the number of times
* it exosts in each data source in the current case.
*
* @return Collection which maps datasource id to maps of artifact display
* name to number of occurences in the datasource, will only contain
* entries for artifacts which have at least one occurence in the
* data source.
*/
static Map<Long, Map<String, Long>> getCountsOfArtifactsByType() {
try {
final String countArtifactsQuery = "blackboard_artifacts.data_source_obj_id, blackboard_artifact_types.display_name AS label, COUNT(*) AS value"
+ " FROM blackboard_artifacts, blackboard_artifact_types"
+ " WHERE blackboard_artifacts.artifact_type_id = blackboard_artifact_types.artifact_type_id"
+ " GROUP BY blackboard_artifacts.data_source_obj_id, blackboard_artifact_types.display_name";
return getLabeledValuesMap(countArtifactsQuery);
} catch (TskCoreException | NoCurrentCaseException ex) {
logger.log(Level.WARNING, "Unable to get counts of all artifact types for all datasources, providing empty results", ex);
return Collections.emptyMap();
}
}
/**
* Helper method to execute a select query with a
* DataSourceLabeledValueCallback.
*
* @param query the portion of the query which should follow the SELECT
*
* @return a map of datasource object IDs to maps of String labels to the
* values associated with them.
*
* @throws TskCoreException
* @throws NoCurrentCaseException
*/
private static Map<Long, Map<String, Long>> getLabeledValuesMap(String query) throws TskCoreException, NoCurrentCaseException {
SleuthkitCase skCase = Case.getCurrentCaseThrows().getSleuthkitCase();
DataSourceLabeledValueCallback callback = new DataSourceLabeledValueCallback();
skCase.getCaseDbAccessManager().select(query, callback);
return callback.getMapOfLabeledValues();
}
/** /**
* Helper method to execute a select query with a * Helper method to execute a select query with a
* DataSourceSingleValueCallback. * DataSourceSingleValueCallback.

View File

@ -70,18 +70,13 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents> <SubComponents>
<Component class="javax.swing.JTable" name="fileCountsByMimeTypeTable"> <Component class="javax.swing.JTable" name="fileCountsByMimeTypeTable">
<Properties>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="filesByMimeTypeTableModel" type="code"/>
</Property>
</Properties>
</Component> </Component>
</SubComponents> </SubComponents>
</Container> </Container>
<Component class="javax.swing.JLabel" name="byMimeTypeLabel"> <Component class="javax.swing.JLabel" name="byMimeTypeLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byMimeTypeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byMimeTypeLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
@ -93,25 +88,20 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents> <SubComponents>
<Component class="javax.swing.JTable" name="fileCountsByCategoryTable"> <Component class="javax.swing.JTable" name="fileCountsByCategoryTable">
<Properties>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="filesByCategoryTableModel" type="code"/>
</Property>
</Properties>
</Component> </Component>
</SubComponents> </SubComponents>
</Container> </Container>
<Component class="javax.swing.JLabel" name="byCategoryLabel"> <Component class="javax.swing.JLabel" name="byCategoryLabel">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byCategoryLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.byCategoryLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
<Component class="javax.swing.JLabel" name="jLabel1"> <Component class="javax.swing.JLabel" name="jLabel1">
<Properties> <Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourceSummary/Bundle.properties" key="DataSourceSummaryCountsPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryCountsPanel.jLabel1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
</Properties> </Properties>
</Component> </Component>
@ -123,15 +113,6 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents> <SubComponents>
<Component class="javax.swing.JTable" name="artifactCountsTable"> <Component class="javax.swing.JTable" name="artifactCountsTable">
<Properties>
<Property name="autoCreateRowSorter" type="boolean" value="true"/>
<Property name="model" type="javax.swing.table.TableModel" editor="org.netbeans.modules.form.editors2.TableModelEditor">
<Table columnCount="2" rowCount="0">
<Column editable="false" title="Result Type" type="java.lang.Object"/>
<Column editable="false" title="Count" type="java.lang.Object"/>
</Table>
</Property>
</Properties>
</Component> </Component>
</SubComponents> </SubComponents>
</Container> </Container>

View File

@ -18,15 +18,12 @@
*/ */
package org.sleuthkit.autopsy.casemodule.datasourcesummary; package org.sleuthkit.autopsy.casemodule.datasourcesummary;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.FileTypeUtils; import org.sleuthkit.autopsy.coreutils.FileTypeUtils;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
@ -34,69 +31,224 @@ import org.sleuthkit.datamodel.DataSource;
* Panel for displaying summary information on the known files present in the * Panel for displaying summary information on the known files present in the
* specified DataSource * specified DataSource
*/ */
@Messages({"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.type.header=File Type",
"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.count.header=Count",
"DataSourceSummaryCountsPanel.ArtifactCountsTableModel.type.header=Result Type",
"DataSourceSummaryCountsPanel.ArtifactCountsTableModel.count.header=Count",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.type.header=File Type",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count"
})
class DataSourceSummaryCountsPanel extends javax.swing.JPanel { class DataSourceSummaryCountsPanel extends javax.swing.JPanel {
// Result returned for a data model if no data found.
private static final Object[][] EMPTY_PAIRS = new Object[][]{};
// column headers for mime type table
private static final Object[] MIME_TYPE_COLUMN_HEADERS = new Object[]{
Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_type_header(),
Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_count_header()
};
// column headers for file by category table
private static final Object[] FILE_BY_CATEGORY_COLUMN_HEADERS = new Object[]{
Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_type_header(),
Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_count_header()
};
// column headers for artifact counts table
private static final Object[] ARTIFACT_COUNTS_COLUMN_HEADERS = new Object[]{
Bundle.DataSourceSummaryCountsPanel_ArtifactCountsTableModel_type_header(),
Bundle.DataSourceSummaryCountsPanel_ArtifactCountsTableModel_count_header()
};
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private FilesByMimeTypeTableModel filesByMimeTypeTableModel = new FilesByMimeTypeTableModel(null);
private FilesByCategoryTableModel filesByCategoryTableModel = new FilesByCategoryTableModel(null);
private static final Logger logger = Logger.getLogger(DataSourceSummaryCountsPanel.class.getName()); private static final Logger logger = Logger.getLogger(DataSourceSummaryCountsPanel.class.getName());
private final Map<Long, Long> allFilesCountsMap;
private final Map<Long, Long> slackFilesCountsMap;
private final Map<Long, Long> directoriesCountsMap;
private final Map<Long, Long> unallocatedFilesCountsMap;
private final Map<Long, Map<String, Long>> artifactsByTypeCountsMap;
private final DefaultTableCellRenderer rightAlignedRenderer = new DefaultTableCellRenderer(); private final DefaultTableCellRenderer rightAlignedRenderer = new DefaultTableCellRenderer();
private DataSource dataSource;
/** /**
* Creates new form DataSourceSummaryCountsPanel * Creates new form DataSourceSummaryCountsPanel
*/ */
DataSourceSummaryCountsPanel(Map<Long, Long> fileCountsMap) { DataSourceSummaryCountsPanel() {
this.allFilesCountsMap = fileCountsMap;
this.slackFilesCountsMap = DataSourceInfoUtilities.getCountsOfSlackFiles();
this.directoriesCountsMap = DataSourceInfoUtilities.getCountsOfDirectories();
this.unallocatedFilesCountsMap = DataSourceInfoUtilities.getCountsOfUnallocatedFiles();
this.artifactsByTypeCountsMap = DataSourceInfoUtilities.getCountsOfArtifactsByType();
rightAlignedRenderer.setHorizontalAlignment(JLabel.RIGHT); rightAlignedRenderer.setHorizontalAlignment(JLabel.RIGHT);
initComponents(); initComponents();
fileCountsByMimeTypeTable.getTableHeader().setReorderingAllowed(false); fileCountsByMimeTypeTable.getTableHeader().setReorderingAllowed(false);
fileCountsByCategoryTable.getTableHeader().setReorderingAllowed(false); fileCountsByCategoryTable.getTableHeader().setReorderingAllowed(false);
artifactCountsTable.getTableHeader().setReorderingAllowed(false);
setDataSource(null);
} }
/** /**
* Specify the DataSource to display file information for * The datasource currently used as the model in this panel.
* *
* @param selectedDataSource the DataSource to display file information for * @return The datasource currently being used as the model in this panel.
*/ */
void updateCountsTableData(DataSource selectedDataSource) { public DataSource getDataSource() {
filesByMimeTypeTableModel = new FilesByMimeTypeTableModel(selectedDataSource); return dataSource;
fileCountsByMimeTypeTable.setModel(filesByMimeTypeTableModel); }
/**
* Sets datasource to visualize in the panel.
*
* @param dataSource The datasource to use in this panel.
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
if (dataSource == null || !Case.isCaseOpen()) {
updateCountsTableData(EMPTY_PAIRS,
EMPTY_PAIRS,
EMPTY_PAIRS);
} else {
updateCountsTableData(getMimeTypeModel(dataSource),
getFileCategoryModel(dataSource),
getArtifactCountsModel(dataSource));
}
}
/**
* Specify the DataSource to display file information for.
*
* @param mimeTypeDataModel The mime type data model.
* @param fileCategoryDataModel The file category data model.
* @param artifactDataModel The artifact type data model.
*/
private void updateCountsTableData(Object[][] mimeTypeDataModel, Object[][] fileCategoryDataModel, Object[][] artifactDataModel) {
fileCountsByMimeTypeTable.setModel(new NonEditableTableModel(mimeTypeDataModel, MIME_TYPE_COLUMN_HEADERS));
fileCountsByMimeTypeTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer); fileCountsByMimeTypeTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer);
fileCountsByMimeTypeTable.getColumnModel().getColumn(0).setPreferredWidth(130); fileCountsByMimeTypeTable.getColumnModel().getColumn(0).setPreferredWidth(130);
filesByCategoryTableModel = new FilesByCategoryTableModel(selectedDataSource);
fileCountsByCategoryTable.setModel(filesByCategoryTableModel); fileCountsByCategoryTable.setModel(new NonEditableTableModel(fileCategoryDataModel, FILE_BY_CATEGORY_COLUMN_HEADERS));
fileCountsByCategoryTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer); fileCountsByCategoryTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer);
fileCountsByCategoryTable.getColumnModel().getColumn(0).setPreferredWidth(130); fileCountsByCategoryTable.getColumnModel().getColumn(0).setPreferredWidth(130);
updateArtifactCounts(selectedDataSource);
artifactCountsTable.setModel(new NonEditableTableModel(artifactDataModel, ARTIFACT_COUNTS_COLUMN_HEADERS));
artifactCountsTable.getColumnModel().getColumn(0).setPreferredWidth(230);
artifactCountsTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer);
this.repaint(); this.repaint();
} }
/** /**
* Helper method to update the artifact specific counts by clearing the * Determines the JTable data model for datasource mime types.
* table and adding counts for the artifacts which exist in the selected
* data source.
* *
* @param selectedDataSource the data source to display artifact counts for * @param dataSource The DataSource.
*
* @return The model to be used with a JTable.
*/ */
private void updateArtifactCounts(DataSource selectedDataSource) { @Messages({
((DefaultTableModel) artifactCountsTable.getModel()).setRowCount(0); "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.images.row=Images",
if (selectedDataSource != null && artifactsByTypeCountsMap.get(selectedDataSource.getId()) != null) { "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.videos.row=Videos",
Map<String, Long> artifactCounts = artifactsByTypeCountsMap.get(selectedDataSource.getId()); "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.audio.row=Audio",
for (String key : artifactCounts.keySet()) { "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.documents.row=Documents",
((DefaultTableModel) artifactCountsTable.getModel()).addRow(new Object[]{key, artifactCounts.get(key)}); "DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.executables.row=Executables"
} })
private static Object[][] getMimeTypeModel(DataSource dataSource) {
return new Object[][]{
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_images_row(),
getCount(dataSource, FileTypeUtils.FileTypeCategory.IMAGE)},
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_videos_row(),
getCount(dataSource, FileTypeUtils.FileTypeCategory.VIDEO)},
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_audio_row(),
getCount(dataSource, FileTypeUtils.FileTypeCategory.AUDIO)},
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_documents_row(),
getCount(dataSource, FileTypeUtils.FileTypeCategory.DOCUMENTS)},
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_executables_row(),
getCount(dataSource, FileTypeUtils.FileTypeCategory.EXECUTABLE)}
};
}
/**
* Retrieves the counts of files of a particular mime type for a particular
* DataSource.
*
* @param dataSource The DataSource.
* @param category The mime type category.
*
* @return The count.
*/
private static Long getCount(DataSource dataSource, FileTypeUtils.FileTypeCategory category) {
return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(dataSource, category.getMediaTypes());
}
/**
* Determines the JTable data model for datasource file categories.
*
* @param dataSource The DataSource.
*
* @return The model to be used with a JTable.
*/
@Messages({
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.unallocated.row=Unallocated",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.slack.row=Slack",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.directory.row=Directory"
})
private static Object[][] getFileCategoryModel(DataSource selectedDataSource) {
Long fileCount = zeroIfNull(DataSourceInfoUtilities.getCountOfFiles(selectedDataSource));
Long unallocatedFiles = zeroIfNull(DataSourceInfoUtilities.getCountOfUnallocatedFiles(selectedDataSource));
Long allocatedFiles = zeroIfNull(getAllocatedCount(fileCount, unallocatedFiles));
Long slackFiles = zeroIfNull(DataSourceInfoUtilities.getCountOfSlackFiles(selectedDataSource));
Long directories = zeroIfNull(DataSourceInfoUtilities.getCountOfDirectories(selectedDataSource));
return new Object[][]{
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_all_row(), fileCount},
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_allocated_row(), allocatedFiles},
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_unallocated_row(), unallocatedFiles},
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_slack_row(), slackFiles},
new Object[]{Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_directory_row(), directories}
};
}
/**
* Returns 0 if value is null.
*
* @param origValue The original value.
*
* @return The value or 0 if null.
*/
private static Long zeroIfNull(Long origValue) {
return origValue == null ? 0 : origValue;
}
/**
* Safely gets the allocated files count.
*
* @param allFilesCount The count of all files.
* @param unallocatedFilesCount The count of unallocated files.
*
* @return The count of allocated files.
*/
private static long getAllocatedCount(Long allFilesCount, Long unallocatedFilesCount) {
if (allFilesCount == null) {
return 0;
} else if (unallocatedFilesCount == null) {
return allFilesCount;
} else {
return allFilesCount - unallocatedFilesCount;
} }
artifactCountsTable.getColumnModel().getColumn(0).setPreferredWidth(230); }
artifactCountsTable.getColumnModel().getColumn(1).setCellRenderer(rightAlignedRenderer);
/**
* The counts of different artifact types found in a DataSource.
*
* @param selectedDataSource The DataSource.
*
* @return The JTable data model of counts of artifact types.
*/
private static Object[][] getArtifactCountsModel(DataSource selectedDataSource) {
Map<String, Long> artifactMapping = DataSourceInfoUtilities.getCountsOfArtifactsByType(selectedDataSource);
if (artifactMapping == null) {
return EMPTY_PAIRS;
}
return artifactMapping.entrySet().stream()
.filter((entrySet) -> entrySet != null && entrySet.getKey() != null)
.sorted((a, b) -> a.getKey().compareTo(b.getKey()))
.map((entrySet) -> new Object[]{entrySet.getKey(), entrySet.getValue()})
.toArray(Object[][]::new);
} }
/** /**
@ -118,35 +270,16 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel {
artifactCountsScrollPane = new javax.swing.JScrollPane(); artifactCountsScrollPane = new javax.swing.JScrollPane();
artifactCountsTable = new javax.swing.JTable(); artifactCountsTable = new javax.swing.JTable();
fileCountsByMimeTypeTable.setModel(filesByMimeTypeTableModel);
fileCountsByMimeTypeScrollPane.setViewportView(fileCountsByMimeTypeTable); fileCountsByMimeTypeScrollPane.setViewportView(fileCountsByMimeTypeTable);
org.openide.awt.Mnemonics.setLocalizedText(byMimeTypeLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byMimeTypeLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(byMimeTypeLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byMimeTypeLabel.text")); // NOI18N
fileCountsByCategoryTable.setModel(filesByCategoryTableModel);
fileCountsByCategoryScrollPane.setViewportView(fileCountsByCategoryTable); fileCountsByCategoryScrollPane.setViewportView(fileCountsByCategoryTable);
org.openide.awt.Mnemonics.setLocalizedText(byCategoryLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byCategoryLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(byCategoryLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.byCategoryLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.jLabel1.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(jLabel1, org.openide.util.NbBundle.getMessage(DataSourceSummaryCountsPanel.class, "DataSourceSummaryCountsPanel.jLabel1.text")); // NOI18N
artifactCountsTable.setAutoCreateRowSorter(true);
artifactCountsTable.setModel(new javax.swing.table.DefaultTableModel(
new Object [][] {
},
new String [] {
"Result Type", "Count"
}
) {
boolean[] canEdit = new boolean [] {
false, false
};
public boolean isCellEditable(int rowIndex, int columnIndex) {
return canEdit [columnIndex];
}
});
artifactCountsScrollPane.setViewportView(artifactCountsTable); artifactCountsScrollPane.setViewportView(artifactCountsTable);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
@ -206,190 +339,4 @@ class DataSourceSummaryCountsPanel extends javax.swing.JPanel {
private javax.swing.JTable fileCountsByMimeTypeTable; private javax.swing.JTable fileCountsByMimeTypeTable;
private javax.swing.JLabel jLabel1; private javax.swing.JLabel jLabel1;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
/**
* Table model for the files table model to display counts of specific file
* types by mime type found in the currently selected data source.
*/
@Messages({"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.type.header=File Type",
"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.count.header=Count"})
private class FilesByMimeTypeTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private final DataSource currentDataSource;
private final List<String> columnHeaders = new ArrayList<>();
private static final int IMAGES_ROW_INDEX = 0;
private static final int VIDEOS_ROW_INDEX = 1;
private static final int AUDIO_ROW_INDEX = 2;
private static final int DOCUMENTS_ROW_INDEX = 3;
private static final int EXECUTABLES_ROW_INDEX = 4;
/**
* Create a FilesByMimeTypeTableModel for the speicified datasource.
*
* @param selectedDataSource the datasource which this
* FilesByMimeTypeTablemodel will represent
*/
FilesByMimeTypeTableModel(DataSource selectedDataSource) {
columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_type_header());
columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_count_header());
currentDataSource = selectedDataSource;
}
@Override
public int getRowCount() {
//should be kept equal to the number of types we are displaying in the tables
return 5;
}
@Override
public int getColumnCount() {
return columnHeaders.size();
}
@Messages({
"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.images.row=Images",
"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.videos.row=Videos",
"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.audio.row=Audio",
"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.documents.row=Documents",
"DataSourceSummaryCountsPanel.FilesByMimeTypeTableModel.executables.row=Executables"
})
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
switch (rowIndex) {
case IMAGES_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_images_row();
case VIDEOS_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_videos_row();
case AUDIO_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_audio_row();
case DOCUMENTS_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_documents_row();
case EXECUTABLES_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByMimeTypeTableModel_executables_row();
default:
break;
}
} else if (columnIndex == 1) {
switch (rowIndex) {
case 0:
return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.IMAGE.getMediaTypes());
case 1:
return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.VIDEO.getMediaTypes());
case 2:
return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.AUDIO.getMediaTypes());
case 3:
return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.DOCUMENTS.getMediaTypes());
case 4:
return DataSourceInfoUtilities.getCountOfFilesForMimeTypes(currentDataSource, FileTypeUtils.FileTypeCategory.EXECUTABLE.getMediaTypes());
default:
break;
}
}
return null;
}
@Override
public String getColumnName(int column) {
return columnHeaders.get(column);
}
}
/**
* Table model for the files table model to display counts of specific file
* types by category found in the currently selected data source.
*/
@Messages({"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.type.header=File Type",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.count.header=Count"})
private class FilesByCategoryTableModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
private final DataSource currentDataSource;
private final List<String> columnHeaders = new ArrayList<>();
private static final int ALL_FILES_ROW_INDEX = 0;
private static final int ALLOCATED_FILES_ROW_INDEX = 1;
private static final int UNALLOCATED_FILES_ROW_INDEX = 2;
private static final int SLACK_FILES_ROW_INDEX = 3;
private static final int DIRECTORIES_ROW_INDEX = 4;
/**
* Create a FilesByCategoryTableModel for the speicified datasource.
*
* @param selectedDataSource the datasource which this
* FilesByCategoryTablemodel will represent
*/
FilesByCategoryTableModel(DataSource selectedDataSource) {
columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_type_header());
columnHeaders.add(Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_count_header());
currentDataSource = selectedDataSource;
}
@Override
public int getRowCount() {
//should be kept equal to the number of types we are displaying in the tables
return 5;
}
@Override
public int getColumnCount() {
return columnHeaders.size();
}
@Messages({
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.all.row=All",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.allocated.row=Allocated",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.unallocated.row=Unallocated",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.slack.row=Slack",
"DataSourceSummaryCountsPanel.FilesByCategoryTableModel.directory.row=Directory"
})
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (columnIndex == 0) {
switch (rowIndex) {
case ALL_FILES_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_all_row();
case ALLOCATED_FILES_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_allocated_row();
case UNALLOCATED_FILES_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_unallocated_row();
case SLACK_FILES_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_slack_row();
case DIRECTORIES_ROW_INDEX:
return Bundle.DataSourceSummaryCountsPanel_FilesByCategoryTableModel_directory_row();
default:
break;
}
} else if (columnIndex == 1 && currentDataSource != null) {
switch (rowIndex) {
case 0:
return allFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : allFilesCountsMap.get(currentDataSource.getId());
case 1:
//All files should be either allocated or unallocated as dir_flags only has two values so any file that isn't unallocated is allocated
Long unallocatedFilesCount = unallocatedFilesCountsMap.get(currentDataSource.getId());
Long allFilesCount = allFilesCountsMap.get(currentDataSource.getId());
if (allFilesCount == null) {
return 0;
} else if (unallocatedFilesCount == null) {
return allFilesCount;
} else {
return allFilesCount - unallocatedFilesCount;
}
case 2:
return unallocatedFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : unallocatedFilesCountsMap.get(currentDataSource.getId());
case 3:
return slackFilesCountsMap.get(currentDataSource.getId()) == null ? 0 : slackFilesCountsMap.get(currentDataSource.getId());
case 4:
return directoriesCountsMap.get(currentDataSource.getId()) == null ? 0 : directoriesCountsMap.get(currentDataSource.getId());
default:
break;
}
}
return null;
}
@Override
public String getColumnName(int column) {
return columnHeaders.get(column);
}
}
} }

View File

@ -19,12 +19,12 @@
package org.sleuthkit.autopsy.casemodule.datasourcesummary; package org.sleuthkit.autopsy.casemodule.datasourcesummary;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.Map;
import java.util.HashMap;
import java.util.logging.Level; import java.util.logging.Level;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import javax.swing.table.DefaultTableModel; import javax.swing.table.DefaultTableModel;
import org.apache.commons.lang3.StringUtils;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.DataSource; import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.datamodel.Image; import org.sleuthkit.datamodel.Image;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -36,23 +36,47 @@ class DataSourceSummaryDetailsPanel extends javax.swing.JPanel {
//Because this panel was made using the gridbaglayout and netbean's Customize Layout tool it will be best to continue to modify it through that //Because this panel was made using the gridbaglayout and netbean's Customize Layout tool it will be best to continue to modify it through that
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Map<Long, String> osDetailMap = new HashMap<>();
private static final Integer SIZE_COVERSION_CONSTANT = 1000; private static final Integer SIZE_COVERSION_CONSTANT = 1000;
private static final DecimalFormat APPROXIMATE_SIZE_FORMAT = new DecimalFormat("#.##"); private static final DecimalFormat APPROXIMATE_SIZE_FORMAT = new DecimalFormat("#.##");
private final Map<Long, Long> unallocatedFilesSizeMap;
private final Map<Long, String> usageMap;
private static final Logger logger = Logger.getLogger(DataSourceSummaryDetailsPanel.class.getName()); private static final Logger logger = Logger.getLogger(DataSourceSummaryDetailsPanel.class.getName());
private DataSource dataSource;
/** /**
* Creates new form DataSourceSummaryDetailsPanel * Creates new form DataSourceSummaryDetailsPanel
*/ */
@Messages({"DataSourceSummaryDetailsPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.", @Messages({"DataSourceSummaryDetailsPanel.getDataSources.error.text=Failed to get the list of datasources for the current case.",
"DataSourceSummaryDetailsPanel.getDataSources.error.title=Load Failure"}) "DataSourceSummaryDetailsPanel.getDataSources.error.title=Load Failure"})
DataSourceSummaryDetailsPanel(Map<Long, String> usageMap) { DataSourceSummaryDetailsPanel() {
initComponents(); initComponents();
this.usageMap = usageMap; setDataSource(null);
this.unallocatedFilesSizeMap = DataSourceInfoUtilities.getSizeOfUnallocatedFiles(); }
osDetailMap = DataSourceInfoUtilities.getOperatingSystems();
/**
* The datasource currently used as the model in this panel.
*
* @return The datasource currently being used as the model in this panel.
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* Sets datasource to visualize in the panel.
*
* @param dataSource The datasource to use in this panel.
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
if (dataSource == null || !Case.isCaseOpen()) {
updateDetailsPanelData(null, null, null, null);
} else {
updateDetailsPanelData(dataSource,
DataSourceInfoUtilities.getSizeOfUnallocatedFiles(dataSource),
DataSourceInfoUtilities.getOperatingSystems(dataSource),
DataSourceInfoUtilities.getDataSourceType(dataSource));
}
} }
/** /**
@ -60,77 +84,80 @@ class DataSourceSummaryDetailsPanel extends javax.swing.JPanel {
* *
* @param selectedDataSource the DataSource to display details about. * @param selectedDataSource the DataSource to display details about.
*/ */
void updateDetailsPanelData(DataSource selectedDataSource) { private void updateDetailsPanelData(DataSource selectedDataSource, Long unallocatedFilesSize, String osDetails, String usage) {
clearTableValues(); clearTableValues();
if (selectedDataSource != null) { if (selectedDataSource != null) {
String sizeString = ""; unallocatedSizeValue.setText(getSizeString(unallocatedFilesSize));
String sectorSizeString = ""; operatingSystemValue.setText(StringUtils.isBlank(osDetails) ? "" : osDetails);
String md5String = ""; dataSourceUsageValue.setText(StringUtils.isBlank(usage) ? "" : usage);
String sha1String = "";
String sha256String = ""; timeZoneValue.setText(selectedDataSource.getTimeZone());
String acquisitionDetailsString = "";
String imageTypeString = "";
String[] filePaths = new String[0];
String osDetailString = osDetailMap.get(selectedDataSource.getId()) == null ? "" : osDetailMap.get(selectedDataSource.getId());
String dataSourceTypeString = usageMap.get(selectedDataSource.getId()) == null ? "" : usageMap.get(selectedDataSource.getId());
try {
acquisitionDetailsString = selectedDataSource.getAcquisitionDetails();
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get aquisition details for selected data source", ex);
}
if (selectedDataSource instanceof Image) {
imageTypeString = ((Image) selectedDataSource).getType().getName();
filePaths = ((Image) selectedDataSource).getPaths();
sizeString = getSizeString(selectedDataSource.getSize());
sectorSizeString = getSizeString(((Image) selectedDataSource).getSsize());
try {
//older databases may have null as the hash values
md5String = ((Image) selectedDataSource).getMd5();
if (md5String == null) {
md5String = "";
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get MD5 for selected data source", ex);
}
try {
sha1String = ((Image) selectedDataSource).getSha1();
if (sha1String == null) {
sha1String = "";
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get SHA1 for selected data source", ex);
}
try {
sha256String = ((Image) selectedDataSource).getSha256();
if (sha256String == null) {
sha256String = "";
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get SHA256 for selected data source", ex);
}
}
displayNameValue.setText(selectedDataSource.getName()); displayNameValue.setText(selectedDataSource.getName());
originalNameValue.setText(selectedDataSource.getName()); originalNameValue.setText(selectedDataSource.getName());
deviceIdValue.setText(selectedDataSource.getDeviceId()); deviceIdValue.setText(selectedDataSource.getDeviceId());
dataSourceUsageValue.setText(dataSourceTypeString);
operatingSystemValue.setText(osDetailString); try {
timeZoneValue.setText(selectedDataSource.getTimeZone()); acquisitionDetailsTextArea.setText(selectedDataSource.getAcquisitionDetails());
acquisitionDetailsTextArea.setText(acquisitionDetailsString); } catch (TskCoreException ex) {
imageTypeValue.setText(imageTypeString); logger.log(Level.WARNING, "Unable to get aquisition details for selected data source", ex);
sizeValue.setText(sizeString); }
unallocatedSizeValue.setText(getSizeString(unallocatedFilesSizeMap.get(selectedDataSource.getId())));
sectorSizeValue.setText(sectorSizeString); if (selectedDataSource instanceof Image) {
md5HashValue.setText(md5String); setFieldsForImage((Image) selectedDataSource);
sha1HashValue.setText(sha1String);
sha256HashValue.setText(sha256String);
for (String path : filePaths) {
((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path});
} }
} }
updateFieldVisibility(); updateFieldVisibility();
this.repaint(); this.repaint();
} }
/**
* Sets text fields for an image. This should be called after
* clearTableValues and before updateFieldVisibility to ensure the proper
* rendering.
*
* @param selectedImage The selected image.
*/
private void setFieldsForImage(Image selectedImage) {
imageTypeValue.setText(selectedImage.getType().getName());
sizeValue.setText(getSizeString(selectedImage.getSize()));
sectorSizeValue.setText(getSizeString(selectedImage.getSsize()));
for (String path : selectedImage.getPaths()) {
((DefaultTableModel) filePathsTable.getModel()).addRow(new Object[]{path});
}
try {
//older databases may have null as the hash values
String md5String = selectedImage.getMd5();
if (md5String == null) {
md5String = "";
}
md5HashValue.setText(md5String);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get MD5 for selected data source", ex);
}
try {
String sha1String = selectedImage.getSha1();
if (sha1String == null) {
sha1String = "";
}
sha1HashValue.setText(sha1String);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get SHA1 for selected data source", ex);
}
try {
String sha256String = selectedImage.getSha256();
if (sha256String == null) {
sha256String = "";
}
sha256HashValue.setText(sha256String);
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Unable to get SHA256 for selected data source", ex);
}
}
/** /**
* Get a long size in bytes as a string formated to be read by users. * Get a long size in bytes as a string formated to be read by users.
* *
@ -147,7 +174,7 @@ class DataSourceSummaryDetailsPanel extends javax.swing.JPanel {
"DataSourceSummaryDetailsPanel.units.terabytes= TB", "DataSourceSummaryDetailsPanel.units.terabytes= TB",
"DataSourceSummaryDetailsPanel.units.petabytes= PB" "DataSourceSummaryDetailsPanel.units.petabytes= PB"
}) })
private String getSizeString(Long size) { private static String getSizeString(Long size) {
if (size == null) { if (size == null) {
return ""; return "";
} }

View File

@ -68,6 +68,11 @@
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/> <Layout class="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout"/>
<SubComponents> <SubComponents>
<Container class="javax.swing.JTabbedPane" name="dataSourceTabbedPane"> <Container class="javax.swing.JTabbedPane" name="dataSourceTabbedPane">
<AuxValues>
<AuxValue name="JavaCodeGenerator_CreateCodeCustom" type="java.lang.String" value="dataSourceSummaryTabbedPane"/>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
<Constraints> <Constraints>
<Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription"> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout" value="org.netbeans.modules.form.compat2.layouts.support.JSplitPaneSupportLayout$JSplitPaneConstraintsDescription">
<JSplitPaneConstraints position="right"/> <JSplitPaneConstraints position="right"/>

View File

@ -27,7 +27,6 @@ import java.util.Observer;
import java.util.Set; import java.util.Set;
import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionEvent;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
import org.sleuthkit.autopsy.ingest.IngestManager; import org.sleuthkit.autopsy.ingest.IngestManager;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent; import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent;
import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent.Reason; import org.sleuthkit.autopsy.ingest.events.DataSourceAnalysisCompletedEvent.Reason;
@ -41,10 +40,8 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED); private static final Set<IngestManager.IngestJobEvent> INGEST_JOB_EVENTS_OF_INTEREST = EnumSet.of(IngestManager.IngestJobEvent.DATA_SOURCE_ANALYSIS_COMPLETED);
private final DataSourceSummaryCountsPanel countsPanel;
private final DataSourceSummaryDetailsPanel detailsPanel;
private final DataSourceBrowser dataSourcesPanel; private final DataSourceBrowser dataSourcesPanel;
private final IngestJobInfoPanel ingestHistoryPanel; private final DataSourceSummaryTabbedPane dataSourceSummaryTabbedPane;
/** /**
* Creates new form DataSourceSummaryDialog for displaying a summary of the * Creates new form DataSourceSummaryDialog for displaying a summary of the
@ -52,30 +49,20 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
* datasource. * datasource.
*/ */
@Messages({ @Messages({
"DataSourceSummaryDialog.window.title=Data Sources Summary", "DataSourceSummaryDialog.window.title=Data Sources Summary"
"DataSourceSummaryDialog.countsTab.title=Counts",
"DataSourceSummaryDialog.detailsTab.title=Details",
"DataSourceSummaryDialog.ingestHistoryTab.title=Ingest History"
}) })
DataSourceSummaryDialog(Frame owner) { DataSourceSummaryDialog(Frame owner) {
super(owner, Bundle.DataSourceSummaryDialog_window_title(), true); super(owner, Bundle.DataSourceSummaryDialog_window_title(), true);
Map<Long, String> usageMap = DataSourceInfoUtilities.getDataSourceTypes(); Map<Long, String> usageMap = DataSourceInfoUtilities.getDataSourceTypes();
Map<Long, Long> fileCountsMap = DataSourceInfoUtilities.getCountsOfFiles(); Map<Long, Long> fileCountsMap = DataSourceInfoUtilities.getCountsOfFiles();
countsPanel = new DataSourceSummaryCountsPanel(fileCountsMap);
detailsPanel = new DataSourceSummaryDetailsPanel(usageMap);
dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap); dataSourcesPanel = new DataSourceBrowser(usageMap, fileCountsMap);
ingestHistoryPanel = new IngestJobInfoPanel(); dataSourceSummaryTabbedPane = new DataSourceSummaryTabbedPane();
initComponents(); initComponents();
dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel); dataSourceSummarySplitPane.setLeftComponent(dataSourcesPanel);
dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_detailsTab_title(), detailsPanel);
dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_countsTab_title(), countsPanel);
dataSourceTabbedPane.addTab(Bundle.DataSourceSummaryDialog_ingestHistoryTab_title(), ingestHistoryPanel);
dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> { dataSourcesPanel.addListSelectionListener((ListSelectionEvent e) -> {
if (!e.getValueIsAdjusting()) { if (!e.getValueIsAdjusting()) {
DataSource selectedDataSource = dataSourcesPanel.getSelectedDataSource(); DataSource selectedDataSource = dataSourcesPanel.getSelectedDataSource();
countsPanel.updateCountsTableData(selectedDataSource); dataSourceSummaryTabbedPane.setDataSource(selectedDataSource);
detailsPanel.updateDetailsPanelData(selectedDataSource);
ingestHistoryPanel.setDataSource(selectedDataSource);
this.repaint(); this.repaint();
} }
}); });
@ -116,7 +103,7 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
closeButton = new javax.swing.JButton(); closeButton = new javax.swing.JButton();
dataSourceSummarySplitPane = new javax.swing.JSplitPane(); dataSourceSummarySplitPane = new javax.swing.JSplitPane();
dataSourceTabbedPane = new javax.swing.JTabbedPane(); javax.swing.JTabbedPane dataSourceTabbedPane = dataSourceSummaryTabbedPane;
org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(DataSourceSummaryDialog.class, "DataSourceSummaryDialog.closeButton.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(closeButton, org.openide.util.NbBundle.getMessage(DataSourceSummaryDialog.class, "DataSourceSummaryDialog.closeButton.text")); // NOI18N
closeButton.addActionListener(new java.awt.event.ActionListener() { closeButton.addActionListener(new java.awt.event.ActionListener() {
@ -173,6 +160,5 @@ final class DataSourceSummaryDialog extends javax.swing.JDialog implements Obser
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JButton closeButton; private javax.swing.JButton closeButton;
private javax.swing.JSplitPane dataSourceSummarySplitPane; private javax.swing.JSplitPane dataSourceSummarySplitPane;
private javax.swing.JTabbedPane dataSourceTabbedPane;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -0,0 +1,81 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed 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.sleuthkit.autopsy.casemodule.datasourcesummary;
import javax.swing.JTabbedPane;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.IngestJobInfoPanel;
import org.sleuthkit.datamodel.DataSource;
/**
* A tabbed pane showing the summary of a data source including tabs of:
* DataSourceSummaryCountsPanel, DataSourceSummaryDetailsPanel, and
* IngestJobInfoPanel.
*/
@Messages({
"DataSourceSummaryTabbedPane_countsTab_title=Counts",
"DataSourceSummaryTabbedPane_detailsTab_title=Details",
"DataSourceSummaryTabbedPane_userActivityTab_title=User Activity",
"DataSourceSummaryTabbedPane_ingestHistoryTab_title=Ingest History"
})
public class DataSourceSummaryTabbedPane extends JTabbedPane {
private static final long serialVersionUID = 1L;
private final DataSourceSummaryCountsPanel countsPanel = new DataSourceSummaryCountsPanel();
private final DataSourceSummaryDetailsPanel detailsPanel = new DataSourceSummaryDetailsPanel();
private final DataSourceSummaryUserActivityPanel userActivityPanel = new DataSourceSummaryUserActivityPanel();
private final IngestJobInfoPanel ingestHistoryPanel = new IngestJobInfoPanel();
private DataSource dataSource = null;
/**
* Constructs a tabbed pane showing the summary of a data source.
*/
public DataSourceSummaryTabbedPane() {
addTab(Bundle.DataSourceSummaryTabbedPane_detailsTab_title(), detailsPanel);
addTab(Bundle.DataSourceSummaryTabbedPane_countsTab_title(), countsPanel);
addTab(Bundle.DataSourceSummaryTabbedPane_userActivityTab_title(), userActivityPanel);
addTab(Bundle.DataSourceSummaryTabbedPane_ingestHistoryTab_title(), ingestHistoryPanel);
}
/**
* The datasource currently used as the model in this panel.
*
* @return The datasource currently being used as the model in this panel.
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* Sets datasource to visualize in the tabbed panel.
*
* @param dataSource The datasource to use in this panel.
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
detailsPanel.setDataSource(dataSource);
countsPanel.setDataSource(dataSource);
userActivityPanel.setDataSource(dataSource);
ingestHistoryPanel.setDataSource(dataSource);
}
}

View File

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_generateFQN" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_generateMnemonicsCode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_i18nAutoMode" type="java.lang.Boolean" value="true"/>
<AuxValue name="FormSettings_layoutCodeTarget" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_listenerGenerationStyle" type="java.lang.Integer" value="0"/>
<AuxValue name="FormSettings_variablesLocal" type="java.lang.Boolean" value="false"/>
<AuxValue name="FormSettings_variablesModifier" type="java.lang.Integer" value="2"/>
</AuxValues>
<Layout>
<DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="programsRunLabel" min="-2" pref="155" max="-2" attributes="0"/>
<Component id="topProgramsScrollPane" min="-2" pref="460" max="-2" attributes="0"/>
</Group>
<EmptySpace pref="128" max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
<DimensionLayout dim="1">
<Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Component id="programsRunLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="topProgramsScrollPane" min="-2" max="-2" attributes="0"/>
<EmptySpace max="32767" attributes="0"/>
</Group>
</Group>
</DimensionLayout>
</Layout>
<SubComponents>
<Component class="javax.swing.JLabel" name="programsRunLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/casemodule/datasourcesummary/Bundle.properties" key="DataSourceSummaryUserActivityPanel.programsRunLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Container class="javax.swing.JScrollPane" name="topProgramsScrollPane">
<Properties>
<Property name="preferredSize" type="java.awt.Dimension" editor="org.netbeans.beaninfo.editors.DimensionEditor">
<Dimension value="[750, 187]"/>
</Property>
</Properties>
<AuxValues>
<AuxValue name="autoScrollPane" type="java.lang.Boolean" value="true"/>
</AuxValues>
<Layout class="org.netbeans.modules.form.compat2.layouts.support.JScrollPaneSupportLayout"/>
<SubComponents>
<Component class="javax.swing.JTable" name="topProgramsTable">
</Component>
</SubComponents>
</Container>
</SubComponents>
</Form>

View File

@ -0,0 +1,291 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed 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.sleuthkit.autopsy.casemodule.datasourcesummary;
import java.awt.Component;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.swing.JLabel;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellRenderer;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.datamodel.DataSource;
/**
* A panel to display user activity.
*/
@Messages({
"DataSourceSummaryUserActivityPanel_tab_title=User Activity",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header=Program",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header=Folder",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header=Run Times",
"DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header=Last Run"
})
public class DataSourceSummaryUserActivityPanel extends javax.swing.JPanel {
private static final long serialVersionUID = 1L;
private static final DateFormat DATETIME_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault());
private static final int TOP_PROGS_COUNT = 10;
private static final DefaultTableCellRenderer RIGHT_ALIGNED_RENDERER = new DefaultTableCellRenderer();
static {
RIGHT_ALIGNED_RENDERER.setHorizontalAlignment(JLabel.RIGHT);
}
private DataSource dataSource;
/**
* Creates new form DataSourceUserActivityPanel
*/
public DataSourceSummaryUserActivityPanel() {
initComponents();
topProgramsTable.getTableHeader().setReorderingAllowed(false);
}
/**
* The datasource currently used as the model in this panel.
*
* @return The datasource currently being used as the model in this panel.
*/
public DataSource getDataSource() {
return dataSource;
}
/**
* Sets datasource to visualize in the panel.
*
* @param dataSource The datasource to use in this panel.
*/
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
if (dataSource == null || !Case.isCaseOpen()) {
updateTopPrograms(new TopProgramsModel(null));
} else {
updateTopPrograms(getTopProgramsModel(dataSource));
}
}
/**
* Updates the Top Programs Table in the gui.
*
* @param data The data in Object[][] form to be used by the
* DefaultTableModel.
*/
private void updateTopPrograms(TopProgramsModel model) {
topProgramsTable.setModel(model);
topProgramsTable.getColumnModel().getColumn(0).setPreferredWidth(250);
topProgramsTable.getColumnModel().getColumn(0).setCellRenderer(PATH_CELL_RENDERER);
topProgramsTable.getColumnModel().getColumn(1).setPreferredWidth(150);
topProgramsTable.getColumnModel().getColumn(2).setCellRenderer(RIGHT_ALIGNED_RENDERER);
topProgramsTable.getColumnModel().getColumn(2).setPreferredWidth(80);
topProgramsTable.getColumnModel().getColumn(3).setPreferredWidth(150);
topProgramsScrollPane.getVerticalScrollBar().setValue(0);
this.repaint();
}
/**
* The counts of top programs run.
*
* @param selectedDataSource The DataSource.
*
* @return The JTable data model of counts of program runs.
*/
private static TopProgramsModel getTopProgramsModel(DataSource selectedDataSource) {
List<DataSourceInfoUtilities.TopProgramsResult> topProgramList
= DataSourceInfoUtilities.getTopPrograms(selectedDataSource, TOP_PROGS_COUNT);
if (topProgramList == null) {
return new TopProgramsModel(null);
} else {
return new TopProgramsModel(topProgramList);
}
}
/**
* A POJO defining the values present in the name cell. Defines the name as
* well as the path for the tooltip.
*/
private static class ProgramNameCellValue {
private final String programName;
private final String programPath;
ProgramNameCellValue(String programName, String programPath) {
this.programName = programName;
this.programPath = programPath;
}
@Override
public String toString() {
// override so that the value in the cell reads as programName
return programName;
}
/**
* @return The program name.
*/
String getProgramName() {
return programName;
}
/**
* @return The path of the program.
*/
String getProgramPath() {
return programPath;
}
}
/**
* Defines a cell renderer for the first cell rendering the name as the text
* and path as the tooltip.
*/
private static TableCellRenderer PATH_CELL_RENDERER = new DefaultTableCellRenderer() {
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
JLabel c = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (value instanceof ProgramNameCellValue) {
ProgramNameCellValue cellValue = (ProgramNameCellValue) value;
c.setToolTipText(cellValue.getProgramPath());
}
return c;
}
};
/**
* Defines the table model for a JTable of the programs. Accepts a list of
* TopProgramsResult objects as rows data source.
*/
private static class TopProgramsModel extends AbstractTableModel {
private static final long serialVersionUID = 1L;
// column headers for artifact counts table
private static final String[] TOP_PROGS_COLUMN_HEADERS = new String[]{
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_name_header(),
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_folder_header(),
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_count_header(),
Bundle.DataSourceSummaryUserActivityPanel_TopProgramsTableModel_lastrun_header()
};
private final List<DataSourceInfoUtilities.TopProgramsResult> programResults;
/**
* Main constructor.
*
* @param programResults The results to display.
*/
TopProgramsModel(List<DataSourceInfoUtilities.TopProgramsResult> programResults) {
this.programResults = programResults == null ? new ArrayList<>() : Collections.unmodifiableList(programResults);
}
@Override
public String getColumnName(int column) {
return column < 0 || column >= TOP_PROGS_COLUMN_HEADERS.length ? null : TOP_PROGS_COLUMN_HEADERS[column];
}
@Override
public int getRowCount() {
return programResults.size();
}
@Override
public int getColumnCount() {
return TOP_PROGS_COLUMN_HEADERS.length;
}
@Override
public Object getValueAt(int rowIndex, int columnIndex) {
if (rowIndex < 0 || rowIndex >= programResults.size()) {
return null;
}
DataSourceInfoUtilities.TopProgramsResult result = programResults.get(rowIndex);
switch (columnIndex) {
case 0:
return new ProgramNameCellValue(result.getProgramName(), result.getProgramPath());
case 1:
return DataSourceInfoUtilities.getShortFolderName(result.getProgramPath(), result.getProgramName());
case 2:
return result.getRunTimes();
case 3:
return result.getLastRun() == null ? null : DATETIME_FORMAT.format(result.getLastRun());
default:
return null;
}
}
}
/**
* This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always
* regenerated by the Form Editor.
*/
@SuppressWarnings("unchecked")
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() {
javax.swing.JLabel programsRunLabel = new javax.swing.JLabel();
topProgramsScrollPane = new javax.swing.JScrollPane();
topProgramsTable = new javax.swing.JTable();
org.openide.awt.Mnemonics.setLocalizedText(programsRunLabel, org.openide.util.NbBundle.getMessage(DataSourceSummaryUserActivityPanel.class, "DataSourceSummaryUserActivityPanel.programsRunLabel.text")); // NOI18N
topProgramsScrollPane.setPreferredSize(new java.awt.Dimension(750, 187));
topProgramsScrollPane.setViewportView(topProgramsTable);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout);
layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(programsRunLabel, javax.swing.GroupLayout.PREFERRED_SIZE, 155, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(topProgramsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, 460, javax.swing.GroupLayout.PREFERRED_SIZE))
.addContainerGap(128, Short.MAX_VALUE))
);
layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup()
.addContainerGap()
.addComponent(programsRunLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(topProgramsScrollPane, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
);
}// </editor-fold>//GEN-END:initComponents
// Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JScrollPane topProgramsScrollPane;
private javax.swing.JTable topProgramsTable;
// End of variables declaration//GEN-END:variables
}

View File

@ -0,0 +1,37 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed 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.sleuthkit.autopsy.casemodule.datasourcesummary;
import javax.swing.table.DefaultTableModel;
/**
* A Table model where cells are not editable.
*/
class NonEditableTableModel extends DefaultTableModel {
private static final long serialVersionUID = 1L;
NonEditableTableModel(Object[][] data, Object[] columnNames) {
super(data, columnNames);
}
@Override
public boolean isCellEditable(int row, int column) {
return false;
}
}

View File

@ -35,8 +35,12 @@ import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.datamodel.NodeProperty; import org.sleuthkit.autopsy.datamodel.NodeProperty;
import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.AccountDeviceInstance; import org.sleuthkit.datamodel.AccountDeviceInstance;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME;
import org.sleuthkit.datamodel.CommunicationsFilter; import org.sleuthkit.datamodel.CommunicationsFilter;
import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.TskCoreException;
/** /**
* Node to represent an Account Device Instance in the CVT * Node to represent an Account Device Instance in the CVT
@ -49,6 +53,8 @@ final class AccountDeviceInstanceNode extends AbstractNode {
private final CommunicationsManager commsManager; private final CommunicationsManager commsManager;
private final Account account; private final Account account;
private static final BlackboardAttribute.Type NAME_ATTRIBUTE = new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(TSK_NAME.getTypeID()));
AccountDeviceInstanceNode(AccountDeviceInstanceKey accountDeviceInstanceKey, CommunicationsManager commsManager) { AccountDeviceInstanceNode(AccountDeviceInstanceKey accountDeviceInstanceKey, CommunicationsManager commsManager) {
super(Children.LEAF, Lookups.fixed(accountDeviceInstanceKey, commsManager)); super(Children.LEAF, Lookups.fixed(accountDeviceInstanceKey, commsManager));
this.accountDeviceInstanceKey = accountDeviceInstanceKey; this.accountDeviceInstanceKey = accountDeviceInstanceKey;
@ -122,15 +128,17 @@ final class AccountDeviceInstanceNode extends AbstractNode {
@Override @Override
public String getShortDescription() { public String getShortDescription() {
List<PersonaAccount> personaList; List<PersonaAccount> personaList;
List<BlackboardArtifact> contactArtifactList;
try { try {
personaList = CVTPersonaCache.getPersonaAccounts(account); personaList = CVTPersonaCache.getPersonaAccounts(account);
contactArtifactList = ContactCache.getContacts(account);
} catch (ExecutionException ex) { } catch (ExecutionException ex) {
logger.log(Level.WARNING, "Failed to retrieve Persona details for node.", ex); logger.log(Level.WARNING, "Failed to retrieve Persona details for node.", ex);
return getDisplayName(); return getDisplayName();
} }
String personaName; String personaName;
if (!personaList.isEmpty()) { if (personaList != null && !personaList.isEmpty()) {
personaName = personaList.get(0).getPersona().getName(); personaName = personaList.get(0).getPersona().getName();
if (personaList.size() > 1) { if (personaList.size() > 1) {
personaName += Bundle.AccountInstanceNode_Tooltip_suffix(Integer.toString(personaList.size())); personaName += Bundle.AccountInstanceNode_Tooltip_suffix(Integer.toString(personaList.size()));
@ -139,6 +147,19 @@ final class AccountDeviceInstanceNode extends AbstractNode {
personaName = "None"; personaName = "None";
} }
return Bundle.AccountInstanceNode_Tooltip_Template(getDisplayName(), personaName); String contactName = getDisplayName();
if (contactArtifactList != null && !contactArtifactList.isEmpty()) {
try {
BlackboardArtifact contactArtifact = contactArtifactList.get(0);
BlackboardAttribute attribute = contactArtifact.getAttribute(NAME_ATTRIBUTE);
if (attribute != null) {
contactName = attribute.getValueString();
}
} catch (TskCoreException ex) {
logger.log(Level.WARNING, "Failed to retrive name attribute from contact artifact.", ex);
}
}
return Bundle.AccountInstanceNode_Tooltip_Template(contactName, personaName);
} }
} }

View File

@ -0,0 +1,166 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed 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.sleuthkit.autopsy.communications;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A singleton cache of the Contact artifacts for accounts. The map of account
* unique ids to list of contact artifacts is stored in a LoadingCache which
* expires after 10 of non-use.
*
*/
final class ContactCache {
private static final Logger logger = Logger.getLogger(ContactCache.class.getName());
private static ContactCache instance;
private final LoadingCache<String, Map<String, List<BlackboardArtifact>>> accountMap;
/**
* Returns the list of Contacts for the given Account.
*
* @param account Account instance.
*
* @return List of TSK_CONTACT artifacts that references the given Account.
* An empty list is returned if no contacts are found.
*
* @throws ExecutionException
*/
static synchronized List<BlackboardArtifact> getContacts(Account account) throws ExecutionException {
return getInstance().accountMap.get("realMap").get(account.getTypeSpecificID());
}
/**
* Force the cache to invalidate all entries.
*/
static synchronized void invalidateCache() {
getInstance().accountMap.invalidateAll();
}
/**
* Construct a new instance.
*/
private ContactCache() {
accountMap = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build(
new CacheLoader<String, Map<String, List<BlackboardArtifact>>>() {
@Override
public Map<String, List<BlackboardArtifact>> load(String key) {
try {
return buildMap();
} catch (SQLException | TskCoreException ex) {
logger.log(Level.WARNING, "Failed to build account to contact map", ex);
}
return new HashMap<>(); // Return an empty map if there is an exception to avoid NPE and continual trying.
}
});
PropertyChangeListener ingestListener = pce -> {
String eventType = pce.getPropertyName();
if (eventType.equals(DATA_ADDED.toString())) {
ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue();
if (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) {
invalidateCache();
}
}
};
Case.addEventTypeSubscriber(EnumSet.of(Case.Events.CURRENT_CASE), (PropertyChangeEvent event) -> {
if (event.getNewValue() == null) {
invalidateCache();
}
});
IngestManager.getInstance().addIngestModuleEventListener(EnumSet.of(DATA_ADDED), ingestListener);
}
/**
* Returns the singleton instance of the cache object.
*
* @return AccountCache instance.
*/
private static synchronized ContactCache getInstance() {
if (instance == null) {
instance = new ContactCache();
}
return instance;
}
/**
* Builds the map of account IDs to contacts that reference them.
*
* @return A map of account IDs to contact artifacts.
*
* @throws TskCoreException
* @throws SQLException
*/
private Map<String, List<BlackboardArtifact>> buildMap() throws TskCoreException, SQLException {
Map<String, List<BlackboardArtifact>> acctMap = new HashMap<>();
List<BlackboardArtifact> contactList = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT);
for (BlackboardArtifact contactArtifact : contactList) {
List<BlackboardAttribute> contactAttributes = contactArtifact.getAttributes();
for (BlackboardAttribute attribute : contactAttributes) {
String typeName = attribute.getAttributeType().getTypeName();
if (typeName.startsWith("TSK_EMAIL")
|| typeName.startsWith("TSK_PHONE")
|| typeName.startsWith("TSK_NAME")
|| typeName.startsWith("TSK_ID")) {
String accountID = attribute.getValueString();
List<BlackboardArtifact> artifactList = acctMap.getOrDefault(accountID, new ArrayList<>());
acctMap.put(accountID, artifactList);
if (!artifactList.contains(contactArtifact)) {
artifactList.add(contactArtifact);
}
}
}
}
return acctMap;
}
}

View File

@ -18,9 +18,12 @@
*/ */
package org.sleuthkit.autopsy.communications; package org.sleuthkit.autopsy.communications;
import java.awt.Component;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;
import java.util.TimeZone; import java.util.TimeZone;
import javax.swing.table.TableCellRenderer;
import org.netbeans.swing.outline.Outline;
import org.sleuthkit.autopsy.core.UserPreferences; import org.sleuthkit.autopsy.core.UserPreferences;
import org.sleuthkit.autopsy.datamodel.accounts.Accounts; import org.sleuthkit.autopsy.datamodel.accounts.Accounts;
import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.Account;
@ -47,5 +50,29 @@ public final class Utils {
static public final String getIconFilePath(Account.Type type) { static public final String getIconFilePath(Account.Type type) {
return Accounts.getIconFilePath(type); return Accounts.getIconFilePath(type);
} }
static public void setColumnWidths(Outline outline) {
int margin = 4;
int padding = 8;
final int rows = Math.min(100, outline.getRowCount());
for (int column = 0; column < outline.getColumnCount(); column++) {
int columnWidthLimit = 500;
int columnWidth = 0;
// find the maximum width needed to fit the values for the first 100 rows, at most
for (int row = 0; row < rows; row++) {
TableCellRenderer renderer = outline.getCellRenderer(row, column);
Component comp = outline.prepareRenderer(renderer, row, column);
columnWidth = Math.max(comp.getPreferredSize().width, columnWidth);
}
columnWidth += 2 * margin + padding; // add margin and regular padding
columnWidth = Math.min(columnWidth, columnWidthLimit);
outline.getColumnModel().getColumn(column).setPreferredWidth(columnWidth);
}
}
} }

View File

@ -73,5 +73,5 @@ SummaryViewer.referencesLabel.text=Communication References:
SummaryViewer.referencesDataLabel.text=<reference count> SummaryViewer.referencesDataLabel.text=<reference count>
SummaryViewer.contactsLabel.text=Book Entries: SummaryViewer.contactsLabel.text=Book Entries:
SummaryViewer.accountCountry.text=<account country> SummaryViewer.accountCountry.text=<account country>
SummaryViewer.fileRefPane.border.title=File Referernce(s) in Current Case SummaryViewer.fileRefPane.border.title=File References in Current Case
SummaryViewer.selectAccountFileRefLabel.text=<Select a single account to see File References> SummaryViewer.selectAccountFileRefLabel.text=<Select a single account to see File References>

View File

@ -38,6 +38,8 @@ import javax.swing.JPanel;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities; import javax.swing.SwingUtilities;
import static javax.swing.SwingUtilities.isDescendingFrom; import static javax.swing.SwingUtilities.isDescendingFrom;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import org.netbeans.swing.outline.DefaultOutlineModel; import org.netbeans.swing.outline.DefaultOutlineModel;
import org.netbeans.swing.outline.Outline; import org.netbeans.swing.outline.Outline;
import org.openide.explorer.ExplorerManager; import org.openide.explorer.ExplorerManager;
@ -50,6 +52,7 @@ import org.openide.nodes.Node.PropertySet;
import org.openide.util.Lookup; import org.openide.util.Lookup;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.communications.ModifiableProxyLookup; import org.sleuthkit.autopsy.communications.ModifiableProxyLookup;
import org.sleuthkit.autopsy.communications.Utils;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
/** /**
@ -115,6 +118,13 @@ final class MessageViewer extends JPanel implements RelationshipsViewer {
showSelectedThread(); showSelectedThread();
} }
}); });
rootTablePane.getOutlineView().getOutline().getModel().addTableModelListener(new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
Utils.setColumnWidths(rootTablePane.getOutlineView().getOutline());
}
});
threadMessagesPanel.setChildFactory(threadMessageNodeFactory); threadMessagesPanel.setChildFactory(threadMessageNodeFactory);

View File

@ -26,6 +26,9 @@ CallLogArtifactViewer_label_from=From
CallLogArtifactViewer_label_to=To CallLogArtifactViewer_label_to=To
CallLogArtifactViewer_suffix_local=(Local) CallLogArtifactViewer_suffix_local=(Local)
CallLogArtifactViewer_value_unknown=Unknown CallLogArtifactViewer_value_unknown=Unknown
#{0} - contact name
CommunicationArtifactViewerHelper_contact_label=Contact: {0}
CommunicationArtifactViewerHelper_contact_label_unknown=Unknown
CommunicationArtifactViewerHelper_menuitem_copy=Copy CommunicationArtifactViewerHelper_menuitem_copy=Copy
CommunicationArtifactViewerHelper_persona_button_create=Create CommunicationArtifactViewerHelper_persona_button_create=Create
CommunicationArtifactViewerHelper_persona_button_view=View CommunicationArtifactViewerHelper_persona_button_view=View
@ -61,6 +64,7 @@ DefaultArtifactContentViewer.copyMenuItem.text=Copy
DefaultArtifactContentViewer.selectAllMenuItem.text=Select All DefaultArtifactContentViewer.selectAllMenuItem.text=Select All
MessageAccountPanel_button_create_label=Create MessageAccountPanel_button_create_label=Create
MessageAccountPanel_button_view_label=View MessageAccountPanel_button_view_label=View
MessageAccountPanel_contact_label=Contact:
MessageAccountPanel_no_matches=No matches found. MessageAccountPanel_no_matches=No matches found.
MessageAccountPanel_persona_label=Persona: MessageAccountPanel_persona_label=Persona:
MessageAccountPanel_unknown_label=Unknown MessageAccountPanel_unknown_label=Unknown

View File

@ -36,6 +36,7 @@ import org.openide.util.NbBundle;
import org.openide.util.lookup.ServiceProvider; import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository; import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.guiutils.ContactCache;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.Content; import org.sleuthkit.datamodel.Content;
@ -66,7 +67,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
private GridBagLayout m_gridBagLayout = new GridBagLayout(); private GridBagLayout m_gridBagLayout = new GridBagLayout();
private GridBagConstraints m_constraints = new GridBagConstraints(); private GridBagConstraints m_constraints = new GridBagConstraints();
private PersonaAccountFetcher currentAccountFetcher = null; private PersonaAccountFetcher currentAccountFetcher = null;
/** /**
@ -106,9 +107,9 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
// update the view with the call log data // update the view with the call log data
if (callLogViewData != null) { if (callLogViewData != null) {
List<AccountPersonaSearcherData> personaSearchDataList = updateView(callLogViewData); List<AccountPersonaSearcherData> personaSearchDataList = updateView(callLogViewData);
if(!personaSearchDataList.isEmpty()) { if (!personaSearchDataList.isEmpty()) {
currentAccountFetcher = new PersonaAccountFetcher(artifact, personaSearchDataList, this); currentAccountFetcher = new PersonaAccountFetcher(artifact, personaSearchDataList, this);
currentAccountFetcher.execute(); currentAccountFetcher.execute();
} else { } else {
currentAccountFetcher = null; currentAccountFetcher = null;
} }
@ -143,6 +144,8 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
String fromAccountIdentifier = null; String fromAccountIdentifier = null;
String toAccountIdentifier = null; String toAccountIdentifier = null;
List<String> otherParties = null; List<String> otherParties = null;
List<String> toContactNames = null;
List<String> fromContactNames = null;
Content dataSource = artifact.getDataSource(); Content dataSource = artifact.getDataSource();
String deviceId = ((DataSource) dataSource).getDeviceId(); String deviceId = ((DataSource) dataSource).getDeviceId();
@ -186,6 +189,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
String fromAccountAttrValue = fromAccountAttr.getValueString(); String fromAccountAttrValue = fromAccountAttr.getValueString();
if (fromAccountAttrValue.equalsIgnoreCase(deviceId) == false) { if (fromAccountAttrValue.equalsIgnoreCase(deviceId) == false) {
fromAccountIdentifier = fromAccountAttrValue; fromAccountIdentifier = fromAccountAttrValue;
fromContactNames = ContactCache.getContactNameList(fromAccountIdentifier);
} }
} }
@ -195,6 +199,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
String toAccountAttrValue = StringUtils.trim(numbers[0]); String toAccountAttrValue = StringUtils.trim(numbers[0]);
if (toAccountAttrValue.equalsIgnoreCase(deviceId) == false) { if (toAccountAttrValue.equalsIgnoreCase(deviceId) == false) {
toAccountIdentifier = toAccountAttrValue; toAccountIdentifier = toAccountAttrValue;
toContactNames = ContactCache.getContactNameList(toAccountIdentifier);
} }
// if more than one To address, then stick the rest of them in the // if more than one To address, then stick the rest of them in the
@ -228,6 +233,9 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
} }
callLogViewData.setOtherAttributes(extractOtherAttributes(artifact)); callLogViewData.setOtherAttributes(extractOtherAttributes(artifact));
callLogViewData.setFromContactNameList(fromContactNames);
callLogViewData.setToContactNameList(toContactNames);
} }
return callLogViewData; return callLogViewData;
@ -237,9 +245,9 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
* Extract the call time and duration from the artifact and saves in the * Extract the call time and duration from the artifact and saves in the
* CallLogViewData. * CallLogViewData.
* *
* @param artifact Call log artifact. * @param artifact Call log artifact.
* @param callLogViewData CallLogViewData object to save the time & duration * @param callLogViewData CallLogViewData object to save the time & duration
* in. * in.
* *
* @throws TskCoreException * @throws TskCoreException
*/ */
@ -290,7 +298,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
* Update the viewer with the call log data. * Update the viewer with the call log data.
* *
* @param callLogViewData Call log data to update the view with. * @param callLogViewData Call log data to update the view with.
* *
* @return List of AccountPersonaSearcherData objects. * @return List of AccountPersonaSearcherData objects.
*/ */
@NbBundle.Messages({ @NbBundle.Messages({
@ -312,8 +320,13 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
String accountDisplayString = getAccountDisplayString(callLogViewData.getFromAccount(), callLogViewData); String accountDisplayString = getAccountDisplayString(callLogViewData.getFromAccount(), callLogViewData);
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString); CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString);
List<String> contactNames = callLogViewData.getFromContactNameList();
for (String name : contactNames) {
CommunicationArtifactViewerHelper.addContactRow(this, m_gridBagLayout, m_constraints, name);
}
// show persona // show persona
dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getFromAccount())); dataList.addAll(CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getFromAccount()));
} else { } else {
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_value_unknown()); CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_value_unknown());
} }
@ -324,8 +337,13 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
String accountDisplayString = getAccountDisplayString(callLogViewData.getToAccount(), callLogViewData); String accountDisplayString = getAccountDisplayString(callLogViewData.getToAccount(), callLogViewData);
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString); CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, accountDisplayString);
dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getToAccount())); List<String> contactNames = callLogViewData.getToContactNameList();
for (String name : contactNames) {
CommunicationArtifactViewerHelper.addContactRow(this, m_gridBagLayout, m_constraints, name);
}
dataList.addAll(CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, callLogViewData.getToAccount()));
} else { } else {
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_value_unknown()); CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_value_unknown());
} }
@ -335,7 +353,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_to()); CommunicationArtifactViewerHelper.addKey(this, m_gridBagLayout, this.m_constraints, Bundle.CallLogArtifactViewer_label_to());
CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, otherParty); CommunicationArtifactViewerHelper.addValue(this, m_gridBagLayout, this.m_constraints, otherParty);
dataList.addAll( CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, otherParty)); dataList.addAll(CommunicationArtifactViewerHelper.addPersonaRow(this, m_gridBagLayout, this.m_constraints, otherParty));
} }
updateMetadataView(callLogViewData); updateMetadataView(callLogViewData);
@ -353,7 +371,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
this.setLayout(m_gridBagLayout); this.setLayout(m_gridBagLayout);
this.revalidate(); this.revalidate();
this.repaint(); this.repaint();
return dataList; return dataList;
} }
@ -441,7 +459,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
* local account, if it is known. If it is, it appends a "(Local)" suffix to * local account, if it is known. If it is, it appends a "(Local)" suffix to
* account display string. * account display string.
* *
* @param accountIdentifier Account identifier to check. * @param accountIdentifier Account identifier to check.
* @param callLogViewDataNew Call log data which may have the lock account. * @param callLogViewDataNew Call log data which may have the lock account.
* *
* @return Account string to display. * @return Account string to display.
@ -474,7 +492,7 @@ public class CallLogArtifactViewer extends javax.swing.JPanel implements Artifac
private void resetComponent() { private void resetComponent() {
// cancel any outstanding persona searching threads. // cancel any outstanding persona searching threads.
if(currentAccountFetcher != null && !currentAccountFetcher.isDone()) { if (currentAccountFetcher != null && !currentAccountFetcher.isDone()) {
currentAccountFetcher.cancel(true); currentAccountFetcher.cancel(true);
currentAccountFetcher = null; currentAccountFetcher = null;
} }

View File

@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
/** /**
@ -47,11 +48,14 @@ final class CallLogViewData {
private String dataSourceName = null; private String dataSourceName = null;
private List<String> toContactNameList = null;
private List<String> fromContactNameList = null;
/** /**
* Constructor. * Constructor.
* *
* @param fromAccount From account identifier, may be null; * @param fromAccount From account identifier, may be null;
* @param toAccount To account identifier, may be null; * @param toAccount To account identifier, may be null;
*/ */
CallLogViewData(String fromAccount, String toAccount) { CallLogViewData(String fromAccount, String toAccount) {
this(fromAccount, toAccount, null); this(fromAccount, toAccount, null);
@ -61,8 +65,8 @@ final class CallLogViewData {
* Constructor. * Constructor.
* *
* @param fromAccount From account identifier, may be null; * @param fromAccount From account identifier, may be null;
* @param toAccount To account identifier, may be null; * @param toAccount To account identifier, may be null;
* @param direction Direction, may be null. * @param direction Direction, may be null.
*/ */
CallLogViewData(String fromAccount, String toAccount, String direction) { CallLogViewData(String fromAccount, String toAccount, String direction) {
this.fromAccount = fromAccount; this.fromAccount = fromAccount;
@ -146,4 +150,28 @@ final class CallLogViewData {
this.localAccountId = localAccountId; this.localAccountId = localAccountId;
} }
public void setToContactNameList(List<String> contactNameList) {
if (contactNameList != null) {
this.toContactNameList = new ArrayList<>(contactNameList);
} else {
this.toContactNameList = new ArrayList<>();
}
}
public List<String> getToContactNameList() {
return Collections.unmodifiableList(this.toContactNameList);
}
public void setFromContactNameList(List<String> contactNameList) {
if (contactNameList != null) {
this.fromContactNameList = new ArrayList<>(contactNameList);
} else {
this.fromContactNameList = new ArrayList<>();
}
}
public List<String> getFromContactNameList() {
return Collections.unmodifiableList(this.fromContactNameList);
}
} }

View File

@ -378,7 +378,7 @@ final class CommunicationArtifactViewerHelper {
"CommunicationArtifactViewerHelper_persona_button_view=View", "CommunicationArtifactViewerHelper_persona_button_view=View",
"CommunicationArtifactViewerHelper_persona_button_create=Create" "CommunicationArtifactViewerHelper_persona_button_create=Create"
}) })
static List<AccountPersonaSearcherData> addPersonaRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String accountIdentifier) { static List<AccountPersonaSearcherData> addPersonaRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String accountIdentifier) {
List<AccountPersonaSearcherData> dataList = new ArrayList<>(); List<AccountPersonaSearcherData> dataList = new ArrayList<>();
@ -428,6 +428,35 @@ final class CommunicationArtifactViewerHelper {
return dataList; return dataList;
} }
/**
* Adds a contact row to the panel.
*
* @param panel Panel to update.
* @param gridbagLayout Layout to use.
* @param constraints Constrains to use.
* @param contactId Contact name to display.
*
* @return A JLabel with the contact information.
*/
@NbBundle.Messages({
"#{0} - contact name",
"CommunicationArtifactViewerHelper_contact_label=Contact: {0}",
"CommunicationArtifactViewerHelper_contact_label_unknown=Unknown"
})
static JLabel addContactRow(JPanel panel, GridBagLayout gridbagLayout, GridBagConstraints constraints, String contactId) {
// Increase the y value because we are not calling the addKey
constraints.gridy++;
//Don't change the origian constraints, just make a copy to modify
GridBagConstraints indentedConstraints = (GridBagConstraints) constraints.clone();
// Add an indent to match persona labels
indentedConstraints.insets = new java.awt.Insets(0, 2 * LEFT_INSET, 0, 0);
String contactInfo = Bundle.CommunicationArtifactViewerHelper_contact_label(contactId != null && !contactId.isEmpty() ? contactId : Bundle.CommunicationArtifactViewerHelper_contact_label_unknown());
return addValueAtCol(panel, gridbagLayout, indentedConstraints, contactInfo, 1);
}
/** /**
* Event handler for mouse click event. Attaches a 'Copy' menu item to right * Event handler for mouse click event. Attaches a 'Copy' menu item to right

View File

@ -16,7 +16,6 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.sleuthkit.autopsy.contentviewers.artifactviewers; package org.sleuthkit.autopsy.contentviewers.artifactviewers;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
@ -39,6 +38,7 @@ import javax.swing.SwingUtilities;
import javax.swing.SwingWorker; import javax.swing.SwingWorker;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.centralrepository.datamodel.CentralRepository;
import org.sleuthkit.autopsy.centralrepository.datamodel.Persona; import org.sleuthkit.autopsy.centralrepository.datamodel.Persona;
import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount; import org.sleuthkit.autopsy.centralrepository.datamodel.PersonaAccount;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialog; import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialog;
@ -46,8 +46,10 @@ import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsDialogCallb
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode; import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsMode;
import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel; import org.sleuthkit.autopsy.centralrepository.persona.PersonaDetailsPanel;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.guiutils.ContactCache;
import org.sleuthkit.datamodel.Account; import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.CommunicationsManager; import org.sleuthkit.datamodel.CommunicationsManager;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
@ -113,13 +115,24 @@ final class MessageAccountPanel extends JPanel {
return new ArrayList<>(); return new ArrayList<>();
} }
Collection<PersonaAccount> personAccounts = PersonaAccount.getPersonaAccountsForAccount(account); List<BlackboardArtifact> contactList = ContactCache.getContacts(account);
if (personAccounts != null && !personAccounts.isEmpty()) { BlackboardArtifact contact = null;
for (PersonaAccount personaAccount : PersonaAccount.getPersonaAccountsForAccount(account)) {
dataList.add(new AccountContainer(account, personaAccount)); if (contactList != null && !contactList.isEmpty()) {
contact = contactList.get(0);
}
if (CentralRepository.isEnabled()) {
Collection<PersonaAccount> personAccounts = PersonaAccount.getPersonaAccountsForAccount(account);
if (personAccounts != null && !personAccounts.isEmpty()) {
for (PersonaAccount personaAccount : PersonaAccount.getPersonaAccountsForAccount(account)) {
dataList.add(new AccountContainer(account, personaAccount, contact));
}
} else {
dataList.add(new AccountContainer(account, null, contact));
} }
} else { } else {
dataList.add(new AccountContainer(account, null)); dataList.add(new AccountContainer(account, null, contact));
} }
} }
@ -127,8 +140,7 @@ final class MessageAccountPanel extends JPanel {
} }
@Messages({ @Messages({
"MessageAccountPanel_no_matches=No matches found.", "MessageAccountPanel_no_matches=No matches found.",})
})
@Override @Override
protected void done() { protected void done() {
try { try {
@ -199,6 +211,7 @@ final class MessageAccountPanel extends JPanel {
for (AccountContainer o : data) { for (AccountContainer o : data) {
group.addGap(5) group.addGap(5)
.addComponent(o.getAccountLabel()) .addComponent(o.getAccountLabel())
.addGroup(o.getContactLineVerticalGroup(layout))
.addGroup(o.getPersonLineVerticalGroup(layout)); .addGroup(o.getPersonLineVerticalGroup(layout));
} }
@ -234,6 +247,7 @@ final class MessageAccountPanel extends JPanel {
group.addGap(10); group.addGap(10);
for (AccountContainer o : data) { for (AccountContainer o : data) {
pgroup.addGroup(o.getPersonaSequentialGroup(layout)); pgroup.addGroup(o.getPersonaSequentialGroup(layout));
pgroup.addGroup(o.getContactSequentialGroup(layout));
} }
group.addGap(10) group.addGap(10)
.addGroup(pgroup) .addGroup(pgroup)
@ -253,10 +267,13 @@ final class MessageAccountPanel extends JPanel {
private final Account account; private final Account account;
private Persona persona = null; private Persona persona = null;
private final String contactName;
private JLabel accountLabel; private JLabel accountLabel;
private JLabel personaHeader; private JLabel personaHeader;
private JLabel personaDisplayName; private JLabel personaDisplayName;
private JLabel contactHeader;
private JLabel contactDisplayName;
private JButton button; private JButton button;
/** /**
@ -265,16 +282,22 @@ final class MessageAccountPanel extends JPanel {
* @param account * @param account
* @param personaAccount * @param personaAccount
*/ */
AccountContainer(Account account, PersonaAccount personaAccount) { AccountContainer(Account account, PersonaAccount personaAccount, BlackboardArtifact contactArtifact) throws TskCoreException {
if (contactArtifact != null && contactArtifact.getArtifactTypeID() != BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) {
throw new IllegalArgumentException("Failed to create AccountContainer object, passed in artifact was not a TSK_CONTACT");
}
this.account = account; this.account = account;
this.persona = personaAccount != null ? personaAccount.getPersona() : null; this.persona = personaAccount != null ? personaAccount.getPersona() : null;
this.contactName = getNameFromContactArtifact(contactArtifact);
} }
@Messages({ @Messages({
"MessageAccountPanel_persona_label=Persona:", "MessageAccountPanel_persona_label=Persona:",
"MessageAccountPanel_unknown_label=Unknown", "MessageAccountPanel_unknown_label=Unknown",
"MessageAccountPanel_button_view_label=View", "MessageAccountPanel_button_view_label=View",
"MessageAccountPanel_button_create_label=Create" "MessageAccountPanel_button_create_label=Create",
"MessageAccountPanel_contact_label=Contact:"
}) })
/** /**
* Swing components will not be initialized until this method is called. * Swing components will not be initialized until this method is called.
@ -282,16 +305,29 @@ final class MessageAccountPanel extends JPanel {
private void initalizeSwingControls() { private void initalizeSwingControls() {
accountLabel = new JLabel(); accountLabel = new JLabel();
personaHeader = new JLabel(Bundle.MessageAccountPanel_persona_label()); personaHeader = new JLabel(Bundle.MessageAccountPanel_persona_label());
contactHeader = new JLabel(Bundle.MessageAccountPanel_contact_label());
personaDisplayName = new JLabel(); personaDisplayName = new JLabel();
contactDisplayName = new JLabel();
button = new JButton(); button = new JButton();
button.addActionListener(new PersonaButtonListener(this)); button.addActionListener(new PersonaButtonListener(this));
accountLabel.setText(account.getTypeSpecificID()); accountLabel.setText(account.getTypeSpecificID());
contactDisplayName.setText(contactName);
personaDisplayName.setText(persona != null ? persona.getName() : Bundle.MessageAccountPanel_unknown_label()); personaDisplayName.setText(persona != null ? persona.getName() : Bundle.MessageAccountPanel_unknown_label());
button.setText(persona != null ? Bundle.MessageAccountPanel_button_view_label() : Bundle.MessageAccountPanel_button_create_label()); button.setText(persona != null ? Bundle.MessageAccountPanel_button_view_label() : Bundle.MessageAccountPanel_button_create_label());
} }
private String getNameFromContactArtifact(BlackboardArtifact contactArtifact) throws TskCoreException {
if (contactArtifact != null) {
BlackboardAttribute attribute = contactArtifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME));
if (attribute != null) {
return attribute.getValueString();
}
}
return Bundle.MessageAccountPanel_unknown_label();
}
/** /**
* Sets a new persona for this object and update the controls. * Sets a new persona for this object and update the controls.
* *
@ -365,6 +401,17 @@ final class MessageAccountPanel extends JPanel {
return group; return group;
} }
private SequentialGroup getContactSequentialGroup(GroupLayout layout) {
SequentialGroup group = layout.createSequentialGroup();
group
.addComponent(contactHeader)
.addPreferredGap(ComponentPlacement.RELATED)
.addComponent(contactDisplayName);
return group;
}
/** /**
* Generates the vertical layout code for the persona line. * Generates the vertical layout code for the persona line.
@ -379,6 +426,12 @@ final class MessageAccountPanel extends JPanel {
.addComponent(personaDisplayName) .addComponent(personaDisplayName)
.addComponent(button); .addComponent(button);
} }
private ParallelGroup getContactLineVerticalGroup(GroupLayout layout) {
return layout.createParallelGroup(Alignment.BASELINE)
.addComponent(contactHeader)
.addComponent(contactDisplayName);
}
} }
/** /**

View File

@ -447,10 +447,8 @@ public class MessageArtifactViewer extends javax.swing.JPanel implements Artifac
resetComponent(); resetComponent();
} }
msgbodyTabbedPane.setEnabledAt(ACCT_TAB_INDEX, CentralRepository.isEnabled()); msgbodyTabbedPane.setEnabledAt(ACCT_TAB_INDEX, true);
if(CentralRepository.isEnabled()) { accountsPanel.setArtifact(artifact);
accountsPanel.setArtifact(artifact);
}
} }
/** /**

View File

@ -0,0 +1,192 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed 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.sleuthkit.autopsy.guiutils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import java.beans.PropertyChangeListener;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.ingest.IngestManager;
import static org.sleuthkit.autopsy.ingest.IngestManager.IngestModuleEvent.DATA_ADDED;
import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.datamodel.Account;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME;
import org.sleuthkit.datamodel.TskCoreException;
/**
* A singleton cache of the Contact artifacts for accounts. The map of account
* unique ids to list of contact artifacts is stored in a LoadingCache which
* expires after 10 of non-use.
*
*/
public final class ContactCache {
private static final Logger logger = Logger.getLogger(ContactCache.class.getName());
private static ContactCache instance;
private final LoadingCache<String, Map<String, List<BlackboardArtifact>>> accountMap;
/**
* Returns the list of Contacts for the given Account.
*
* @param account Account instance.
*
* @return List of TSK_CONTACT artifacts that references the given Account.
* An empty list is returned if no contacts are found.
*
* @throws ExecutionException
*/
public static synchronized List<BlackboardArtifact> getContacts(Account account) throws ExecutionException {
return getInstance().accountMap.get("realMap").get(account.getTypeSpecificID());
}
/**
* Returns a list of Contact TSK_NAME values for the given account type
* specific id.
*
* @param accountTypeSpecificID Account type specific id
*
* @return List of contact string names or empty list if none were found.
*
* @throws TskCoreException
*/
public static synchronized List<String> getContactNameList(String accountTypeSpecificID) throws TskCoreException {
List<BlackboardArtifact> contactList;
try {
contactList = getInstance().accountMap.get("realMap").get(accountTypeSpecificID);
} catch (ExecutionException ex) {
throw new TskCoreException("Unable to get contact list from cache", ex);
}
List<String> contactNameList = new ArrayList<>();
if (contactList != null) {
for (BlackboardArtifact artifact : contactList) {
BlackboardAttribute attribute = artifact.getAttribute(new BlackboardAttribute.Type(BlackboardAttribute.ATTRIBUTE_TYPE.fromID(TSK_NAME.getTypeID())));
if (attribute != null && !contactNameList.contains(attribute.getValueString())) {
contactNameList.add(attribute.getValueString());
}
}
}
return contactNameList;
}
/**
* Force the cache to invalidate all entries.
*/
static synchronized void invalidateCache() {
getInstance().accountMap.invalidateAll();
}
/**
* Construct a new instance.
*/
private ContactCache() {
accountMap = CacheBuilder.newBuilder().expireAfterAccess(10, TimeUnit.MINUTES).build(
new CacheLoader<String, Map<String, List<BlackboardArtifact>>>() {
@Override
public Map<String, List<BlackboardArtifact>> load(String key) {
try {
return buildMap();
} catch (SQLException | TskCoreException ex) {
logger.log(Level.WARNING, "Failed to build account to contact map", ex);
}
return new HashMap<>(); // Return an empty map if there is an exception to avoid NPE and continual trying.
}
});
PropertyChangeListener ingestListener = pce -> {
String eventType = pce.getPropertyName();
if (eventType.equals(DATA_ADDED.toString())) {
ModuleDataEvent eventData = (ModuleDataEvent) pce.getOldValue();
if (eventData.getBlackboardArtifactType().getTypeID() == BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT.getTypeID()) {
invalidateCache();
}
}
};
IngestManager.getInstance().addIngestModuleEventListener(EnumSet.of(DATA_ADDED), ingestListener);
}
/**
* Returns the singleton instance of the cache object.
*
* @return AccountCache instance.
*/
private static synchronized ContactCache getInstance() {
if (instance == null) {
instance = new ContactCache();
}
return instance;
}
/**
* Builds the map of account IDs to contacts that reference them.
*
* @return A map of account IDs to contact artifacts.
*
* @throws TskCoreException
* @throws SQLException
*/
private Map<String, List<BlackboardArtifact>> buildMap() throws TskCoreException, SQLException {
Map<String, List<BlackboardArtifact>> acctMap = new HashMap<>();
List<BlackboardArtifact> contactList = Case.getCurrentCase().getSleuthkitCase().getBlackboardArtifacts(BlackboardArtifact.ARTIFACT_TYPE.TSK_CONTACT);
for (BlackboardArtifact contactArtifact : contactList) {
List<BlackboardAttribute> contactAttributes = contactArtifact.getAttributes();
for (BlackboardAttribute attribute : contactAttributes) {
String typeName = attribute.getAttributeType().getTypeName();
if (typeName.startsWith("TSK_EMAIL")
|| typeName.startsWith("TSK_PHONE")
|| typeName.startsWith("TSK_NAME")
|| typeName.startsWith("TSK_ID")) {
String accountID = attribute.getValueString();
List<BlackboardArtifact> artifactList = acctMap.get(accountID);
if (artifactList == null) {
artifactList = new ArrayList<>();
acctMap.put(accountID, artifactList);
}
if (!artifactList.contains(contactArtifact)) {
artifactList.add(contactArtifact);
}
}
}
}
return acctMap;
}
}

View File

@ -72,7 +72,10 @@ public final class HashDatabaseOptionsPanelController extends OptionsPanelContro
*/ */
@Override @Override
public void cancel() { public void cancel() {
getPanel().cancel(); if(changed) {
getPanel().cancel();
changed = false;
}
} }
@Override @Override

View File

@ -326,17 +326,19 @@ public class HashDbManager implements PropertyChangeListener {
} }
// Add the hash database to the collection // Add the hash database to the collection
hashSets.add(db); if(!hashSets.contains(db)) {
hashSets.add(db);
// Let any external listeners know that there's a new set
try { // Let any external listeners know that there's a new set
changeSupport.firePropertyChange(SetEvt.DB_ADDED.toString(), null, hashSetName); try {
} catch (Exception e) { changeSupport.firePropertyChange(SetEvt.DB_ADDED.toString(), null, hashSetName);
logger.log(Level.SEVERE, "HashDbManager listener threw exception", e); //NON-NLS } catch (Exception e) {
MessageNotifyUtil.Notify.show( logger.log(Level.SEVERE, "HashDbManager listener threw exception", e); //NON-NLS
NbBundle.getMessage(this.getClass(), "HashDbManager.moduleErr"), MessageNotifyUtil.Notify.show(
NbBundle.getMessage(this.getClass(), "HashDbManager.moduleErrorListeningToUpdatesMsg"), NbBundle.getMessage(this.getClass(), "HashDbManager.moduleErr"),
MessageNotifyUtil.MessageType.ERROR); NbBundle.getMessage(this.getClass(), "HashDbManager.moduleErrorListeningToUpdatesMsg"),
MessageNotifyUtil.MessageType.ERROR);
}
} }
return db; return db;

View File

@ -18,14 +18,17 @@
*/ */
package org.sleuthkit.autopsy.modules.hashdatabase; package org.sleuthkit.autopsy.modules.hashdatabase;
import java.awt.Component;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import javax.swing.JLabel;
import javax.swing.JScrollPane; import javax.swing.JScrollPane;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn; import javax.swing.table.TableColumn;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
@ -78,6 +81,7 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe
if (i == 0) { if (i == 0) {
column.setPreferredWidth(((int) (width1 * 0.07))); column.setPreferredWidth(((int) (width1 * 0.07)));
} else { } else {
column.setCellRenderer(new HashSetTableCellRenderer());
column.setPreferredWidth(((int) (width1 * 0.92))); column.setPreferredWidth(((int) (width1 * 0.92)));
} }
} }
@ -225,6 +229,24 @@ public final class HashLookupModuleSettingsPanel extends IngestModuleIngestJobSe
return valid; return valid;
} }
} }
/**
* Simple TableCellRenderer to add tool tips to the cells
*/
private static final class HashSetTableCellRenderer extends DefaultTableCellRenderer{
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
JLabel label = (JLabel)super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
label.setToolTipText(label.getText());
return label;
}
}
private static final class HashSetsTableModel extends AbstractTableModel { private static final class HashSetsTableModel extends AbstractTableModel {

View File

@ -18,13 +18,16 @@
*/ */
package org.sleuthkit.autopsy.modules.interestingitems; package org.sleuthkit.autopsy.modules.interestingitems;
import java.awt.Component;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Observable; import java.util.Observable;
import java.util.Observer; import java.util.Observer;
import java.util.TreeMap; import java.util.TreeMap;
import javax.swing.JLabel;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn; import javax.swing.table.TableColumn;
import org.openide.util.NbBundle.Messages; import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil;
@ -110,6 +113,7 @@ final class FilesIdentifierIngestJobSettingsPanel extends IngestModuleIngestJobS
if (i == 0) { if (i == 0) {
column.setPreferredWidth(((int) (width * 0.07))); column.setPreferredWidth(((int) (width * 0.07)));
} else { } else {
column.setCellRenderer(new FileSetsTableCellRenderer());
column.setPreferredWidth(((int) (width * 0.92))); column.setPreferredWidth(((int) (width * 0.92)));
} }
} }
@ -158,6 +162,24 @@ final class FilesIdentifierIngestJobSettingsPanel extends IngestModuleIngestJobS
// Cache the snapshot so it will be avaialble for the next update. // Cache the snapshot so it will be avaialble for the next update.
this.filesSetSnapshot = newFilesSetSnapshot; this.filesSetSnapshot = newFilesSetSnapshot;
} }
/**
* Simple TableCellRenderer to add tool tips to cells.
*/
private static final class FileSetsTableCellRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
label.setToolTipText(label.getText());
return label;
}
}
/** /**
* Table model for a JTable component that allows users to enable and * Table model for a JTable component that allows users to enable and

View File

@ -19,3 +19,10 @@ PhotoRecIngestModule.error.msg=Error processing {0} with PhotoRec carver.
PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving: PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving:
PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text=PhotoRec Settings PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text=PhotoRec Settings
PhotoRecCarverIngestJobSettingsPanel.keepCorruptedFilesCheckbox.text=Keep corrupted files PhotoRecCarverIngestJobSettingsPanel.keepCorruptedFilesCheckbox.text=Keep corrupted files
PhotoRecCarverIngestJobSettingsPanel.includeExcludeCheckbox.text=Focus on certain file types
PhotoRecCarverIngestJobSettingsPanel.fullListOfTypesLabel.text=Full List of Types:
PhotoRecCarverIngestJobSettingsPanel.exampleLabel.text=Example: jpg,png,zip
PhotoRecCarverIngestJobSettingsPanel.extensionListTextfield.text=
PhotoRecCarverIngestJobSettingsPanel.extensionListLabel.text=Types (comma separated list of extensions)
PhotoRecCarverIngestJobSettingsPanel.excludeRadioButton.text=Ignore the specified types
PhotoRecCarverIngestJobSettingsPanel.includeRadioButton.text=Carve only the specified types

View File

@ -8,6 +8,9 @@ OpenIDE-Module-Long-Description=PhotoRec Carver ingest module. \n\n Carves unall
OpenIDE-Module-Short-Description=Carves unallocated space and feeds carved files back into the system for processing. OpenIDE-Module-Short-Description=Carves unallocated space and feeds carved files back into the system for processing.
moduleDisplayName.text=PhotoRec Carver moduleDisplayName.text=PhotoRec Carver
moduleDescription.text=Runs PhotoRec carver against unallocated space in the data source. moduleDescription.text=Runs PhotoRec carver against unallocated space in the data source.
# {0} - extensions
PhotoRecCarverFileIngestModule_startUp_invalidExtensions_description=The following extensions are invalid: {0}
PhotoRecCarverFileIngestModule_startUp_noExtensionsProvided_description=No extensions provided for PhotoRec to carve.
PhotoRecIngestModule.nonHostnameUNCPathUsed=PhotoRec cannot operate with a UNC path containing IP addresses. PhotoRecIngestModule.nonHostnameUNCPathUsed=PhotoRec cannot operate with a UNC path containing IP addresses.
PhotoRecIngestModule.PermissionsNotSufficient=Insufficient permissions accessing PhotoRecIngestModule.PermissionsNotSufficient=Insufficient permissions accessing
PhotoRecIngestModule.PermissionsNotSufficientSeeReference=See 'Shared Drive Authentication' in Autopsy help. PhotoRecIngestModule.PermissionsNotSufficientSeeReference=See 'Shared Drive Authentication' in Autopsy help.
@ -26,5 +29,12 @@ PhotoRecIngestModule.error.msg=Error processing {0} with PhotoRec carver.
PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving: PhotoRecIngestModule.complete.numberOfErrors=Number of Errors while Carving:
PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text=PhotoRec Settings PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text=PhotoRec Settings
PhotoRecCarverIngestJobSettingsPanel.keepCorruptedFilesCheckbox.text=Keep corrupted files PhotoRecCarverIngestJobSettingsPanel.keepCorruptedFilesCheckbox.text=Keep corrupted files
PhotoRecCarverIngestJobSettingsPanel.includeExcludeCheckbox.text=Focus on certain file types
PhotoRecCarverIngestJobSettingsPanel.fullListOfTypesLabel.text=Full List of Types:
PhotoRecCarverIngestJobSettingsPanel.exampleLabel.text=Example: jpg,png,zip
PhotoRecCarverIngestJobSettingsPanel.extensionListTextfield.text=
PhotoRecCarverIngestJobSettingsPanel.extensionListLabel.text=Types (comma separated list of extensions)
PhotoRecCarverIngestJobSettingsPanel.excludeRadioButton.text=Ignore the specified types
PhotoRecCarverIngestJobSettingsPanel.includeRadioButton.text=Carve only the specified types
unallocatedSpaceProcessingSettingsError.message=The selected file ingest filter ignores unallocated space. This module carves unallocated space. Please choose a filter which does not ignore unallocated space or disable this module. unallocatedSpaceProcessingSettingsError.message=The selected file ingest filter ignores unallocated space. This module carves unallocated space. Please choose a filter which does not ignore unallocated space or disable this module.
unsupportedOS.message=PhotoRec module is supported on Windows platforms only. unsupportedOS.message=PhotoRec module is supported on Windows platforms only.

View File

@ -29,6 +29,7 @@ import java.nio.file.Paths;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -36,6 +37,7 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors;
import org.openide.modules.InstalledFileLocator; import org.openide.modules.InstalledFileLocator;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
@ -79,7 +81,11 @@ import org.sleuthkit.datamodel.TskData;
final class PhotoRecCarverFileIngestModule implements FileIngestModule { final class PhotoRecCarverFileIngestModule implements FileIngestModule {
static final boolean DEFAULT_CONFIG_KEEP_CORRUPTED_FILES = false; static final boolean DEFAULT_CONFIG_KEEP_CORRUPTED_FILES = false;
static final PhotoRecCarverIngestJobSettings.ExtensionFilterOption DEFAULT_CONFIG_EXTENSION_FILTER =
PhotoRecCarverIngestJobSettings.ExtensionFilterOption.NO_FILTER;
static final boolean DEFAULT_CONFIG_INCLUDE_ELSE_EXCLUDE = false;
private static final String PHOTOREC_DIRECTORY = "photorec_exec"; //NON-NLS private static final String PHOTOREC_DIRECTORY = "photorec_exec"; //NON-NLS
private static final String PHOTOREC_SUBDIRECTORY = "bin"; //NON-NLS private static final String PHOTOREC_SUBDIRECTORY = "bin"; //NON-NLS
private static final String PHOTOREC_EXECUTABLE = "photorec_win.exe"; //NON-NLS private static final String PHOTOREC_EXECUTABLE = "photorec_win.exe"; //NON-NLS
@ -99,23 +105,70 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
private File executableFile; private File executableFile;
private IngestServices services; private IngestServices services;
private final UNCPathUtilities uncPathUtilities = new UNCPathUtilities(); private final UNCPathUtilities uncPathUtilities = new UNCPathUtilities();
private final PhotoRecCarverIngestJobSettings settings;
private String optionsString;
private long jobId; private long jobId;
private final boolean keepCorruptedFiles;
private static class IngestJobTotals { private static class IngestJobTotals {
private final AtomicLong totalItemsRecovered = new AtomicLong(0); private final AtomicLong totalItemsRecovered = new AtomicLong(0);
private final AtomicLong totalItemsWithErrors = new AtomicLong(0); private final AtomicLong totalItemsWithErrors = new AtomicLong(0);
private final AtomicLong totalWritetime = new AtomicLong(0); private final AtomicLong totalWritetime = new AtomicLong(0);
private final AtomicLong totalParsetime = new AtomicLong(0); private final AtomicLong totalParsetime = new AtomicLong(0);
} }
/** /**
* Create a PhotoRec Carver ingest module instance. * Create a PhotoRec Carver ingest module instance.
* *
* @param settings Ingest job settings used to configure the module. * @param settings Ingest job settings used to configure the module.
*/ */
PhotoRecCarverFileIngestModule(PhotoRecCarverIngestJobSettings settings) { PhotoRecCarverFileIngestModule(PhotoRecCarverIngestJobSettings settings) {
keepCorruptedFiles = settings.isKeepCorruptedFiles(); this.settings = settings;
}
/**
* Creates a photorec command line options string based on the settings.
*
* @param settings The settings.
*
* @return The options string to be provided to Photorec on the command
* line.
*/
private String getPhotorecOptions(PhotoRecCarverIngestJobSettings settings) {
List<String> toRet = new ArrayList<String>();
if (settings.isKeepCorruptedFiles()) {
toRet.addAll(Arrays.asList("options", "keep_corrupted_file"));
}
if (settings.getExtensionFilterOption() !=
PhotoRecCarverIngestJobSettings.ExtensionFilterOption.NO_FILTER) {
// add the file opt menu item
toRet.add("fileopt");
String enable = "enable";
String disable = "disable";
// if we are including file extensions, then we are excluding
// everything else and vice-versa.
String everythingEnable = settings.getExtensionFilterOption() ==
PhotoRecCarverIngestJobSettings.ExtensionFilterOption.INCLUDE
? disable : enable;
toRet.addAll(Arrays.asList("everything", everythingEnable));
final String itemEnable = settings.getExtensionFilterOption() ==
PhotoRecCarverIngestJobSettings.ExtensionFilterOption.INCLUDE
? enable : disable;
settings.getExtensions().forEach((extension) -> {
toRet.addAll(Arrays.asList(extension, itemEnable));
});
}
toRet.add("search");
return String.join(",", toRet);
} }
private static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId) { private static synchronized IngestJobTotals getTotalsForIngestJobs(long ingestJobId) {
@ -136,7 +189,34 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
* @inheritDoc * @inheritDoc
*/ */
@Override @Override
@NbBundle.Messages({
"# {0} - extensions",
"PhotoRecCarverFileIngestModule_startUp_invalidExtensions_description=The following extensions are invalid: {0}",
"PhotoRecCarverFileIngestModule_startUp_noExtensionsProvided_description=No extensions provided for PhotoRec to carve."
})
public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException { public void startUp(IngestJobContext context) throws IngestModule.IngestModuleException {
// validate settings
if (this.settings.getExtensionFilterOption() != PhotoRecCarverIngestJobSettings.ExtensionFilterOption.NO_FILTER) {
if (this.settings.getExtensions().isEmpty() &&
this.settings.getExtensionFilterOption() == PhotoRecCarverIngestJobSettings.ExtensionFilterOption.INCLUDE) {
throw new IngestModule.IngestModuleException(
Bundle.PhotoRecCarverFileIngestModule_startUp_noExtensionsProvided_description());
}
List<String> invalidExtensions = this.settings.getExtensions().stream()
.filter((ext) -> !PhotoRecCarverFileOptExtensions.isValidExtension(ext))
.collect(Collectors.toList());
if (!invalidExtensions.isEmpty()) {
throw new IngestModule.IngestModuleException(
Bundle.PhotoRecCarverFileIngestModule_startUp_invalidExtensions_description(
String.join(",", invalidExtensions)));
}
}
this.optionsString = getPhotorecOptions(this.settings);
this.context = context; this.context = context;
this.services = IngestServices.getInstance(); this.services = IngestServices.getInstance();
this.jobId = this.context.getJobId(); this.jobId = this.context.getJobId();
@ -152,7 +232,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
this.rootOutputDirPath = createModuleOutputDirectoryForCase(); this.rootOutputDirPath = createModuleOutputDirectoryForCase();
//Set photorec executable directory based on operating system. //Set photorec executable directory based on operating system.
executableFile = locateExecutable(); executableFile = locateExecutable();
if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.jobId) == 1) { if (PhotoRecCarverFileIngestModule.refCounter.incrementAndGet(this.jobId) == 1) {
try { try {
@ -242,12 +322,9 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
outputDirPath.toAbsolutePath().toString() + File.separator + PHOTOREC_RESULTS_BASE, outputDirPath.toAbsolutePath().toString() + File.separator + PHOTOREC_RESULTS_BASE,
"/cmd", // NON-NLS "/cmd", // NON-NLS
tempFilePath.toFile().toString()); tempFilePath.toFile().toString());
if (keepCorruptedFiles) {
processAndSettings.command().add("options,keep_corrupted_file,search"); // NON-NLS processAndSettings.command().add(this.optionsString);
} else {
processAndSettings.command().add("search"); // NON-NLS
}
// Add environment variable to force PhotoRec to run with the same permissions Autopsy uses // Add environment variable to force PhotoRec to run with the same permissions Autopsy uses
processAndSettings.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS processAndSettings.environment().put("__COMPAT_LAYER", "RunAsInvoker"); //NON-NLS
processAndSettings.redirectErrorStream(true); processAndSettings.redirectErrorStream(true);
@ -455,7 +532,6 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
return path; return path;
} }
/** /**
* Finds and returns the path to the executable, if able. * Finds and returns the path to the executable, if able.
* *
@ -477,9 +553,9 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
File usrLocalBin = new File("/usr/local/bin/photorec"); File usrLocalBin = new File("/usr/local/bin/photorec");
if (usrBin.canExecute() && usrBin.exists() && !usrBin.isDirectory()) { if (usrBin.canExecute() && usrBin.exists() && !usrBin.isDirectory()) {
photorec_linux_directory = "/usr/bin"; photorec_linux_directory = "/usr/bin";
}else if(usrLocalBin.canExecute() && usrLocalBin.exists() && !usrLocalBin.isDirectory()){ } else if (usrLocalBin.canExecute() && usrLocalBin.exists() && !usrLocalBin.isDirectory()) {
photorec_linux_directory = "/usr/local/bin"; photorec_linux_directory = "/usr/local/bin";
}else{ } else {
throw new IngestModule.IngestModuleException("Photorec not found"); throw new IngestModule.IngestModuleException("Photorec not found");
} }
execName = Paths.get(photorec_linux_directory, PHOTOREC_LINUX_EXECUTABLE); execName = Paths.get(photorec_linux_directory, PHOTOREC_LINUX_EXECUTABLE);
@ -489,8 +565,7 @@ final class PhotoRecCarverFileIngestModule implements FileIngestModule {
if (null == exeFile) { if (null == exeFile) {
throw new IngestModule.IngestModuleException(Bundle.missingExecutable_message()); throw new IngestModule.IngestModuleException(Bundle.missingExecutable_message());
} }
if (!exeFile.canExecute()) { if (!exeFile.canExecute()) {
throw new IngestModule.IngestModuleException(Bundle.cannotRunExecutable_message()); throw new IngestModule.IngestModuleException(Bundle.cannotRunExecutable_message());
} }

View File

@ -0,0 +1,384 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed 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.sleuthkit.autopsy.modules.photoreccarver;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* Specifies what extensions can be used for the "fileopt" option of photorec,
* which allows certain extensions to be included or excluded.
*/
final class PhotoRecCarverFileOptExtensions {
/**
* Returns true if the extension provided in the string can be used with
* photorec.
*
* @param extension The extension (i.e. 'exe').
*
* @return True if the extension can be used with photorec.
*/
static boolean isValidExtension(String extension) {
return KNOWN_EXTENSIONS.contains(extension);
}
// This was generated by photorec. It can be generated by photorec by
// launching photorec_win with no arguments, go to "Proceed", go to
// "File Opt" and press 'b'. This should generate a photorec.cfg file in
// the working directory with all the extensions.
private static final Set<String> KNOWN_EXTENSIONS = new HashSet<String>(Arrays.asList(
"1cd",
"3dm",
"7z",
"DB",
"a",
"abr",
"acb",
"accdb",
"ace",
"ab",
"ado",
"afdesign",
"ahn",
"aif",
"all",
"als",
"amd",
"amr",
"apa",
"ape",
"apple",
"ari",
"arj",
"asf",
"asl",
"asm",
"atd",
"au",
"axp",
"axx",
"bac",
"bdm",
"db",
"bim",
"bin",
"binvox",
"bkf",
"blend",
"bmp",
"bpg",
"bvr",
"bz2",
"c4d",
"cab",
"caf",
"cam",
"catdrawing",
"cdt",
"che",
"chm",
"class",
"comicdoc",
"cp_",
"cow",
"cpi",
"crw",
"csh",
"ctg",
"cwk",
"d2s",
"dad",
"dar",
"dat",
"dbf",
"dbn",
"dcm",
"ddf",
"dex",
"diskimage",
"fat",
"djv",
"dmp",
"drw",
"doc",
"dpx",
"ds2",
"DS_Store",
"dsc",
"dss",
"dst",
"dta",
"dump",
"dv",
"dvi",
"dvr",
"dwg",
"dxf",
"e01",
"eCryptfs",
"edb",
"elf",
"emf",
"ess",
"evt",
"evtx",
"exe",
"exs",
"ext",
"ext",
"fat",
"fbf",
"fbk",
"fcp",
"fcs",
"fdb",
"fds",
"fh10",
"fh5",
"sparseimage",
"fits",
"fit",
"flac",
"flp",
"flv",
"fm",
"fob",
"fos",
"fp5",
"fp7",
"freeway",
"frm",
"fs",
"fwd",
"gam",
"gct",
"gho",
"gi",
"gif",
"gm*",
"gp2",
"gp5",
"gpg",
"gpx",
"gsm",
"gz",
"hdf",
"hdr",
"hds",
"hfsp",
"hm",
"hr9",
"http",
"ibd",
"icc",
"icns",
"ico",
"idx",
"ifo",
"imb",
"indd",
"info",
"iso",
"it",
"itu",
"jks",
"jpg",
"jsonlz4",
"kdb",
"kdbx",
"key",
"ldf",
"lit",
"logic",
"lnk",
"lso",
"luks",
"lxo",
"lzh",
"lzo",
"m2ts",
"mat",
"max",
"mb",
"mcd",
"mdb",
"mdf",
"mfa",
"mfg",
"mft",
"mid",
"mig",
"mk5",
"mkv",
"mlv",
"mobi",
"mov/mdat",
"mov",
"mp3",
"mpg",
"mpl",
"mrw",
"msa",
"mus",
"myo",
"MYI",
"mxf",
"nd2",
"nds",
"nes",
"njx",
"nk2",
"nsf",
"oci",
"ogg",
"one",
"orf",
"paf",
"pap",
"par2",
"pcap",
"pcb",
"pct",
"pcx",
"pdb",
"pdf",
"pds",
"pf",
"pfx",
"dump",
"plist",
"plr",
"plt",
"png",
"pnm",
"prc",
"prd",
"prt",
"ps",
"psb",
"psd",
"psf",
"psp",
"pst",
"ptb",
"ptf",
"pyc",
"pzf",
"pzh",
"qbb",
"qdf",
"qkt",
"qxd",
"r3d",
"ra",
"raf",
"rar",
"raw",
"rdc",
"reg",
"res",
"rfp",
"riff",
"rlv",
"rm",
"rns",
"rpm",
"rw2",
"rx2",
"save",
"ses",
"sgcta",
"shn",
"sib",
"sit",
"skd",
"skp",
"snag",
"snz",
"sp3",
"spe",
"spf",
"sav",
"sqlite",
"sqm",
"steuer2014",
"stl",
"studio",
"sit",
"swf",
"tar",
"tax",
"tg",
"tib",
"tif",
"TiVo",
"torrent",
"tph",
"tpl",
"ts",
"ttf",
"tx?",
"txt",
"tz",
"v2i",
"vault",
"vdj",
"vfb",
"vdi",
"veg",
"vib",
"vmdk",
"vmg",
"wallet",
"wdp",
"wee",
"wim",
"win",
"wks",
"wld",
"wmf",
"wnk",
"woff",
"wpb",
"wpd",
"wtv",
"wv",
"x3f",
"x3i",
"x4a",
"xar",
"xcf",
"xfi",
"xfs",
"xm",
"xml",
"xsv",
"xpt",
"xv",
"xz",
"z2d",
"zcode",
"zip",
"zpr"));
private PhotoRecCarverFileOptExtensions() {
}
}

View File

@ -18,53 +18,132 @@
*/ */
package org.sleuthkit.autopsy.modules.photoreccarver; package org.sleuthkit.autopsy.modules.photoreccarver;
import java.util.ArrayList;
import java.util.Collections;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import java.util.List;
/** /**
* Ingest job settings for the PhotoRec Carver module. * Ingest job settings for the PhotoRec Carver module.
*/ */
final class PhotoRecCarverIngestJobSettings implements IngestModuleIngestJobSettings { final class PhotoRecCarverIngestJobSettings implements IngestModuleIngestJobSettings {
/**
* What kind of filtering should occur for the extension list.
*/
enum ExtensionFilterOption {
/**
* The file extensions should be included (and others should be
* filtered).
*/
INCLUDE,
/**
* The extensions should be excluded from the results list.
*/
EXCLUDE,
/**
* No extension filtering should take place.
*/
NO_FILTER
};
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private boolean keepCorruptedFiles; private boolean keepCorruptedFiles;
private List<String> extensions;
private ExtensionFilterOption extensionFilterOption;
/** /**
* Instantiate the ingest job settings with default values. * Instantiate the ingest job settings with default values.
*/ */
PhotoRecCarverIngestJobSettings() { PhotoRecCarverIngestJobSettings() {
this.keepCorruptedFiles = PhotoRecCarverFileIngestModule.DEFAULT_CONFIG_KEEP_CORRUPTED_FILES; this(PhotoRecCarverFileIngestModule.DEFAULT_CONFIG_KEEP_CORRUPTED_FILES,
PhotoRecCarverFileIngestModule.DEFAULT_CONFIG_EXTENSION_FILTER,
null);
} }
/** /**
* Instantiate the ingest job settings. * Sets the photo rec settings.
* *
* @param keepCorruptedFiles Keep corrupted files. * @param keepCorruptedFiles Whether or not to keep corrupted files.
* @param fileOptOption Whether or not the file opt options
* @param extensionFilterOption How the includeExcludeExtensions should
* be filtered.
* @param includeExcludeExtensions The extensions to include or exclude
* (i.e. jpg, gif)
*/ */
PhotoRecCarverIngestJobSettings(boolean keepCorruptedFiles) { PhotoRecCarverIngestJobSettings(boolean keepCorruptedFiles, ExtensionFilterOption extensionFilterOption, List<String> includeExcludeExtensions) {
this.keepCorruptedFiles = keepCorruptedFiles; this.keepCorruptedFiles = keepCorruptedFiles;
setExtensionFilterOption(extensionFilterOption);
setExtensions(includeExcludeExtensions);
} }
@Override @Override
public long getVersionNumber() { public long getVersionNumber() {
return serialVersionUID; return serialVersionUID;
} }
/** /**
* Are corrupted files being kept? * Are corrupted files being kept?
* *
* @return True if keeping corrupted files; otherwise false. * @return True if keeping corrupted files; otherwise false.
*/ */
boolean isKeepCorruptedFiles() { boolean isKeepCorruptedFiles() {
return keepCorruptedFiles; return keepCorruptedFiles;
} }
/** /**
* Keep or disgard corrupted files. * Keep or disgard corrupted files.
* *
* @param keepCorruptedFiles Are corrupted files being kept? * @param keepCorruptedFiles Are corrupted files being kept?
*/ */
void setKeepCorruptedFiles(boolean keepCorruptedFiles) { void setKeepCorruptedFiles(boolean keepCorruptedFiles) {
this.keepCorruptedFiles = keepCorruptedFiles; this.keepCorruptedFiles = keepCorruptedFiles;
} }
/**
* Gets extension names (i.e. jpg, exe) to include or exclude from photorec
* carving.
*
* @return The extension names.
*/
List<String> getExtensions() {
return extensions == null
? Collections.emptyList()
: Collections.unmodifiableList(extensions);
}
/**
* Sets extension names (i.e. jpg, exe) to include or exclude from photorec
* carving.
*
* @param includeExcludeExtensions The extension names.
*/
void setExtensions(List<String> includeExcludeExtensions) {
this.extensions = new ArrayList<>();
if (includeExcludeExtensions != null) {
this.extensions.addAll(includeExcludeExtensions);
}
}
/**
* How extension filtering should be handled.
* @return How extension filtering should be handled.
*/
ExtensionFilterOption getExtensionFilterOption() {
return (this.extensionFilterOption == null) ?
ExtensionFilterOption.NO_FILTER :
extensionFilterOption;
}
/**
* Sets how extension filtering should be handled.
* @param extensionFilterOption How extension filtering should be handled.
*/
void setExtensionFilterOption(ExtensionFilterOption extensionFilterOption) {
this.extensionFilterOption = (extensionFilterOption == null) ?
ExtensionFilterOption.NO_FILTER :
extensionFilterOption;
}
} }

View File

@ -1,6 +1,10 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo"> <Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
<NonVisualComponents>
<Component class="javax.swing.ButtonGroup" name="includeExcludeButtonGroup">
</Component>
</NonVisualComponents>
<AuxValues> <AuxValues>
<AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/> <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
<AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/> <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@ -16,16 +20,30 @@
<Layout> <Layout>
<DimensionLayout dim="0"> <DimensionLayout dim="0">
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" alignment="0" attributes="0"> <Group type="102" attributes="0">
<EmptySpace max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0"> <Group type="103" groupAlignment="0" attributes="0">
<Group type="102" attributes="0"> <Group type="102" alignment="0" attributes="0">
<EmptySpace min="10" pref="10" max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="keepCorruptedFilesCheckbox" min="-2" max="-2" attributes="0"/> <Group type="103" groupAlignment="0" attributes="0">
<Component id="detectionSettingsLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="keepCorruptedFilesCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="includeExcludeCheckbox" alignment="0" min="-2" max="-2" attributes="0"/>
</Group>
</Group>
<Group type="102" alignment="0" attributes="0">
<EmptySpace min="-2" pref="31" max="-2" attributes="0"/>
<Group type="103" groupAlignment="0" attributes="0">
<Component id="includeRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="excludeRadioButton" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="exampleLabel" min="-2" max="-2" attributes="0"/>
<Component id="extensionListTextfield" min="-2" pref="258" max="-2" attributes="0"/>
<Component id="fullListOfTypesLabel" min="-2" max="-2" attributes="0"/>
<Component id="extensionListLabel" alignment="0" min="-2" max="-2" attributes="0"/>
<Component id="fullListOfTypesHyperlink" alignment="0" min="-2" pref="247" max="-2" attributes="0"/>
</Group>
</Group> </Group>
<Component id="detectionSettingsLabel" min="-2" max="-2" attributes="0"/>
</Group> </Group>
<EmptySpace pref="159" max="32767" attributes="0"/> <EmptySpace max="32767" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -34,9 +52,25 @@
<Group type="102" alignment="0" attributes="0"> <Group type="102" alignment="0" attributes="0">
<EmptySpace max="-2" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="detectionSettingsLabel" min="-2" max="-2" attributes="0"/> <Component id="detectionSettingsLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace type="unrelated" max="-2" attributes="0"/> <EmptySpace min="0" pref="2" max="2" attributes="0"/>
<Component id="keepCorruptedFilesCheckbox" min="-2" max="-2" attributes="0"/> <Component id="keepCorruptedFilesCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace pref="145" max="32767" attributes="0"/> <EmptySpace max="-2" attributes="0"/>
<Component id="includeExcludeCheckbox" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="includeRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="excludeRadioButton" min="-2" max="-2" attributes="0"/>
<EmptySpace min="-2" pref="4" max="-2" attributes="0"/>
<Component id="extensionListLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="extensionListTextfield" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="exampleLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="fullListOfTypesLabel" min="-2" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
<Component id="fullListOfTypesHyperlink" min="-2" pref="36" max="-2" attributes="0"/>
<EmptySpace max="-2" attributes="0"/>
</Group> </Group>
</Group> </Group>
</DimensionLayout> </DimensionLayout>
@ -60,6 +94,82 @@
<ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/> <ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property> </Property>
</Properties> </Properties>
<AuxValues>
<AuxValue name="JavaCodeGenerator_VariableLocal" type="java.lang.Boolean" value="true"/>
<AuxValue name="JavaCodeGenerator_VariableModifier" type="java.lang.Integer" value="0"/>
</AuxValues>
</Component>
<Component class="javax.swing.JCheckBox" name="includeExcludeCheckbox">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.includeExcludeCheckbox.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
<Events>
<EventHandler event="actionPerformed" listener="java.awt.event.ActionListener" parameters="java.awt.event.ActionEvent" handler="includeExcludeCheckboxActionPerformed"/>
</Events>
</Component>
<Component class="javax.swing.JRadioButton" name="excludeRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="includeExcludeButtonGroup"/>
</Property>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.excludeRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="exampleLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.exampleLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="fullListOfTypesLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.fullListOfTypesLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JLabel" name="extensionListLabel">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.extensionListLabel.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JTextField" name="extensionListTextfield">
<Properties>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.extensionListTextfield.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JRadioButton" name="includeRadioButton">
<Properties>
<Property name="buttonGroup" type="javax.swing.ButtonGroup" editor="org.netbeans.modules.form.RADComponent$ButtonGroupPropertyEditor">
<ComponentRef name="includeExcludeButtonGroup"/>
</Property>
<Property name="selected" type="boolean" value="true"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor">
<ResourceString bundle="org/sleuthkit/autopsy/modules/photoreccarver/Bundle.properties" key="PhotoRecCarverIngestJobSettingsPanel.includeRadioButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
</Property>
</Properties>
</Component>
<Component class="javax.swing.JTextArea" name="fullListOfTypesHyperlink">
<Properties>
<Property name="editable" type="boolean" value="false"/>
<Property name="columns" type="int" value="20"/>
<Property name="lineWrap" type="boolean" value="true"/>
<Property name="rows" type="int" value="5"/>
<Property name="text" type="java.lang.String" editor="org.netbeans.modules.form.RADConnectionPropertyEditor">
<Connection code="PHOTOREC_TYPES_URL" type="code"/>
</Property>
<Property name="focusable" type="boolean" value="false"/>
<Property name="opaque" type="boolean" value="false"/>
</Properties>
</Component> </Component>
</SubComponents> </SubComponents>
</Form> </Form>

View File

@ -1,7 +1,7 @@
/* /*
* Autopsy Forensic Browser * Autopsy Forensic Browser
* *
* Copyright 2018 Basis Technology Corp. * Copyright 2018-2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org * Contact: carrier <at> sleuthkit <dot> org
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
@ -18,8 +18,23 @@
*/ */
package org.sleuthkit.autopsy.modules.photoreccarver; package org.sleuthkit.autopsy.modules.photoreccarver;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.commons.lang.StringUtils;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
import org.sleuthkit.autopsy.coreutils.Logger;
/** /**
* Ingest job settings panel for the Encryption Detection module. * Ingest job settings panel for the Encryption Detection module.
@ -27,6 +42,10 @@ import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettingsPanel;
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives @SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
final class PhotoRecCarverIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel { final class PhotoRecCarverIngestJobSettingsPanel extends IngestModuleIngestJobSettingsPanel {
private static final Logger logger = Logger.getLogger(PhotoRecCarverIngestJobSettingsPanel.class.getName());
private static final String EXTENSION_LIST_SEPARATOR = ",";
private static final String PHOTOREC_TYPES_URL = "http://sleuthkit.org/autopsy/docs/user-docs/latest/photorec_carver_page.html";
/** /**
* Instantiate the ingest job settings panel. * Instantiate the ingest job settings panel.
* *
@ -43,13 +62,100 @@ final class PhotoRecCarverIngestJobSettingsPanel extends IngestModuleIngestJobSe
* @param settings The ingest job settings. * @param settings The ingest job settings.
*/ */
private void customizeComponents(PhotoRecCarverIngestJobSettings settings) { private void customizeComponents(PhotoRecCarverIngestJobSettings settings) {
includeExcludeCheckbox.setSelected(settings.getExtensionFilterOption() != PhotoRecCarverIngestJobSettings.ExtensionFilterOption.NO_FILTER);
extensionListTextfield.setText(String.join(EXTENSION_LIST_SEPARATOR, settings.getExtensions()));
includeRadioButton.setSelected(settings.getExtensionFilterOption() == PhotoRecCarverIngestJobSettings.ExtensionFilterOption.INCLUDE);
excludeRadioButton.setSelected(settings.getExtensionFilterOption() == PhotoRecCarverIngestJobSettings.ExtensionFilterOption.EXCLUDE);
keepCorruptedFilesCheckbox.setSelected(settings.isKeepCorruptedFiles()); keepCorruptedFilesCheckbox.setSelected(settings.isKeepCorruptedFiles());
setupTypesHyperlink();
setIncludePanelEnabled();
}
/**
* Sets up a clickable hyperlink for the different supported types for
* extensions.
*/
private void setupTypesHyperlink() {
// taken from https://www.codejava.net/java-se/swing/how-to-create-hyperlink-with-jlabel-in-java-swing
this.fullListOfTypesHyperlink.setForeground(Color.BLUE.darker());
this.fullListOfTypesHyperlink.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
this.fullListOfTypesHyperlink.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
try {
Desktop.getDesktop().browse(new URI(PHOTOREC_TYPES_URL));
} catch (IOException | URISyntaxException ex) {
logger.log(Level.WARNING, "There was an error going to types hyperlink: " + PHOTOREC_TYPES_URL, ex);
}
}
});
}
/**
* Whether or not the file type inclusion/exclusion panel should be enabled
* based on whether or not the includeExcludeCheckbox is checked.
*/
private void setIncludePanelEnabled() {
setIncludePanelEnabled(includeExcludeCheckbox.isSelected());
}
/**
* Sets components in the inclusion/exclusion panel to the specified enabled
* state.
*
* @param enabled Whether or not to enable components.
*/
private void setIncludePanelEnabled(boolean enabled) {
includeRadioButton.setEnabled(enabled);
excludeRadioButton.setEnabled(enabled);
extensionListLabel.setEnabled(enabled);
extensionListTextfield.setEnabled(enabled);
exampleLabel.setEnabled(enabled);
fullListOfTypesLabel.setEnabled(enabled);
} }
@Override @Override
public IngestModuleIngestJobSettings getSettings() { public IngestModuleIngestJobSettings getSettings() {
PhotoRecCarverIngestJobSettings.ExtensionFilterOption filterOption =
PhotoRecCarverIngestJobSettings.ExtensionFilterOption.NO_FILTER;
if (includeExcludeCheckbox.isSelected()) {
if (includeRadioButton.isSelected()) {
filterOption = PhotoRecCarverIngestJobSettings.ExtensionFilterOption.INCLUDE;
} else {
filterOption = PhotoRecCarverIngestJobSettings.ExtensionFilterOption.EXCLUDE;
}
}
return new PhotoRecCarverIngestJobSettings( return new PhotoRecCarverIngestJobSettings(
keepCorruptedFilesCheckbox.isSelected()); keepCorruptedFilesCheckbox.isSelected(),
filterOption,
getExtensions(extensionListTextfield.getText())
);
}
/**
* Determines a list of extensions to pass as parameters to photorec based
* on the specified input.
*
* @param combinedList The comma-separated list.
*
* @return The list of strings to use with photorec.
*/
private List<String> getExtensions(String combinedList) {
if (StringUtils.isBlank(combinedList)) {
return Collections.emptyList();
}
return Stream.of(combinedList.split(EXTENSION_LIST_SEPARATOR))
.map(ext -> ext.trim())
.filter(ext -> StringUtils.isNotBlank(ext))
.sorted((a, b) -> a.toLowerCase().compareTo(b.toLowerCase()))
.collect(Collectors.toList());
} }
/** /**
@ -61,40 +167,120 @@ final class PhotoRecCarverIngestJobSettingsPanel extends IngestModuleIngestJobSe
// <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
private void initComponents() { private void initComponents() {
includeExcludeButtonGroup = new javax.swing.ButtonGroup();
keepCorruptedFilesCheckbox = new javax.swing.JCheckBox(); keepCorruptedFilesCheckbox = new javax.swing.JCheckBox();
detectionSettingsLabel = new javax.swing.JLabel(); javax.swing.JLabel detectionSettingsLabel = new javax.swing.JLabel();
includeExcludeCheckbox = new javax.swing.JCheckBox();
excludeRadioButton = new javax.swing.JRadioButton();
exampleLabel = new javax.swing.JLabel();
fullListOfTypesLabel = new javax.swing.JLabel();
extensionListLabel = new javax.swing.JLabel();
extensionListTextfield = new javax.swing.JTextField();
includeRadioButton = new javax.swing.JRadioButton();
fullListOfTypesHyperlink = new javax.swing.JTextArea();
setPreferredSize(null);
org.openide.awt.Mnemonics.setLocalizedText(keepCorruptedFilesCheckbox, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.keepCorruptedFilesCheckbox.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(keepCorruptedFilesCheckbox, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.keepCorruptedFilesCheckbox.text")); // NOI18N
detectionSettingsLabel.setFont(detectionSettingsLabel.getFont().deriveFont(detectionSettingsLabel.getFont().getStyle() | java.awt.Font.BOLD)); detectionSettingsLabel.setFont(detectionSettingsLabel.getFont().deriveFont(detectionSettingsLabel.getFont().getStyle() | java.awt.Font.BOLD));
org.openide.awt.Mnemonics.setLocalizedText(detectionSettingsLabel, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text")); // NOI18N org.openide.awt.Mnemonics.setLocalizedText(detectionSettingsLabel, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.detectionSettingsLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(includeExcludeCheckbox, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.includeExcludeCheckbox.text")); // NOI18N
includeExcludeCheckbox.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt) {
includeExcludeCheckboxActionPerformed(evt);
}
});
includeExcludeButtonGroup.add(excludeRadioButton);
org.openide.awt.Mnemonics.setLocalizedText(excludeRadioButton, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.excludeRadioButton.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(exampleLabel, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.exampleLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(fullListOfTypesLabel, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.fullListOfTypesLabel.text")); // NOI18N
org.openide.awt.Mnemonics.setLocalizedText(extensionListLabel, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.extensionListLabel.text")); // NOI18N
extensionListTextfield.setText(org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.extensionListTextfield.text")); // NOI18N
includeExcludeButtonGroup.add(includeRadioButton);
includeRadioButton.setSelected(true);
org.openide.awt.Mnemonics.setLocalizedText(includeRadioButton, org.openide.util.NbBundle.getMessage(PhotoRecCarverIngestJobSettingsPanel.class, "PhotoRecCarverIngestJobSettingsPanel.includeRadioButton.text")); // NOI18N
fullListOfTypesHyperlink.setEditable(false);
fullListOfTypesHyperlink.setColumns(20);
fullListOfTypesHyperlink.setLineWrap(true);
fullListOfTypesHyperlink.setRows(5);
fullListOfTypesHyperlink.setText(PHOTOREC_TYPES_URL);
fullListOfTypesHyperlink.setFocusable(false);
fullListOfTypesHyperlink.setOpaque(false);
javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
this.setLayout(layout); this.setLayout(layout);
layout.setHorizontalGroup( layout.setHorizontalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addContainerGap()
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addGap(10, 10, 10) .addContainerGap()
.addComponent(keepCorruptedFilesCheckbox)) .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(detectionSettingsLabel)) .addComponent(detectionSettingsLabel)
.addContainerGap(159, Short.MAX_VALUE)) .addComponent(keepCorruptedFilesCheckbox)
.addComponent(includeExcludeCheckbox)))
.addGroup(layout.createSequentialGroup()
.addGap(31, 31, 31)
.addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addComponent(includeRadioButton)
.addComponent(excludeRadioButton)
.addComponent(exampleLabel)
.addComponent(extensionListTextfield, javax.swing.GroupLayout.PREFERRED_SIZE, 258, javax.swing.GroupLayout.PREFERRED_SIZE)
.addComponent(fullListOfTypesLabel)
.addComponent(extensionListLabel)
.addComponent(fullListOfTypesHyperlink, javax.swing.GroupLayout.PREFERRED_SIZE, 247, javax.swing.GroupLayout.PREFERRED_SIZE))))
.addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
); );
layout.setVerticalGroup( layout.setVerticalGroup(
layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
.addGroup(layout.createSequentialGroup() .addGroup(layout.createSequentialGroup()
.addContainerGap() .addContainerGap()
.addComponent(detectionSettingsLabel) .addComponent(detectionSettingsLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) .addGap(0, 2, 2)
.addComponent(keepCorruptedFilesCheckbox) .addComponent(keepCorruptedFilesCheckbox)
.addContainerGap(145, Short.MAX_VALUE)) .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(includeExcludeCheckbox)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(includeRadioButton)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(excludeRadioButton)
.addGap(4, 4, 4)
.addComponent(extensionListLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(extensionListTextfield, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(exampleLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fullListOfTypesLabel)
.addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
.addComponent(fullListOfTypesHyperlink, javax.swing.GroupLayout.PREFERRED_SIZE, 36, javax.swing.GroupLayout.PREFERRED_SIZE)
.addContainerGap())
); );
}// </editor-fold>//GEN-END:initComponents }// </editor-fold>//GEN-END:initComponents
private void includeExcludeCheckboxActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_includeExcludeCheckboxActionPerformed
setIncludePanelEnabled();
}//GEN-LAST:event_includeExcludeCheckboxActionPerformed
// Variables declaration - do not modify//GEN-BEGIN:variables // Variables declaration - do not modify//GEN-BEGIN:variables
private javax.swing.JLabel detectionSettingsLabel; private javax.swing.JLabel exampleLabel;
private javax.swing.JRadioButton excludeRadioButton;
private javax.swing.JLabel extensionListLabel;
private javax.swing.JTextField extensionListTextfield;
private javax.swing.JTextArea fullListOfTypesHyperlink;
private javax.swing.JLabel fullListOfTypesLabel;
private javax.swing.ButtonGroup includeExcludeButtonGroup;
private javax.swing.JCheckBox includeExcludeCheckbox;
private javax.swing.JRadioButton includeRadioButton;
private javax.swing.JCheckBox keepCorruptedFilesCheckbox; private javax.swing.JCheckBox keepCorruptedFilesCheckbox;
// End of variables declaration//GEN-END:variables // End of variables declaration//GEN-END:variables
} }

View File

@ -0,0 +1 @@
DataSourceSummaryResultViewer_title=Summary

View File

@ -0,0 +1,137 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2020 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed 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.sleuthkit.autopsy.resultviewers.summary;
import java.awt.BorderLayout;
import java.awt.Cursor;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import org.openide.explorer.ExplorerManager;
import org.openide.nodes.Node;
import org.openide.util.NbBundle.Messages;
import org.openide.util.lookup.ServiceProvider;
import org.sleuthkit.autopsy.casemodule.datasourcesummary.DataSourceSummaryTabbedPane;
import org.sleuthkit.autopsy.corecomponentinterfaces.DataResultViewer;
import org.sleuthkit.autopsy.corecomponents.AbstractDataResultViewer;
import org.sleuthkit.datamodel.DataSource;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ThreadConfined;
/**
* A tabular result viewer that displays a summary of the selected Data Source.
*/
@ServiceProvider(service = DataResultViewer.class)
@SuppressWarnings("PMD.SingularField") // UI widgets cause lots of false positives
public class DataSourceSummaryResultViewer extends AbstractDataResultViewer {
private static final long serialVersionUID = 1L;
private static final Logger LOGGER = Logger.getLogger(DataSourceSummaryResultViewer.class.getName());
private final String title;
/**
* Constructs a tabular result viewer that displays a summary of the
* selected Data Source.
*/
public DataSourceSummaryResultViewer() {
this(null);
}
/**
* Constructs a tabular result viewer that displays a summary of the
* selected Data Source.
*
* @param explorerManager The explorer manager of the ancestor top
* component.
*
*/
@Messages({
"DataSourceSummaryResultViewer_title=Summary"
})
public DataSourceSummaryResultViewer(ExplorerManager explorerManager) {
this(explorerManager, Bundle.DataSourceSummaryResultViewer_title());
}
/**
* Constructs a tabular result viewer that displays a summary of the
* selected Data Source.
*
* @param explorerManager The explorer manager of the ancestor top
* component.
* @param title The title.
*
*/
public DataSourceSummaryResultViewer(ExplorerManager explorerManager, String title) {
super(explorerManager);
this.title = title;
initComponents();
}
@Override
public DataResultViewer createInstance() {
return new DataSourceSummaryResultViewer();
}
@Override
public boolean isSupported(Node node) {
return getDataSource(node) != null;
}
/**
* Returns the datasource attached to the node or null if none can be found.
*
* @param node The node to search.
*
* @return The datasource or null if not found.
*/
private DataSource getDataSource(Node node) {
return node == null ? null : node.getLookup().lookup(DataSource.class);
}
@Override
@ThreadConfined(type = ThreadConfined.ThreadType.AWT)
public void setNode(Node node) {
if (!SwingUtilities.isEventDispatchThread()) {
LOGGER.log(Level.SEVERE, "Attempting to run setNode() from non-EDT thread.");
return;
}
DataSource dataSource = getDataSource(node);
this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
try {
summaryPanel.setDataSource(dataSource);
} finally {
this.setCursor(null);
}
}
@Override
public String getTitle() {
return title;
}
private void initComponents() {
summaryPanel = new DataSourceSummaryTabbedPane();
setLayout(new BorderLayout());
add(summaryPanel, BorderLayout.CENTER);
}
private DataSourceSummaryTabbedPane summaryPanel;
}

View File

@ -109,10 +109,12 @@ public class SaveSnapshotAsReport extends Action {
String reportName = JOptionPane.showInputDialog(SwingUtilities.windowForComponent(controller.getTopComponent()), message, String reportName = JOptionPane.showInputDialog(SwingUtilities.windowForComponent(controller.getTopComponent()), message,
Bundle.SaveSnapShotAsReport_action_dialogs_title(), JOptionPane.QUESTION_MESSAGE); Bundle.SaveSnapShotAsReport_action_dialogs_title(), JOptionPane.QUESTION_MESSAGE);
// if reportName is null then cancel was selected, if reportName is empty then ok was selected and no report name specified
if (reportName != null) {
reportName = StringUtils.defaultIfBlank(reportName, defaultReportName);
reportName = StringUtils.defaultIfBlank(reportName, defaultReportName); createReport(controller, reportName, generationDate, snapshot);
}
createReport(controller, reportName, generationDate, snapshot);
}); });
}); });
} }

View File

@ -36,7 +36,7 @@ KeywordSearchResultFactory.createNodeForKey.noResultsFound.text=No results found
KeywordSearchResultFactory.query.exception.msg=Could not perform the query KeywordSearchResultFactory.query.exception.msg=Could not perform the query
OpenIDE-Module-Display-Category=Ingest Module OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\n\The module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found. OpenIDE-Module-Long-Description=Keyword Search ingest module.\n\nThe module indexes files found in the disk image at ingest time.\nIt then periodically runs the search on the indexed files using one or more keyword lists (containing pure words and/or regular expressions) and posts results.\n\nThe module also contains additional tools integrated in the main GUI, such as keyword list configuration, keyword search bar in the top-right corner, extracted text viewer and search results viewer showing highlighted keywords found.
OpenIDE-Module-Name=KeywordSearch OpenIDE-Module-Name=KeywordSearch
OptionsCategory_Name_KeywordSearchOptions=Keyword Search OptionsCategory_Name_KeywordSearchOptions=Keyword Search
OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search OptionsCategory_Keywords_KeywordSearchOptions=Keyword Search

View File

@ -18,15 +18,18 @@
*/ */
package org.sleuthkit.autopsy.keywordsearch; package org.sleuthkit.autopsy.keywordsearch;
import java.awt.Component;
import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener; import java.beans.PropertyChangeListener;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import javax.swing.JLabel;
import javax.swing.JTable; import javax.swing.JTable;
import javax.swing.ListSelectionModel; import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel; import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableColumn; import javax.swing.table.TableColumn;
import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT; import org.sleuthkit.autopsy.coreutils.StringExtract.StringExtractUnicodeTable.SCRIPT;
import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestModuleIngestJobSettings;
@ -81,6 +84,7 @@ public final class KeywordSearchJobSettingsPanel extends IngestModuleIngestJobSe
if (i == 0) { if (i == 0) {
column.setPreferredWidth(((int) (width * 0.07))); column.setPreferredWidth(((int) (width * 0.07)));
} else { } else {
column.setCellRenderer(new KeywordTableCellRenderer());
column.setPreferredWidth(((int) (width * 0.92))); column.setPreferredWidth(((int) (width * 0.92)));
} }
} }
@ -179,6 +183,25 @@ public final class KeywordSearchJobSettingsPanel extends IngestModuleIngestJobSe
displayEncodings(); displayEncodings();
tableModel.fireTableDataChanged(); tableModel.fireTableDataChanged();
} }
/**
* Simple TableCellRenderer to add tool tips to cells.
*/
private static final class KeywordTableCellRenderer extends DefaultTableCellRenderer{
private static final long serialVersionUID = 1L;
@Override
public Component getTableCellRendererComponent(
JTable table, Object value,
boolean isSelected, boolean hasFocus,
int row, int column) {
JLabel label = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
label.setToolTipText(label.getText());
return label;
}
}
private class KeywordListsTableModel extends AbstractTableModel { private class KeywordListsTableModel extends AbstractTableModel {

View File

@ -2,7 +2,14 @@ OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy.
OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Name=RecentActivity
OpenIDE-Module-Short-Description=Recent Activity finder ingest module OpenIDE-Module-Short-Description=Recent Activity finder ingest module
Chrome.moduleName=Chrome Browser.name.Microsoft.Edge=Microsoft Edge
Browser.name.Yandex=Yandex
Browser.name.Opera=Opera
Browser.name.SalamWeb=SalamWeb
Browser.name.UC.Browser=UC Browser
Browser.name.Brave=Brave
Browser.name.Google.Chrome=Google Chrome
Chrome.moduleName=Chromium
Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files. Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files.
Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files. Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files.
Chrome.getHistory.errMsg.errAnalyzingFile={0}: Error while trying to analyze file:{1} Chrome.getHistory.errMsg.errAnalyzingFile={0}: Error while trying to analyze file:{1}

View File

@ -5,15 +5,10 @@ ChromeCacheExtract_adding_artifacts_msg=Chrome Cache: Adding %d artifacts for an
ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis. ChromeCacheExtract_adding_extracted_files_msg=Chrome Cache: Adding %d extracted files for analysis.
ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s. ChromeCacheExtract_loading_files_msg=Chrome Cache: Loading files from %s.
ChromeCacheExtractor.moduleName=ChromeCacheExtractor ChromeCacheExtractor.moduleName=ChromeCacheExtractor
# {0} - module name
# {1} - row number
# {2} - table length
# {3} - cache path
ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3} ChromeCacheExtractor.progressMsg={0}: Extracting cache entry {1} of {2} entries from {3}
DataSourceUsage_AndroidMedia=Android Media Card DataSourceUsage_AndroidMedia=Android Media Card
DataSourceUsage_DJU_Drone_DAT=DJI Internal SD Card DataSourceUsage_DJU_Drone_DAT=DJI Internal SD Card
DataSourceUsage_FlashDrive=Flash Drive DataSourceUsage_FlashDrive=Flash Drive
# {0} - OS name
DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0}) DataSourceUsageAnalyzer.customVolume.label=OS Drive ({0})
DataSourceUsageAnalyzer.parentModuleName=Recent Activity DataSourceUsageAnalyzer.parentModuleName=Recent Activity
Extract.indexError.message=Failed to index artifact for keyword search. Extract.indexError.message=Failed to index artifact for keyword search.
@ -77,10 +72,17 @@ ExtractZone_progress_Msg=Extracting :Zone.Identifer files
ExtractZone_Restricted=Restricted Sites Zone ExtractZone_Restricted=Restricted Sites Zone
ExtractZone_Trusted=Trusted Sites Zone ExtractZone_Trusted=Trusted Sites Zone
OpenIDE-Module-Display-Category=Ingest Module OpenIDE-Module-Display-Category=Ingest Module
OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\n\The module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy. OpenIDE-Module-Long-Description=Recent Activity ingest module.\n\nThe module extracts useful information about the recent user activity on the disk image being ingested, such as:\n\n- Recently open documents,\n- Web activity (sites visited, stored cookies, book marked sites, search engine queries, file downloads),\n- Recently attached devices,\n- Installed programs.\n\nThe module currently supports Windows only disk images.\nThe plugin is also fully functional when deployed on Windows version of Autopsy.
OpenIDE-Module-Name=RecentActivity OpenIDE-Module-Name=RecentActivity
OpenIDE-Module-Short-Description=Recent Activity finder ingest module OpenIDE-Module-Short-Description=Recent Activity finder ingest module
Chrome.moduleName=Chrome Browser.name.Microsoft.Edge=Microsoft Edge
Browser.name.Yandex=Yandex
Browser.name.Opera=Opera
Browser.name.SalamWeb=SalamWeb
Browser.name.UC.Browser=UC Browser
Browser.name.Brave=Brave
Browser.name.Google.Chrome=Google Chrome
Chrome.moduleName=Chromium
Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files. Chrome.getHistory.errMsg.errGettingFiles=Error when trying to get Chrome history files.
Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files. Chrome.getHistory.errMsg.couldntFindAnyFiles=Could not find any allocated Chrome history files.
Chrome.getHistory.errMsg.errAnalyzingFile={0}: Error while trying to analyze file:{1} Chrome.getHistory.errMsg.errAnalyzingFile={0}: Error while trying to analyze file:{1}
@ -148,14 +150,14 @@ Firefox.getDlV24.errMsg.errAnalyzeFile={0}: Error while trying to analyze file:{
Firefox.getDlV24.errMsg.errParsingArtifacts={0}: Error parsing {1} Firefox web download artifacts. Firefox.getDlV24.errMsg.errParsingArtifacts={0}: Error parsing {1} Firefox web download artifacts.
Progress_Message_Analyze_Registry=Analyzing Registry Files Progress_Message_Analyze_Registry=Analyzing Registry Files
Progress_Message_Analyze_Usage=Data Sources Usage Analysis Progress_Message_Analyze_Usage=Data Sources Usage Analysis
Progress_Message_Chrome_AutoFill=Chrome Auto Fill Progress_Message_Chrome_AutoFill=Chrome Auto Fill Browser {0}
Progress_Message_Chrome_Bookmarks=Chrome Bookmarks Progress_Message_Chrome_Bookmarks=Chrome Bookmarks Browser {0}
Progress_Message_Chrome_Cache=Chrome Cache Progress_Message_Chrome_Cache=Chrome Cache
Progress_Message_Chrome_Cookies=Chrome Cookies Progress_Message_Chrome_Cookies=Chrome Cookies Browser {0}
Progress_Message_Chrome_Downloads=Chrome Downloads Progress_Message_Chrome_Downloads=Chrome Downloads Browser {0}
Progress_Message_Chrome_FormHistory=Chrome Form History Progress_Message_Chrome_FormHistory=Chrome Form History
Progress_Message_Chrome_History=Chrome History Progress_Message_Chrome_History=Chrome History Browser {0}
Progress_Message_Chrome_Logins=Chrome Logins Progress_Message_Chrome_Logins=Chrome Logins Browser {0}
Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks Progress_Message_Edge_Bookmarks=Microsoft Edge Bookmarks
Progress_Message_Edge_Cookies=Microsoft Edge Cookies Progress_Message_Edge_Cookies=Microsoft Edge Cookies
Progress_Message_Edge_History=Microsoft Edge History Progress_Message_Edge_History=Microsoft Edge History
@ -209,7 +211,6 @@ Recently_Used_Artifacts_Winrar=Recently opened according to WinRAR MRU
Registry_System_Bam=Recently Executed according to Background Activity Moderator (BAM) Registry_System_Bam=Recently Executed according to Background Activity Moderator (BAM)
RegRipperFullNotFound=Full version RegRipper executable not found. RegRipperFullNotFound=Full version RegRipper executable not found.
RegRipperNotFound=Autopsy RegRipper executable not found. RegRipperNotFound=Autopsy RegRipper executable not found.
# {0} - file name
SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}. SearchEngineURLQueryAnalyzer.init.exception.msg=Unable to find {0}.
SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine SearchEngineURLQueryAnalyzer.moduleName.text=Search Engine
SearchEngineURLQueryAnalyzer.engineName.none=NONE SearchEngineURLQueryAnalyzer.engineName.none=NONE

View File

@ -22,6 +22,7 @@
*/ */
package org.sleuthkit.autopsy.recentactivity; package org.sleuthkit.autopsy.recentactivity;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonIOException; import com.google.gson.JsonIOException;
@ -58,9 +59,9 @@ import org.sleuthkit.datamodel.TskData;
import org.sleuthkit.datamodel.blackboardutils.WebBrowserArtifactsHelper; import org.sleuthkit.datamodel.blackboardutils.WebBrowserArtifactsHelper;
/** /**
* Chrome recent activity extraction * Chromium recent activity extraction
*/ */
class Chrome extends Extract { class Chromium extends Extract {
private static final String HISTORY_QUERY = "SELECT urls.url, urls.title, urls.visit_count, urls.typed_count, " //NON-NLS private static final String HISTORY_QUERY = "SELECT urls.url, urls.title, urls.visit_count, urls.typed_count, " //NON-NLS
+ "last_visit_time, urls.hidden, visits.visit_time, (SELECT urls.url FROM urls WHERE urls.id=visits.url) AS from_visit, visits.transition FROM urls, visits WHERE urls.id = visits.url"; //NON-NLS + "last_visit_time, urls.hidden, visits.visit_time, (SELECT urls.url FROM urls WHERE urls.id=visits.url) AS from_visit, visits.transition FROM urls, visits WHERE urls.id = visits.url"; //NON-NLS
@ -83,20 +84,31 @@ class Chrome extends Extract {
private final Logger logger = Logger.getLogger(this.getClass().getName()); private final Logger logger = Logger.getLogger(this.getClass().getName());
private Content dataSource; private Content dataSource;
private IngestJobContext context; private IngestJobContext context;
private static final Map<String, String> BROWSERS_MAP = ImmutableMap.<String, String>builder()
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Microsoft.Edge"), "Microsoft/Edge")
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Yandex"), "YandexBrowser")
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Opera"), "Opera Software")
.put(NbBundle.getMessage(Chromium.class, "Browser.name.SalamWeb"), "SalamWeb")
.put(NbBundle.getMessage(Chromium.class, "Browser.name.UC.Browser"), "UCBrowser")
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Brave"), "BraveSoftware")
.put(NbBundle.getMessage(Chromium.class, "Browser.name.Google.Chrome"), "Chrome")
.build();
@Messages({ @Messages({
"Progress_Message_Chrome_History=Chrome History", "Progress_Message_Chrome_History=Chrome History Browser {0}",
"Progress_Message_Chrome_Bookmarks=Chrome Bookmarks", "Progress_Message_Chrome_Bookmarks=Chrome Bookmarks Browser {0}",
"Progress_Message_Chrome_Cookies=Chrome Cookies", "Progress_Message_Chrome_Cookies=Chrome Cookies Browser {0}",
"Progress_Message_Chrome_Downloads=Chrome Downloads", "Progress_Message_Chrome_Downloads=Chrome Downloads Browser {0}",
"Progress_Message_Chrome_FormHistory=Chrome Form History", "Progress_Message_Chrome_FormHistory=Chrome Form History",
"Progress_Message_Chrome_AutoFill=Chrome Auto Fill", "Progress_Message_Chrome_AutoFill=Chrome Auto Fill Browser {0}",
"Progress_Message_Chrome_Logins=Chrome Logins", "Progress_Message_Chrome_Logins=Chrome Logins Browser {0}",
"Progress_Message_Chrome_Cache=Chrome Cache", "Progress_Message_Chrome_Cache=Chrome Cache",
}) })
Chrome() { Chromium() {
moduleName = NbBundle.getMessage(Chrome.class, "Chrome.moduleName"); moduleName = NbBundle.getMessage(Chromium.class, "Chrome.moduleName");
} }
@Override @Override
@ -105,55 +117,60 @@ class Chrome extends Extract {
this.context = context; this.context = context;
dataFound = false; dataFound = false;
progressBar.progress(Bundle.Progress_Message_Chrome_History()); for (Map.Entry<String, String> browser : BROWSERS_MAP.entrySet()) {
this.getHistory(); String browserName = browser.getKey();
if (context.dataSourceIngestIsCancelled()) { String browserLocation = browser.getValue();
return; progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_History", browserName));
} this.getHistory(browser.getKey(), browser.getValue());
if (context.dataSourceIngestIsCancelled()) {
progressBar.progress(Bundle.Progress_Message_Chrome_Bookmarks()); return;
this.getBookmark(); }
if (context.dataSourceIngestIsCancelled()) {
return; progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Bookmarks", browserName));
} this.getBookmark(browser.getKey(), browser.getValue());
if (context.dataSourceIngestIsCancelled()) {
progressBar.progress(Bundle.Progress_Message_Chrome_Cookies()); return;
this.getCookie(); }
if (context.dataSourceIngestIsCancelled()) {
return; progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Cookies", browserName));
} this.getCookie(browser.getKey(), browser.getValue());
if (context.dataSourceIngestIsCancelled()) {
progressBar.progress(Bundle.Progress_Message_Chrome_Logins()); return;
this.getLogins(); }
if (context.dataSourceIngestIsCancelled()) {
return; progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Logins", browserName));
} this.getLogins(browser.getKey(), browser.getValue());
if (context.dataSourceIngestIsCancelled()) {
progressBar.progress(Bundle.Progress_Message_Chrome_AutoFill()); return;
this.getAutofill(); }
if (context.dataSourceIngestIsCancelled()) {
return; progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_AutoFill", browserName));
} this.getAutofill(browser.getKey(), browser.getValue());
if (context.dataSourceIngestIsCancelled()) {
progressBar.progress(Bundle.Progress_Message_Chrome_Downloads()); return;
this.getDownload(); }
if (context.dataSourceIngestIsCancelled()) {
return; progressBar.progress(NbBundle.getMessage(this.getClass(), "Progress_Message_Chrome_Downloads", browserName));
this.getDownload(browser.getKey(), browser.getValue());
if (context.dataSourceIngestIsCancelled()) {
return;
}
} }
progressBar.progress(Bundle.Progress_Message_Chrome_Cache()); progressBar.progress(Bundle.Progress_Message_Chrome_Cache());
ChromeCacheExtractor chromeCacheExtractor = new ChromeCacheExtractor(dataSource, context, progressBar); ChromeCacheExtractor chromeCacheExtractor = new ChromeCacheExtractor(dataSource, context, progressBar);
chromeCacheExtractor.processCaches(); chromeCacheExtractor.processCaches();
} }
/** /**
* Query for history databases and add artifacts * Query for history databases and add artifacts
*/ */
private void getHistory() { private void getHistory(String browser, String browserLocation) {
FileManager fileManager = currentCase.getServices().getFileManager(); FileManager fileManager = currentCase.getServices().getFileManager();
List<AbstractFile> historyFiles; List<AbstractFile> historyFiles;
try { try {
historyFiles = fileManager.findFiles(dataSource, "History", "Chrome"); //NON-NLS historyFiles = fileManager.findFiles(dataSource, "%History%", browserLocation); //NON-NLS
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errGettingFiles"); String msg = NbBundle.getMessage(this.getClass(), "Chrome.getHistory.errMsg.errGettingFiles");
logger.log(Level.SEVERE, msg, ex); logger.log(Level.SEVERE, msg, ex);
@ -179,10 +196,11 @@ class Chrome extends Extract {
dataFound = true; dataFound = true;
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>(); Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
int j = 0; int j = 0;
while (j < historyFiles.size()) { while (j < allocatedHistoryFiles.size()) {
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + historyFiles.get(j).getName() + j + ".db"; //NON-NLS String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + allocatedHistoryFiles.get(j).getName() + j + ".db"; //NON-NLS
final AbstractFile historyFile = historyFiles.get(j++); final AbstractFile historyFile = allocatedHistoryFiles.get(j++);
if (historyFile.getSize() == 0) { if ((historyFile.getSize() == 0) || (historyFile.getName().toLowerCase().contains("-slack"))
|| (historyFile.getName().toLowerCase().contains("cache")) || (historyFile.getName().toLowerCase().contains("media"))) {
continue; continue;
} }
try { try {
@ -223,8 +241,7 @@ class Chrome extends Extract {
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS ((result.get("title").toString() != null) ? result.get("title").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(), browser));
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(),
(NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS (NetworkUtils.extractDomain((result.get("url").toString() != null) ? result.get("url").toString() : "")))); //NON-NLS
@ -245,11 +262,11 @@ class Chrome extends Extract {
/** /**
* Search for bookmark files and make artifacts. * Search for bookmark files and make artifacts.
*/ */
private void getBookmark() { private void getBookmark(String browser, String browserLocation) {
FileManager fileManager = currentCase.getServices().getFileManager(); FileManager fileManager = currentCase.getServices().getFileManager();
List<AbstractFile> bookmarkFiles; List<AbstractFile> bookmarkFiles;
try { try {
bookmarkFiles = fileManager.findFiles(dataSource, "Bookmarks", "Chrome"); //NON-NLS bookmarkFiles = fileManager.findFiles(dataSource, "%Bookmarks%", browserLocation); //NON-NLS
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errGettingFiles"); String msg = NbBundle.getMessage(this.getClass(), "Chrome.getBookmark.errMsg.errGettingFiles");
logger.log(Level.SEVERE, msg, ex); logger.log(Level.SEVERE, msg, ex);
@ -268,10 +285,12 @@ class Chrome extends Extract {
while (j < bookmarkFiles.size()) { while (j < bookmarkFiles.size()) {
AbstractFile bookmarkFile = bookmarkFiles.get(j++); AbstractFile bookmarkFile = bookmarkFiles.get(j++);
if (bookmarkFile.getSize() == 0) { if ((bookmarkFile.getSize() == 0) || (bookmarkFile.getName().toLowerCase().contains("-slack"))
|| (bookmarkFile.getName().toLowerCase().contains("extras")) || (bookmarkFile.getName().toLowerCase().contains("log"))
|| (bookmarkFile.getName().toLowerCase().contains("backup")) || (bookmarkFile.getName().toLowerCase().contains("visualized"))) {
continue; continue;
} }
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + bookmarkFile.getName() + j + ".db"; //NON-NLS
try { try {
ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled); ContentUtils.writeToFile(bookmarkFile, new File(temps), context::dataSourceIngestIsCancelled);
} catch (ReadContentInputStreamException ex) { } catch (ReadContentInputStreamException ex) {
@ -359,8 +378,7 @@ class Chrome extends Extract {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
RecentActivityExtracterModuleFactory.getModuleName(), (date / 1000000) - Long.valueOf("11644473600"))); RecentActivityExtracterModuleFactory.getModuleName(), (date / 1000000) - Long.valueOf("11644473600")));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(), browser));
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
RecentActivityExtracterModuleFactory.getModuleName(), domain)); RecentActivityExtracterModuleFactory.getModuleName(), domain));
bbart.addAttributes(bbattributes); bbart.addAttributes(bbattributes);
@ -381,12 +399,12 @@ class Chrome extends Extract {
/** /**
* Queries for cookie files and adds artifacts * Queries for cookie files and adds artifacts
*/ */
private void getCookie() { private void getCookie(String browser, String browserLocation) {
FileManager fileManager = currentCase.getServices().getFileManager(); FileManager fileManager = currentCase.getServices().getFileManager();
List<AbstractFile> cookiesFiles; List<AbstractFile> cookiesFiles;
try { try {
cookiesFiles = fileManager.findFiles(dataSource, "Cookies", "Chrome"); //NON-NLS cookiesFiles = fileManager.findFiles(dataSource, "%Cookies%", browserLocation); //NON-NLS
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errGettingFiles"); String msg = NbBundle.getMessage(this.getClass(), "Chrome.getCookie.errMsg.errGettingFiles");
logger.log(Level.SEVERE, msg, ex); logger.log(Level.SEVERE, msg, ex);
@ -404,10 +422,10 @@ class Chrome extends Extract {
int j = 0; int j = 0;
while (j < cookiesFiles.size()) { while (j < cookiesFiles.size()) {
AbstractFile cookiesFile = cookiesFiles.get(j++); AbstractFile cookiesFile = cookiesFiles.get(j++);
if (cookiesFile.getSize() == 0) { if ((cookiesFile.getSize() == 0) || (cookiesFile.getName().toLowerCase().contains("-slack"))) {
continue; continue;
} }
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + cookiesFile.getName() + j + ".db"; //NON-NLS
try { try {
ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled); ContentUtils.writeToFile(cookiesFile, new File(temps), context::dataSourceIngestIsCancelled);
} catch (ReadContentInputStreamException ex) { } catch (ReadContentInputStreamException ex) {
@ -447,8 +465,7 @@ class Chrome extends Extract {
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS ((result.get("value").toString() != null) ? result.get("value").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(), browser));
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
String domain = result.get("host_key").toString(); //NON-NLS String domain = result.get("host_key").toString(); //NON-NLS
domain = domain.replaceFirst("^\\.+(?!$)", ""); domain = domain.replaceFirst("^\\.+(?!$)", "");
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
@ -471,11 +488,11 @@ class Chrome extends Extract {
/** /**
* Queries for download files and adds artifacts * Queries for download files and adds artifacts
*/ */
private void getDownload() { private void getDownload(String browser, String browserLocation) {
FileManager fileManager = currentCase.getServices().getFileManager(); FileManager fileManager = currentCase.getServices().getFileManager();
List<AbstractFile> downloadFiles; List<AbstractFile> downloadFiles;
try { try {
downloadFiles = fileManager.findFiles(dataSource, "History", "Chrome"); //NON-NLS downloadFiles = fileManager.findFiles(dataSource, "%History%", browserLocation); //NON-NLS
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errGettingFiles"); String msg = NbBundle.getMessage(this.getClass(), "Chrome.getDownload.errMsg.errGettingFiles");
logger.log(Level.SEVERE, msg, ex); logger.log(Level.SEVERE, msg, ex);
@ -493,10 +510,12 @@ class Chrome extends Extract {
int j = 0; int j = 0;
while (j < downloadFiles.size()) { while (j < downloadFiles.size()) {
AbstractFile downloadFile = downloadFiles.get(j++); AbstractFile downloadFile = downloadFiles.get(j++);
if (downloadFile.getSize() == 0) { if ((downloadFile.getSize() == 0) || (downloadFile.getName().toLowerCase().contains("-slack"))
|| (downloadFile.getName().toLowerCase().contains("cache"))) {
continue; continue;
} }
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS
String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + downloadFile.getName() + j + ".db"; //NON-NLS
try { try {
ContentUtils.writeToFile(downloadFile, new File(temps), context::dataSourceIngestIsCancelled); ContentUtils.writeToFile(downloadFile, new File(temps), context::dataSourceIngestIsCancelled);
} catch (ReadContentInputStreamException ex) { } catch (ReadContentInputStreamException ex) {
@ -552,8 +571,7 @@ class Chrome extends Extract {
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DOMAIN,
RecentActivityExtracterModuleFactory.getModuleName(), domain)); RecentActivityExtracterModuleFactory.getModuleName(), domain));
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(), browser));
NbBundle.getMessage(this.getClass(), "Chrome.moduleName")));
BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes); BlackboardArtifact webDownloadArtifact = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_DOWNLOAD, downloadFile, bbattributes);
if (webDownloadArtifact != null) { if (webDownloadArtifact != null) {
@ -561,7 +579,7 @@ class Chrome extends Extract {
// find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact. // find the downloaded file and create a TSK_ASSOCIATED_OBJECT for it, associating it with the TSK_WEB_DOWNLOAD artifact.
try { try {
String normalizedFullPath = FilenameUtils.normalize(fullPath, true); String normalizedFullPath = FilenameUtils.normalize(fullPath, true);
for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(normalizedFullPath), FilenameUtils.getPath(normalizedFullPath))) { for (AbstractFile downloadedFile : fileManager.findFiles(dataSource, FilenameUtils.getName(normalizedFullPath), FilenameUtils.getPath(normalizedFullPath))) {
BlackboardArtifact associatedObjectArtifact = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT); BlackboardArtifact associatedObjectArtifact = downloadedFile.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_ASSOCIATED_OBJECT);
associatedObjectArtifact.addAttribute( associatedObjectArtifact.addAttribute(
@ -588,12 +606,12 @@ class Chrome extends Extract {
/** /**
* Gets user logins from Login Data sqlite database * Gets user logins from Login Data sqlite database
*/ */
private void getLogins() { private void getLogins(String browser, String browserLocation) {
FileManager fileManager = currentCase.getServices().getFileManager(); FileManager fileManager = currentCase.getServices().getFileManager();
List<AbstractFile> loginDataFiles; List<AbstractFile> loginDataFiles;
try { try {
loginDataFiles = fileManager.findFiles(dataSource, "Login Data", "Chrome"); //NON-NLS loginDataFiles = fileManager.findFiles(dataSource, "%Login Data%", browserLocation); //NON-NLS
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errGettingFiles"); String msg = NbBundle.getMessage(this.getClass(), "Chrome.getLogin.errMsg.errGettingFiles");
logger.log(Level.SEVERE, msg, ex); logger.log(Level.SEVERE, msg, ex);
@ -611,10 +629,10 @@ class Chrome extends Extract {
int j = 0; int j = 0;
while (j < loginDataFiles.size()) { while (j < loginDataFiles.size()) {
AbstractFile loginDataFile = loginDataFiles.get(j++); AbstractFile loginDataFile = loginDataFiles.get(j++);
if (loginDataFile.getSize() == 0) { if ((loginDataFile.getSize() == 0) || (loginDataFile.getName().toLowerCase().contains("-slack"))) {
continue; continue;
} }
String temps = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS String temps = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + loginDataFile.getName() + j + ".db"; //NON-NLS
try { try {
ContentUtils.writeToFile(loginDataFile, new File(temps), context::dataSourceIngestIsCancelled); ContentUtils.writeToFile(loginDataFile, new File(temps), context::dataSourceIngestIsCancelled);
} catch (ReadContentInputStreamException ex) { } catch (ReadContentInputStreamException ex) {
@ -661,6 +679,9 @@ class Chrome extends Extract {
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(),
((result.get("signon_realm").toString() != null) ? result.get("signon_realm").toString() : ""))); //NON-NLS ((result.get("signon_realm").toString() != null) ? result.get("signon_realm").toString() : ""))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(), browser));
BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT, loginDataFile, bbattributes); BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_SERVICE_ACCOUNT, loginDataFile, bbattributes);
if (bbart != null) { if (bbart != null) {
bbartifacts.add(bbart); bbartifacts.add(bbart);
@ -679,12 +700,12 @@ class Chrome extends Extract {
* Gets and parses Autofill data from 'Web Data' database, * Gets and parses Autofill data from 'Web Data' database,
* and creates TSK_WEB_FORM_AUTOFILL, TSK_WEB_FORM_ADDRESS artifacts * and creates TSK_WEB_FORM_AUTOFILL, TSK_WEB_FORM_ADDRESS artifacts
*/ */
private void getAutofill() { private void getAutofill(String browser, String browserLocation) {
FileManager fileManager = currentCase.getServices().getFileManager(); FileManager fileManager = currentCase.getServices().getFileManager();
List<AbstractFile> webDataFiles; List<AbstractFile> webDataFiles;
try { try {
webDataFiles = fileManager.findFiles(dataSource, "Web Data", "Chrome"); //NON-NLS webDataFiles = fileManager.findFiles(dataSource, "%Web Data%", browserLocation); //NON-NLS
} catch (TskCoreException ex) { } catch (TskCoreException ex) {
String msg = NbBundle.getMessage(this.getClass(), "Chrome.getAutofills.errMsg.errGettingFiles"); String msg = NbBundle.getMessage(this.getClass(), "Chrome.getAutofills.errMsg.errGettingFiles");
logger.log(Level.SEVERE, msg, ex); logger.log(Level.SEVERE, msg, ex);
@ -702,10 +723,10 @@ class Chrome extends Extract {
int j = 0; int j = 0;
while (j < webDataFiles.size()) { while (j < webDataFiles.size()) {
AbstractFile webDataFile = webDataFiles.get(j++); AbstractFile webDataFile = webDataFiles.get(j++);
if (webDataFile.getSize() == 0) { if ((webDataFile.getSize() == 0) || (webDataFile.getName().toLowerCase().contains("-slack"))) {
continue; continue;
} }
String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, "chrome") + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS String tempFilePath = RAImageIngestModule.getRATempPath(currentCase, browser) + File.separator + webDataFile.getName() + j + ".db"; //NON-NLS
try { try {
ContentUtils.writeToFile(webDataFile, new File(tempFilePath), context::dataSourceIngestIsCancelled); ContentUtils.writeToFile(webDataFile, new File(tempFilePath), context::dataSourceIngestIsCancelled);
} catch (ReadContentInputStreamException ex) { } catch (ReadContentInputStreamException ex) {
@ -731,7 +752,7 @@ class Chrome extends Extract {
boolean isSchemaV8X = Util.checkColumn("date_created", "autofill", tempFilePath); boolean isSchemaV8X = Util.checkColumn("date_created", "autofill", tempFilePath);
// get form autofill artifacts // get form autofill artifacts
bbartifacts.addAll(getFormAutofillArtifacts(webDataFile, tempFilePath, isSchemaV8X)); bbartifacts.addAll(getFormAutofillArtifacts(webDataFile, tempFilePath, isSchemaV8X, browser));
try { try {
// get form address atifacts // get form address atifacts
getFormAddressArtifacts(webDataFile, tempFilePath, isSchemaV8X); getFormAddressArtifacts(webDataFile, tempFilePath, isSchemaV8X);
@ -757,7 +778,7 @@ class Chrome extends Extract {
* *
* @return collection of TSK_WEB_FORM_AUTOFILL artifacts * @return collection of TSK_WEB_FORM_AUTOFILL artifacts
*/ */
private Collection<BlackboardArtifact> getFormAutofillArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X ) { private Collection<BlackboardArtifact> getFormAutofillArtifacts (AbstractFile webDataFile, String dbFilePath , boolean isSchemaV8X, String browser ) {
Collection<BlackboardArtifact> bbartifacts = new ArrayList<>(); Collection<BlackboardArtifact> bbartifacts = new ArrayList<>();
@ -783,7 +804,7 @@ class Chrome extends Extract {
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(),
(Integer.valueOf(result.get("count").toString())))); //NON-NLS (Integer.valueOf(result.get("count").toString())))); //NON-NLS
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED,
RecentActivityExtracterModuleFactory.getModuleName(), RecentActivityExtracterModuleFactory.getModuleName(),
Long.valueOf(result.get("date_created").toString()))); //NON-NLS Long.valueOf(result.get("date_created").toString()))); //NON-NLS
@ -794,6 +815,9 @@ class Chrome extends Extract {
Long.valueOf(result.get("date_last_used").toString()))); //NON-NLS Long.valueOf(result.get("date_last_used").toString()))); //NON-NLS
} }
bbattributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_PROG_NAME,
RecentActivityExtracterModuleFactory.getModuleName(), browser));
// Add an artifact // Add an artifact
BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_FORM_AUTOFILL, webDataFile, bbattributes); BlackboardArtifact bbart = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_WEB_FORM_AUTOFILL, webDataFile, bbattributes);
if (bbart != null) { if (bbart != null) {
@ -886,7 +910,7 @@ class Chrome extends Extract {
use_count, otherAttributes); use_count, otherAttributes);
} }
} }
private boolean isChromePreVersion30(String temps) { private boolean isChromePreVersion30(String temps) {
String query = "PRAGMA table_info(downloads)"; //NON-NLS String query = "PRAGMA table_info(downloads)"; //NON-NLS
List<HashMap<String, Object>> columns = this.dbConnect(temps, query); List<HashMap<String, Object>> columns = this.dbConnect(temps, query);

View File

@ -272,6 +272,8 @@ final class ExtractPrefetch extends Extract {
new BlackboardAttribute( new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, getName(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PROG_NAME, getName(),
applicationName),//NON-NLS applicationName),//NON-NLS
new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH, getName(), filePath),
new BlackboardAttribute( new BlackboardAttribute(
BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, getName(), BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME, getName(),
executionTime), executionTime),

View File

@ -83,9 +83,12 @@ import static org.sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_OS_AC
import org.sleuthkit.datamodel.BlackboardAttribute; import org.sleuthkit.datamodel.BlackboardAttribute;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_ASSOCIATED_ARTIFACT;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_ACCESSED;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_MODIFIED;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_ID;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_NAME;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_PATH;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_ID;
import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME; import static org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_USER_NAME;
@ -353,12 +356,10 @@ class ExtractRegistry extends Extract {
} catch (IOException | TskCoreException ex) { } catch (IOException | TskCoreException ex) {
logger.log(Level.WARNING, String.format("Unable to get shell bags from file %s", regOutputFiles.fullPlugins), ex); logger.log(Level.WARNING, String.format("Unable to get shell bags from file %s", regOutputFiles.fullPlugins), ex);
} }
} else if (regFileNameLocal.toLowerCase().contains("system")) { } else if (regFileNameLocal.toLowerCase().contains("system") && parseSystemPluginOutput(regOutputFiles.fullPlugins, regFile) == false) {
try { this.addErrorMessage(
createSystemArtifacts(regOutputFiles.fullPlugins, regFile); NbBundle.getMessage(this.getClass(), "ExtractRegistry.analyzeRegFiles.failedParsingResults",
} catch (IOException ex) { this.getName(), regFileName));
logger.log(Level.WARNING, String.format("Unable to get artifacts from file %s", regOutputFiles.fullPlugins), ex);
}
} }
try { try {
Report report = currentCase.addReport(regOutputFiles.fullPlugins, Report report = currentCase.addReport(regOutputFiles.fullPlugins,
@ -952,6 +953,119 @@ class ExtractRegistry extends Extract {
return false; return false;
} }
private boolean parseSystemPluginOutput(String regfilePath, AbstractFile regAbstractFile) {
File regfile = new File(regfilePath);
try (BufferedReader reader = new BufferedReader(new FileReader(regfile))) {
String line = reader.readLine();
while (line != null) {
line = line.trim();
if (line.toLowerCase().matches("^bam v.*")) {
parseBamKey(regAbstractFile, reader, Bundle.Registry_System_Bam());
} else if (line.toLowerCase().matches("^bthport v..*")) {
parseBlueToothDevices(regAbstractFile, reader);
}
line = reader.readLine();
}
return true;
} catch (FileNotFoundException ex) {
logger.log(Level.WARNING, "Error finding the registry file.", ex); //NON-NLS
} catch (IOException ex) {
logger.log(Level.WARNING, "Error reading the system hive: {0}", ex); //NON-NLS
}
return false;
}
/**
* Create recently used artifacts to parse the regripper plugin output, this
* format is used in several diffent plugins
*
* @param regFile registry file the artifact is associated with
*
* @param reader buffered reader to parse adobemru records
*
* @param comment string that will populate attribute TSK_COMMENT
*
* @throws FileNotFound and IOException
*/
private void parseBlueToothDevices(AbstractFile regFile, BufferedReader reader) throws FileNotFoundException, IOException {
List<BlackboardArtifact> bbartifacts = new ArrayList<>();
String line = reader.readLine();
while ((line != null) && (!line.contains(SECTION_DIVIDER))) {
line = reader.readLine();
if (line != null) {
line = line.trim();
}
if ((line != null) && (line.toLowerCase().contains("device unique id"))) {
// Columns are seperated by colons :
// Data : Values
// Record is 4 lines in length (Device Unique Id, Name, Last Seen, LastConnected
while (line != null && !line.contains(SECTION_DIVIDER) && !line.isEmpty() && !line.toLowerCase().contains("radio support not found")) {
Collection<BlackboardAttribute> attributes = new ArrayList<>();
addBlueToothAttribute(line, attributes, TSK_DEVICE_ID);
line = reader.readLine();
// Name may not exist, check for it to make sure.
if ((line != null) && (line.toLowerCase().contains("name"))) {
addBlueToothAttribute(line, attributes, TSK_NAME);
line = reader.readLine();
}
addBlueToothAttribute(line, attributes, TSK_DATETIME);
line = reader.readLine();
addBlueToothAttribute(line, attributes, TSK_DATETIME_ACCESSED);
BlackboardArtifact bba = createArtifactWithAttributes(ARTIFACT_TYPE.TSK_BLUETOOTH_PAIRING, regFile, attributes);
if(bba != null) {
bbartifacts.add(bba);
}
// Read blank line between records then next read line is start of next block
reader.readLine();
line = reader.readLine();
}
if (line != null) {
line = line.trim();
}
}
}
if (!bbartifacts.isEmpty()) {
postArtifacts(bbartifacts);
}
}
private void addBlueToothAttribute(String line, Collection<BlackboardAttribute> attributes, ATTRIBUTE_TYPE attributeType) {
if (line == null) {
return;
}
String tokens[] = line.split(": ");
if (tokens.length > 1 && !tokens[1].isEmpty()) {
String tokenString = tokens[1];
if (attributeType.getDisplayName().toLowerCase().contains("date")) {
String dateString = tokenString.toLowerCase().replace(" z", "");
// date format for plugin Tue Jun 23 10:27:54 2020 Z
SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM d HH:mm:ss yyyy", US);
Long dateLong = Long.valueOf(0);
try {
Date newDate = dateFormat.parse(dateString);
dateLong = newDate.getTime()/1000;
} catch (ParseException ex) {
// catching error and displaying date that could not be parsed
// we set the timestamp to 0 and continue on processing
logger.log(Level.WARNING, String.format("Failed to parse date/time %s for Bluetooth Last Seen attribute.", dateString), ex); //NON-NLS
}
attributes.add(new BlackboardAttribute(attributeType, getName(), dateLong));
} else {
attributes.add(new BlackboardAttribute(attributeType, getName(), tokenString));
}
}
}
/** /**
* Parse the output of the SAM regripper plugin to get additional Account * Parse the output of the SAM regripper plugin to get additional Account
* information * information
@ -1280,30 +1394,6 @@ class ExtractRegistry extends Extract {
} }
} }
/**
* Create artifacts from the System registry Hive
*
* @param regFileName name of the regripper output file
*
* @param regFile registry file the artifact is associated with
*
* @throws FileNotFound and IOException
*/
private void createSystemArtifacts(String regFileName, AbstractFile regFile) throws FileNotFoundException, IOException {
File regfile = new File(regFileName);
try (BufferedReader reader = new BufferedReader(new FileReader(regfile))) {
String line = reader.readLine();
while (line != null) {
line = line.trim();
if (line.matches("^bam v.*")) {
parseBamKey(regFile, reader, Bundle.Registry_System_Bam());
}
line = reader.readLine();
}
}
}
/** /**
* Create artifacts from BAM Regripper Plugin records * Create artifacts from BAM Regripper Plugin records
* *
@ -2031,4 +2121,4 @@ class ExtractRegistry extends Extract {
public String autopsyPlugins = ""; public String autopsyPlugins = "";
public String fullPlugins = ""; public String fullPlugins = "";
} }
} }

View File

@ -70,7 +70,7 @@ public final class RAImageIngestModule implements DataSourceIngestModule {
Extract registry = new ExtractRegistry(); Extract registry = new ExtractRegistry();
Extract recentDocuments = new RecentDocumentsByLnk(); Extract recentDocuments = new RecentDocumentsByLnk();
Extract chrome = new Chrome(); Extract chrome = new Chromium();
Extract firefox = new Firefox(); Extract firefox = new Firefox();
Extract SEUQA = new SearchEngineURLQueryAnalyzer(); Extract SEUQA = new SearchEngineURLQueryAnalyzer();
Extract osExtract = new ExtractOs(); Extract osExtract = new ExtractOs();

View File

@ -131,7 +131,8 @@ def get_prop_entries(rows: List[List[str]],
should_delete_converter: Callable[[List[str]], bool] = None, should_delete_converter: Callable[[List[str]], bool] = None,
path_converter: Callable[[str], str] = None) -> Iterator[PropEntry]: path_converter: Callable[[str], str] = None) -> Iterator[PropEntry]:
"""Parses PropEntry objects from rows of values in a csv. """Parses PropEntry objects from rows of values in a csv. Any items that have an empty string value will be
ignored.
Args: Args:
rows (List[List[str]]): The csv file rows to parse. rows (List[List[str]]): The csv file rows to parse.
@ -146,9 +147,11 @@ def get_prop_entries(rows: List[List[str]],
Returns: Returns:
List[PropEntry]: The generated prop entry objects. List[PropEntry]: The generated prop entry objects.
""" """
return map(lambda row: get_prop_entry( propentry_iter = map(lambda row: get_prop_entry(row, path_idx, key_idx, value_idx, should_delete_converter,
row, path_idx, key_idx, value_idx, should_delete_converter, path_converter), path_converter), rows)
rows)
# filter rows that have no value
return filter(lambda entry: entry and entry.value.strip(), propentry_iter)
def get_should_deleted(row_items: List[str], requested_idx: int) -> bool: def get_should_deleted(row_items: List[str], requested_idx: int) -> bool:

View File

@ -1,4 +1,6 @@
The 'bin' folder is the version used when running the PhotoRec ingest module. It is also the 32-bit version. The 'bin' folder is the version used when running the PhotoRec ingest module. It is also the 32-bit version.
When the 64-bit version of the installer is created, the photorec_exec/64-bit/bin folder is placed at photorec_exec/bin. When the 64-bit version of the installer is created, the photorec_exec/64-bit/bin folder is placed at photorec_exec/bin.
When the 32-bit version of the installer is created, the photorec_exec/64-bit folder is deleted. When the 32-bit version of the installer is created, the photorec_exec/64-bit folder is deleted.
See 'build-windows-installer.xml' for more details. See 'build-windows-installer.xml' for more details.
Extensions for PhotoRec need to be placed in the PhotoRecCarverFileOptExtensions class so that only valid extensions will be used with PhotoRec. It can be generated through PhotoRec by launching photorec_win with no arguments, go to "Proceed", go to "File Opt" and press 'b'. This should generate a photorec.cfg file in the current working directory with a list of all the extensions.

View File

@ -5,6 +5,7 @@ auditfail
backuprestore backuprestore
bam bam
bam_tln bam_tln
btconfig
bthport bthport
comfoo comfoo
compname compname

View File

@ -21,13 +21,11 @@ package org.sleuthkit.autopsy.thunderbirdparser;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.logging.Level; import java.util.logging.Level;
import org.apache.james.mime4j.dom.BinaryBody;
import org.apache.james.mime4j.dom.Body; import org.apache.james.mime4j.dom.Body;
import org.apache.james.mime4j.dom.Entity; import org.apache.james.mime4j.dom.Entity;
import org.apache.james.mime4j.dom.Message; import org.apache.james.mime4j.dom.Message;
@ -227,15 +225,15 @@ class MimeJ4MessageParser {
} else if (e.getDispositionType() != null } else if (e.getDispositionType() != null
&& e.getDispositionType().equals(ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT)) { && e.getDispositionType().equals(ContentDispositionField.DISPOSITION_TYPE_ATTACHMENT)) {
handleAttachment(email, e, fileID, index); handleAttachment(email, e, fileID, index);
} else if (e.getMimeType().equals(HTML_TYPE) } else if ((e.getMimeType().equals(HTML_TYPE) && (email.getHtmlBody() == null || email.getHtmlBody().isEmpty()))
|| e.getMimeType().equals(ContentTypeField.TYPE_TEXT_PLAIN)) { || (e.getMimeType().equals(ContentTypeField.TYPE_TEXT_PLAIN) && (email.getTextBody() == null || email.getTextBody().isEmpty()))) {
handleTextBody(email, (TextBody) e.getBody(), e.getMimeType(), e.getHeader().getFields()); handleTextBody(email, (TextBody) e.getBody(), e.getMimeType(), e.getHeader().getFields());
} else { } else {
// Ignore other types. handleAttachment(email, e, fileID, index);
} }
} }
} }
/** /**
* Extract text out of a body part of the message. * Extract text out of a body part of the message.
* *